2018-04-18 09:25:40 -07:00
|
|
|
const MQTT = require('./mqtt');
|
|
|
|
const Zigbee = require('./zigbee');
|
2018-08-05 09:38:33 -07:00
|
|
|
const State = require('./state');
|
2018-04-18 09:25:40 -07:00
|
|
|
const logger = require('./util/logger');
|
2018-04-18 10:09:59 -07:00
|
|
|
const settings = require('./util/settings');
|
2018-08-28 12:55:00 -07:00
|
|
|
const ExtensionNetworkMap = require('./extension/networkMap');
|
2018-10-02 12:15:12 -07:00
|
|
|
const ExtensionSoftReset = require('./extension/softReset');
|
|
|
|
const ExtensionRouterPollXiaomi = require('./extension/routerPollXiaomi');
|
2018-11-05 13:55:30 -07:00
|
|
|
const ExtensionDevicePublish = require('./extension/devicePublish');
|
2018-06-04 11:03:53 -07:00
|
|
|
const zigbeeShepherdConverters = require('zigbee-shepherd-converters');
|
2018-04-24 10:30:56 -07:00
|
|
|
const homeassistant = require('./homeassistant');
|
2018-09-20 12:42:50 -07:00
|
|
|
const objectAssignDeep = require('object-assign-deep');
|
2018-06-15 08:48:10 -07:00
|
|
|
|
2018-04-25 11:54:41 -07:00
|
|
|
const mqttConfigRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/config/\\w+`, 'g');
|
2018-05-17 00:52:28 -07:00
|
|
|
|
2018-06-11 13:15:45 -07:00
|
|
|
const allowedLogLevels = ['error', 'warn', 'info', 'debug'];
|
|
|
|
|
2018-05-28 12:10:58 -07:00
|
|
|
/**
|
|
|
|
* Home Assistant requires ALL attributes to be present in ALL MQTT messages send by the device.
|
|
|
|
* https://community.home-assistant.io/t/missing-value-with-mqtt-only-last-data-set-is-shown/47070/9
|
|
|
|
*
|
|
|
|
* Therefore zigbee2mqtt BY DEFAULT caches all values and resend it with every message.
|
|
|
|
* advanced.cache_state in configuration.yaml allows to configure this.
|
|
|
|
* https://github.com/Koenkk/zigbee2mqtt/wiki/Configuration
|
|
|
|
*/
|
|
|
|
const cacheState = settings.get().advanced && settings.get().advanced.cache_state === false ? false : true;
|
|
|
|
if (settings.get().homeassistant && !cacheState) {
|
|
|
|
logger.warn('In order for Home Assistant integration to work properly set `cache_state: true');
|
|
|
|
}
|
|
|
|
|
2018-05-17 08:20:46 -07:00
|
|
|
class Controller {
|
2018-04-18 09:25:40 -07:00
|
|
|
constructor() {
|
2018-10-02 12:15:12 -07:00
|
|
|
this.handleZigbeeMessage = this.handleZigbeeMessage.bind(this);
|
|
|
|
this.handleMQTTMessage = this.handleMQTTMessage.bind(this);
|
|
|
|
|
|
|
|
this.zigbee = new Zigbee(this.handleZigbeeMessage);
|
2018-04-18 09:25:40 -07:00
|
|
|
this.mqtt = new MQTT();
|
2018-08-05 09:38:33 -07:00
|
|
|
this.state = new State();
|
2018-06-08 11:20:35 -07:00
|
|
|
this.configured = [];
|
2018-10-02 12:15:12 -07:00
|
|
|
this.extensions = [];
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
start() {
|
2018-06-15 08:48:10 -07:00
|
|
|
this.startupLogVersion(() => {
|
2018-10-02 12:15:12 -07:00
|
|
|
this.zigbee.start((error) => {
|
2018-06-15 08:48:10 -07:00
|
|
|
if (error) {
|
2018-06-21 11:11:08 -07:00
|
|
|
logger.error('Failed to start', error);
|
2018-06-15 08:48:10 -07:00
|
|
|
} else {
|
|
|
|
// Log zigbee clients on startup and configure.
|
|
|
|
const devices = this.zigbee.getAllClients();
|
|
|
|
logger.info(`Currently ${devices.length} devices are joined:`);
|
|
|
|
devices.forEach((device) => {
|
|
|
|
logger.info(this.getDeviceStartupLogMessage(device));
|
|
|
|
this.configureDevice(device);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Enable zigbee join.
|
|
|
|
if (settings.get().permit_join) {
|
|
|
|
logger.warn('`permit_join` set to `true` in configuration.yaml.');
|
|
|
|
logger.warn('Allowing new devices to join.');
|
|
|
|
logger.warn('Set `permit_join` to `false` once you joined all devices.');
|
|
|
|
}
|
2018-05-16 10:29:47 -07:00
|
|
|
|
2018-09-22 15:07:31 -07:00
|
|
|
this.zigbee.permitJoin(settings.get().permit_join);
|
|
|
|
|
2018-06-15 08:48:10 -07:00
|
|
|
// Connect to MQTT broker
|
|
|
|
const subscriptions = [
|
|
|
|
`${settings.get().mqtt.base_topic}/bridge/config/+`,
|
|
|
|
];
|
2018-06-11 11:20:18 -07:00
|
|
|
|
2018-06-15 08:48:10 -07:00
|
|
|
if (settings.get().homeassistant) {
|
|
|
|
subscriptions.push('hass/status');
|
|
|
|
}
|
2018-06-11 11:20:18 -07:00
|
|
|
|
2018-06-15 08:48:10 -07:00
|
|
|
this.mqtt.connect(this.handleMQTTMessage, subscriptions, () => this.handleMQTTConnected());
|
2018-06-13 10:51:40 -07:00
|
|
|
}
|
2018-06-15 08:48:10 -07:00
|
|
|
});
|
2018-04-18 09:25:40 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-06-11 11:20:18 -07:00
|
|
|
handleMQTTConnected() {
|
|
|
|
// Home Assistant MQTT discovery on MQTT connected.
|
2018-04-24 10:30:56 -07:00
|
|
|
if (settings.get().homeassistant) {
|
|
|
|
// MQTT discovery of all paired devices on startup.
|
2018-04-25 11:54:41 -07:00
|
|
|
this.zigbee.getAllClients().forEach((device) => {
|
2018-06-04 11:03:53 -07:00
|
|
|
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
|
|
|
|
if (mappedModel) {
|
2018-06-11 11:20:18 -07:00
|
|
|
homeassistant.discover(device.ieeeAddr, mappedModel.model, this.mqtt, true);
|
2018-04-25 12:34:26 -07:00
|
|
|
}
|
2018-04-24 10:30:56 -07:00
|
|
|
});
|
|
|
|
}
|
2018-06-11 11:31:05 -07:00
|
|
|
|
2018-08-28 12:55:00 -07:00
|
|
|
// Initialize extensions.
|
|
|
|
this.extensions = [
|
2018-11-05 13:55:30 -07:00
|
|
|
new ExtensionDevicePublish(this.zigbee, this.mqtt, this.state, this.mqttPublishDeviceState),
|
2018-10-23 11:39:48 -07:00
|
|
|
new ExtensionNetworkMap(this.zigbee, this.mqtt, this.state, this.mqttPublishDeviceState),
|
|
|
|
new ExtensionSoftReset(this.zigbee, this.mqtt, this.state, this.mqttPublishDeviceState),
|
|
|
|
new ExtensionRouterPollXiaomi(this.zigbee, this.mqtt, this.state, this.mqttPublishDeviceState),
|
2018-08-28 12:55:00 -07:00
|
|
|
];
|
|
|
|
|
2018-06-11 11:31:05 -07:00
|
|
|
// Resend all cached states.
|
2018-06-13 10:51:40 -07:00
|
|
|
this.sendAllCachedStates();
|
|
|
|
}
|
|
|
|
|
|
|
|
sendAllCachedStates() {
|
2018-06-11 11:31:05 -07:00
|
|
|
this.zigbee.getAllClients().forEach((device) => {
|
2018-08-05 09:38:33 -07:00
|
|
|
if (this.state.exists(device.ieeeAddr)) {
|
2018-09-20 12:42:50 -07:00
|
|
|
this.mqttPublishDeviceState(device, this.state.get(device.ieeeAddr), false);
|
2018-06-11 11:31:05 -07:00
|
|
|
}
|
|
|
|
});
|
2018-05-21 02:49:02 -07:00
|
|
|
}
|
|
|
|
|
2018-04-18 09:25:40 -07:00
|
|
|
stop(callback) {
|
2018-10-02 12:15:12 -07:00
|
|
|
this.extensions.filter((e) => e.stop).forEach((e) => e.stop());
|
2018-08-05 09:38:33 -07:00
|
|
|
this.state.save();
|
2018-04-18 09:25:40 -07:00
|
|
|
this.mqtt.disconnect();
|
|
|
|
this.zigbee.stop(callback);
|
|
|
|
}
|
|
|
|
|
2018-05-21 04:21:18 -07:00
|
|
|
configureDevice(device) {
|
2018-08-07 23:17:44 -07:00
|
|
|
let friendlyName = 'unknown';
|
2018-05-21 04:21:18 -07:00
|
|
|
const ieeeAddr = device.ieeeAddr;
|
2018-08-07 23:17:44 -07:00
|
|
|
if (settings.getDevice(ieeeAddr)) {
|
|
|
|
friendlyName = settings.getDevice(ieeeAddr).friendly_name;
|
|
|
|
}
|
2018-06-08 11:20:35 -07:00
|
|
|
if (ieeeAddr && device.modelId && !this.configured.includes(ieeeAddr)) {
|
2018-06-04 11:03:53 -07:00
|
|
|
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
|
2018-05-21 04:21:18 -07:00
|
|
|
|
2018-06-08 11:20:35 -07:00
|
|
|
// Call configure function of device.
|
2018-06-04 12:36:51 -07:00
|
|
|
if (mappedModel && mappedModel.configure) {
|
|
|
|
mappedModel.configure(ieeeAddr, this.zigbee.shepherd, this.zigbee.getCoordinator(), (ok, msg) => {
|
|
|
|
if (ok) {
|
2018-08-07 23:17:44 -07:00
|
|
|
logger.info(`Succesfully configured ${friendlyName} ${ieeeAddr}`);
|
2018-06-04 12:36:51 -07:00
|
|
|
} else {
|
2018-08-07 23:17:44 -07:00
|
|
|
logger.error(`Failed to configure ${friendlyName} ${ieeeAddr}`);
|
2018-06-04 12:36:51 -07:00
|
|
|
}
|
|
|
|
});
|
2018-05-21 04:21:18 -07:00
|
|
|
}
|
|
|
|
|
2018-06-08 11:20:35 -07:00
|
|
|
// Setup an OnAfIncomingMsg handler if needed.
|
|
|
|
if (mappedModel && mappedModel.onAfIncomingMsg) {
|
|
|
|
mappedModel.onAfIncomingMsg.forEach((ep) => this.zigbee.registerOnAfIncomingMsg(ieeeAddr, ep));
|
|
|
|
}
|
|
|
|
|
|
|
|
this.configured.push(ieeeAddr);
|
2018-05-21 04:21:18 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-16 10:29:47 -07:00
|
|
|
getDeviceStartupLogMessage(device) {
|
|
|
|
let friendlyName = 'unknown';
|
2018-05-28 11:40:30 -07:00
|
|
|
let type = 'unknown';
|
2018-05-16 10:29:47 -07:00
|
|
|
let friendlyDevice = {model: 'unkown', description: 'unknown'};
|
2018-06-04 11:03:53 -07:00
|
|
|
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
|
|
|
|
if (mappedModel) {
|
|
|
|
friendlyDevice = mappedModel;
|
2018-05-16 10:29:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (settings.getDevice(device.ieeeAddr)) {
|
2018-05-17 08:20:46 -07:00
|
|
|
friendlyName = settings.getDevice(device.ieeeAddr).friendly_name;
|
2018-05-16 10:29:47 -07:00
|
|
|
}
|
|
|
|
|
2018-05-28 11:40:30 -07:00
|
|
|
if (device.type) {
|
|
|
|
type = device.type;
|
|
|
|
}
|
|
|
|
|
2018-05-17 08:20:46 -07:00
|
|
|
return `${friendlyName} (${device.ieeeAddr}): ${friendlyDevice.model} - ` +
|
2018-05-28 11:40:30 -07:00
|
|
|
`${friendlyDevice.vendor} ${friendlyDevice.description} (${type})`;
|
2018-05-16 10:29:47 -07:00
|
|
|
}
|
|
|
|
|
2018-09-20 12:42:50 -07:00
|
|
|
getDeviceInfoForMqtt(device) {
|
|
|
|
const {type, ieeeAddr, nwkAddr, manufId, manufName, powerSource, modelId, status} = device;
|
|
|
|
const deviceSettings = settings.getDevice(device.ieeeAddr);
|
|
|
|
|
|
|
|
return {
|
|
|
|
ieeeAddr,
|
|
|
|
friendlyName: deviceSettings.friendly_name || '',
|
|
|
|
type,
|
|
|
|
nwkAddr,
|
|
|
|
manufId,
|
|
|
|
manufName,
|
|
|
|
powerSource,
|
|
|
|
modelId,
|
|
|
|
status,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-04-18 09:25:40 -07:00
|
|
|
handleZigbeeMessage(message) {
|
2018-10-02 12:15:12 -07:00
|
|
|
// Call extensions.
|
|
|
|
this.extensions.filter((e) => e.handleZigbeeMessage).forEach((e) => e.handleZigbeeMessage(message));
|
2018-05-21 02:49:02 -07:00
|
|
|
|
2018-09-10 09:06:29 -07:00
|
|
|
// Log the message.
|
2018-10-16 08:51:54 -07:00
|
|
|
let logMessage = `Received zigbee message of type '${message.type}' ` +
|
2018-09-10 09:06:29 -07:00
|
|
|
`with data '${JSON.stringify(message.data)}'`;
|
|
|
|
if (message.endpoints && message.endpoints[0].device) {
|
|
|
|
const device = message.endpoints[0].device;
|
|
|
|
logMessage += ` of device '${device.modelId}' (${device.ieeeAddr})`;
|
|
|
|
}
|
|
|
|
logger.debug(logMessage);
|
|
|
|
|
2018-06-25 11:18:39 -07:00
|
|
|
if (message.type == 'devInterview' && !settings.getDevice(message.data)) {
|
2018-06-02 15:50:30 -07:00
|
|
|
logger.info('Connecting with device...');
|
|
|
|
this.mqtt.log('pairing', 'connecting with device');
|
2018-06-25 11:18:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (message.type == 'devIncoming') {
|
2018-06-02 15:50:30 -07:00
|
|
|
logger.info('Device incoming...');
|
|
|
|
this.mqtt.log('pairing', 'device incoming');
|
2018-04-21 03:45:22 -07:00
|
|
|
}
|
|
|
|
|
2018-04-25 11:54:41 -07:00
|
|
|
// We dont handle messages without endpoints.
|
2018-04-18 09:25:40 -07:00
|
|
|
if (!message.endpoints) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const device = message.endpoints[0].device;
|
|
|
|
|
2018-04-23 12:44:06 -07:00
|
|
|
if (!device) {
|
|
|
|
logger.warn('Message without device!');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-18 09:25:40 -07:00
|
|
|
// Check if this is a new device.
|
2018-04-23 09:17:47 -07:00
|
|
|
if (!settings.getDevice(device.ieeeAddr)) {
|
2018-04-18 09:25:40 -07:00
|
|
|
logger.info(`New device with address ${device.ieeeAddr} connected!`);
|
2018-04-25 10:29:03 -07:00
|
|
|
settings.addDevice(device.ieeeAddr);
|
2018-05-30 09:09:24 -07:00
|
|
|
this.mqtt.log('device_connected', device.ieeeAddr);
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
|
2018-04-20 14:39:20 -07:00
|
|
|
// We can't handle devices without modelId.
|
|
|
|
if (!device.modelId) {
|
2018-04-18 09:25:40 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map Zigbee modelID to vendor modelID.
|
2018-04-18 10:09:59 -07:00
|
|
|
const modelID = message.endpoints[0].device.modelId;
|
2018-06-04 11:03:53 -07:00
|
|
|
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(modelID);
|
2018-04-18 09:25:40 -07:00
|
|
|
|
|
|
|
if (!mappedModel) {
|
2018-04-18 11:53:22 -07:00
|
|
|
logger.warn(`Device with modelID '${modelID}' is not supported.`);
|
2018-05-24 03:09:36 -07:00
|
|
|
logger.warn(`Please see: https://github.com/Koenkk/zigbee2mqtt/wiki/How-to-support-new-devices`);
|
2018-04-18 09:25:40 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-21 04:21:18 -07:00
|
|
|
// Configure device.
|
|
|
|
this.configureDevice(device);
|
|
|
|
|
2018-04-25 10:41:20 -07:00
|
|
|
// Home Assistant MQTT discovery
|
2018-04-25 11:54:41 -07:00
|
|
|
if (settings.get().homeassistant) {
|
2018-06-11 11:20:18 -07:00
|
|
|
homeassistant.discover(device.ieeeAddr, mappedModel.model, this.mqtt, false);
|
2018-04-20 10:53:40 -07:00
|
|
|
}
|
|
|
|
|
2018-04-20 14:39:20 -07:00
|
|
|
// After this point we cant handle message withoud cid anymore.
|
2018-06-08 11:20:35 -07:00
|
|
|
if (!message.data || (!message.data.cid && !message.data.cmdId)) {
|
2018-04-20 14:39:20 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-18 09:25:40 -07:00
|
|
|
// Find a conveter for this message.
|
2018-04-18 10:09:59 -07:00
|
|
|
const cid = message.data.cid;
|
2018-06-08 11:20:35 -07:00
|
|
|
const cmdId = message.data.cmdId;
|
|
|
|
const converters = mappedModel.fromZigbee.filter((c) => {
|
|
|
|
if (cid) {
|
|
|
|
return c.cid === cid && c.type === message.type;
|
|
|
|
} else if (cmdId) {
|
|
|
|
return c.cmd === cmdId;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
});
|
2018-04-18 09:25:40 -07:00
|
|
|
|
|
|
|
if (!converters.length) {
|
2018-06-08 11:20:35 -07:00
|
|
|
if (cid) {
|
|
|
|
logger.warn(
|
2018-07-14 12:22:50 -07:00
|
|
|
`No converter available for '${mappedModel.model}' with cid '${cid}', ` +
|
2018-07-18 13:30:51 -07:00
|
|
|
`type '${message.type}' and data '${JSON.stringify(message.data)}'`
|
2018-06-08 11:20:35 -07:00
|
|
|
);
|
|
|
|
} else if (cmdId) {
|
2018-07-14 12:22:50 -07:00
|
|
|
logger.warn(
|
|
|
|
`No converter available for '${mappedModel.model}' with cmd '${cmdId}' ` +
|
2018-07-18 13:30:51 -07:00
|
|
|
`and data '${JSON.stringify(message.data)}'`
|
2018-07-14 12:22:50 -07:00
|
|
|
);
|
2018-06-08 11:20:35 -07:00
|
|
|
}
|
|
|
|
|
2018-05-24 03:09:36 -07:00
|
|
|
logger.warn(`Please see: https://github.com/Koenkk/zigbee2mqtt/wiki/How-to-support-new-devices.`);
|
2018-04-18 09:25:40 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert this Zigbee message to a MQTT message.
|
|
|
|
// Get payload for the message.
|
|
|
|
// - If a payload is returned publish it to the MQTT broker
|
|
|
|
// - If NO payload is returned do nothing. This is for non-standard behaviour
|
|
|
|
// for e.g. click switches where we need to count number of clicks and detect long presses.
|
|
|
|
converters.forEach((converter) => {
|
2018-05-22 10:10:16 -07:00
|
|
|
const publish = (payload) => {
|
2018-06-04 10:23:23 -07:00
|
|
|
// Don't cache messages with following properties:
|
|
|
|
const dontCacheProperties = ['click', 'action', 'button', 'button_left', 'button_right'];
|
|
|
|
let cache = true;
|
|
|
|
dontCacheProperties.forEach((property) => {
|
|
|
|
if (payload.hasOwnProperty(property)) {
|
|
|
|
cache = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-07-29 13:09:20 -07:00
|
|
|
// Add device linkquality.
|
2018-09-11 12:38:01 -07:00
|
|
|
if (message.hasOwnProperty('linkquality')) {
|
2018-07-29 13:09:20 -07:00
|
|
|
payload.linkquality = message.linkquality;
|
|
|
|
}
|
|
|
|
|
2018-09-20 12:42:50 -07:00
|
|
|
this.mqttPublishDeviceState(device, payload, cache);
|
2018-05-22 10:10:16 -07:00
|
|
|
};
|
2018-05-17 08:20:46 -07:00
|
|
|
|
2018-05-24 04:09:58 -07:00
|
|
|
const payload = converter.convert(mappedModel, message, publish, settings.getDevice(device.ieeeAddr));
|
2018-04-18 09:25:40 -07:00
|
|
|
|
|
|
|
if (payload) {
|
2018-05-11 10:14:18 -07:00
|
|
|
publish(payload);
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
handleMQTTMessage(topic, message) {
|
2018-10-23 11:39:48 -07:00
|
|
|
logger.debug(`Received MQTT message on '${topic}' with data '${message}'`);
|
2018-08-28 12:55:00 -07:00
|
|
|
|
2018-10-23 11:39:48 -07:00
|
|
|
// Find extensions that can handle MQTT messages and get results
|
|
|
|
const results = this.extensions
|
|
|
|
.filter((e) => e.handleMQTTMessage)
|
|
|
|
.map((e) => e.handleMQTTMessage(topic, message));
|
2018-08-28 12:55:00 -07:00
|
|
|
|
2018-04-25 11:54:41 -07:00
|
|
|
if (topic.match(mqttConfigRegex)) {
|
|
|
|
this.handleMQTTMessageConfig(topic, message);
|
2018-06-13 10:51:40 -07:00
|
|
|
} else if (topic === 'hass/status') {
|
|
|
|
if (message.toString().toLowerCase() === 'online') {
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
this.sendAllCachedStates();
|
|
|
|
clearTimeout(timer);
|
2018-06-20 13:02:29 -07:00
|
|
|
}, 20000);
|
2018-06-13 10:51:40 -07:00
|
|
|
}
|
2018-10-23 11:39:48 -07:00
|
|
|
} else if (!results.includes(true)) {
|
|
|
|
logger.warn(`Cannot handle MQTT message on '${topic}' with data '${message}'`);
|
2018-04-24 10:03:09 -07:00
|
|
|
}
|
2018-04-25 11:54:41 -07:00
|
|
|
}
|
2018-04-24 10:03:09 -07:00
|
|
|
|
2018-04-25 11:54:41 -07:00
|
|
|
handleMQTTMessageConfig(topic, message) {
|
2018-08-31 03:34:59 -07:00
|
|
|
const option = topic.split('/').slice(-1)[0];
|
2018-04-25 11:54:41 -07:00
|
|
|
|
|
|
|
if (option === 'permit_join') {
|
2018-09-23 02:16:45 -07:00
|
|
|
this.zigbee.permitJoin(message.toString().toLowerCase() === 'true');
|
2018-06-11 11:31:46 -07:00
|
|
|
} else if (option === 'log_level') {
|
2018-06-11 13:15:45 -07:00
|
|
|
const level = message.toString().toLowerCase();
|
|
|
|
if (allowedLogLevels.includes(level)) {
|
2018-06-13 13:38:56 -07:00
|
|
|
logger.info(`Switching log level to '${level}'`);
|
2018-06-11 13:15:45 -07:00
|
|
|
logger.transports.console.level = level;
|
|
|
|
logger.transports.file.level = level;
|
2018-06-11 12:37:18 -07:00
|
|
|
} else {
|
2018-06-11 13:29:33 -07:00
|
|
|
logger.error(`Could not set log level to '${level}'. Allowed level: '${allowedLogLevels.join(',')}'`);
|
2018-06-11 11:31:46 -07:00
|
|
|
}
|
2018-05-30 08:52:46 -07:00
|
|
|
} else if (option === 'devices') {
|
|
|
|
const devices = this.zigbee.getAllClients().map((device) => {
|
2018-06-04 11:03:53 -07:00
|
|
|
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
|
2018-05-30 08:52:46 -07:00
|
|
|
const friendlyDevice = settings.getDevice(device.ieeeAddr);
|
|
|
|
|
|
|
|
return {
|
|
|
|
ieeeAddr: device.ieeeAddr,
|
|
|
|
type: device.type,
|
|
|
|
model: mappedModel ? mappedModel.model : device.modelId,
|
|
|
|
friendly_name: friendlyDevice ? friendlyDevice.friendly_name : device.ieeeAddr,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2018-05-30 09:09:24 -07:00
|
|
|
this.mqtt.log('devices', devices);
|
2018-06-06 12:19:50 -07:00
|
|
|
} else if (option === 'remove') {
|
2018-06-09 03:27:04 -07:00
|
|
|
message = message.toString();
|
2018-06-10 08:35:14 -07:00
|
|
|
const IDByFriendlyName = settings.getIDByFriendlyName(message);
|
|
|
|
const deviceID = IDByFriendlyName ? IDByFriendlyName : message;
|
2018-06-09 03:27:04 -07:00
|
|
|
const device = this.zigbee.getDevice(deviceID);
|
|
|
|
|
2018-06-10 08:35:14 -07:00
|
|
|
const cleanup = () => {
|
|
|
|
// Clear Home Assistant MQTT discovery message
|
|
|
|
if (settings.get().homeassistant && device) {
|
2018-06-09 03:27:04 -07:00
|
|
|
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
|
2018-06-10 08:35:14 -07:00
|
|
|
if (mappedModel) {
|
2018-06-09 03:27:04 -07:00
|
|
|
homeassistant.clear(deviceID, mappedModel.model, this.mqtt);
|
|
|
|
}
|
2018-06-10 08:35:14 -07:00
|
|
|
}
|
2018-06-09 03:27:04 -07:00
|
|
|
|
2018-06-10 08:35:14 -07:00
|
|
|
// Remove from configuration.yaml
|
|
|
|
settings.removeDevice(deviceID);
|
2018-06-09 03:27:04 -07:00
|
|
|
|
2018-08-05 09:38:33 -07:00
|
|
|
// Remove from state
|
|
|
|
this.state.remove(deviceID);
|
|
|
|
|
2018-06-10 08:35:14 -07:00
|
|
|
logger.info(`Successfully removed ${deviceID}`);
|
2018-06-10 08:37:16 -07:00
|
|
|
this.mqtt.log('device_removed', message);
|
2018-06-10 08:35:14 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// Remove from zigbee network.
|
|
|
|
if (device) {
|
|
|
|
this.zigbee.removeDevice(deviceID, (error) => {
|
|
|
|
if (!error) {
|
|
|
|
cleanup();
|
|
|
|
} else {
|
|
|
|
logger.error(`Failed to remove ${deviceID}`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
cleanup();
|
|
|
|
}
|
2018-07-24 09:25:16 -07:00
|
|
|
} else if (option === 'rename') {
|
|
|
|
const invalid = `Invalid rename message format expected {old: 'friendly_name', new: 'new_name} ` +
|
|
|
|
`got ${message.toString()}`;
|
|
|
|
|
|
|
|
let json = null;
|
|
|
|
try {
|
|
|
|
json = JSON.parse(message.toString());
|
|
|
|
} catch (e) {
|
|
|
|
logger.error(invalid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate message
|
|
|
|
if (!json.new || !json.old) {
|
|
|
|
logger.error(invalid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (settings.changeFriendlyName(json.old, json.new)) {
|
|
|
|
logger.info(`Successfully renamed - ${json.old} to ${json.new} `);
|
|
|
|
} else {
|
|
|
|
logger.error(`Failed to renamed - ${json.old} to ${json.new}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Homeassistant rediscover
|
|
|
|
if (settings.get().homeassistant) {
|
|
|
|
const ID = settings.getIDByFriendlyName(json.new);
|
|
|
|
const device = this.zigbee.getDevice(ID);
|
|
|
|
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
|
|
|
|
if (mappedModel) {
|
|
|
|
homeassistant.discover(device.ieeeAddr, mappedModel.model, this.mqtt, true);
|
|
|
|
}
|
|
|
|
}
|
2018-04-25 11:54:41 -07:00
|
|
|
} else {
|
|
|
|
logger.warn(`Cannot handle MQTT config option '${option}' with message '${message}'`);
|
2018-04-24 10:30:56 -07:00
|
|
|
}
|
2018-04-25 11:54:41 -07:00
|
|
|
}
|
|
|
|
|
2018-09-20 12:42:50 -07:00
|
|
|
mqttPublishDeviceState(device, payload, cache) {
|
|
|
|
const deviceID = device.ieeeAddr;
|
|
|
|
const appSettings = settings.get();
|
|
|
|
let messagePayload = {...payload};
|
|
|
|
|
2018-05-28 12:10:58 -07:00
|
|
|
if (cacheState) {
|
|
|
|
// Add cached state to payload
|
2018-08-05 09:38:33 -07:00
|
|
|
if (this.state.exists(deviceID)) {
|
2018-09-20 12:42:50 -07:00
|
|
|
messagePayload = objectAssignDeep.noMutate(this.state.get(deviceID), payload);
|
2018-05-28 12:10:58 -07:00
|
|
|
}
|
2018-04-21 00:13:14 -07:00
|
|
|
|
2018-05-28 12:10:58 -07:00
|
|
|
// Update state cache with new state.
|
|
|
|
if (cache) {
|
2018-10-16 08:34:46 -07:00
|
|
|
this.state.set(deviceID, messagePayload);
|
2018-05-28 12:10:58 -07:00
|
|
|
}
|
2018-05-11 10:14:18 -07:00
|
|
|
}
|
2018-04-21 00:13:14 -07:00
|
|
|
|
2018-04-25 11:54:41 -07:00
|
|
|
const deviceSettings = settings.getDevice(deviceID);
|
2018-10-02 11:35:14 -07:00
|
|
|
const friendlyName = deviceSettings ? deviceSettings.friendly_name : deviceID;
|
2018-05-15 09:42:26 -07:00
|
|
|
const options = {
|
2018-10-02 11:35:14 -07:00
|
|
|
retain: deviceSettings ? deviceSettings.retain : false,
|
|
|
|
qos: deviceSettings && deviceSettings.qos ? deviceSettings.qos : 0,
|
2018-05-15 09:42:26 -07:00
|
|
|
};
|
|
|
|
|
2018-09-20 12:42:50 -07:00
|
|
|
if (appSettings.mqtt.include_device_information) {
|
|
|
|
messagePayload.device = this.getDeviceInfoForMqtt(device);
|
|
|
|
}
|
|
|
|
|
2018-10-02 11:35:14 -07:00
|
|
|
this.mqtt.publish(friendlyName, JSON.stringify(messagePayload), options);
|
2018-04-21 00:13:14 -07:00
|
|
|
}
|
2018-06-15 08:48:10 -07:00
|
|
|
|
|
|
|
startupLogVersion(callback) {
|
|
|
|
const git = require('git-last-commit');
|
|
|
|
const packageJSON = require('../package.json');
|
|
|
|
const version = packageJSON.version;
|
|
|
|
|
|
|
|
git.getLastCommit((err, commit) => {
|
2018-06-15 15:10:08 -07:00
|
|
|
let commitHash = null;
|
|
|
|
|
2018-06-15 08:48:10 -07:00
|
|
|
if (err) {
|
2018-06-15 15:10:08 -07:00
|
|
|
try {
|
|
|
|
commitHash = require('../.hash.json').hash;
|
|
|
|
} catch (error) {
|
|
|
|
commitHash = 'unknown';
|
|
|
|
}
|
2018-06-15 08:48:10 -07:00
|
|
|
} else {
|
2018-06-15 15:10:08 -07:00
|
|
|
commitHash = commit.shortHash;
|
2018-06-15 08:48:10 -07:00
|
|
|
}
|
|
|
|
|
2018-06-15 15:10:08 -07:00
|
|
|
logger.info(`Starting zigbee2mqtt version ${version} (commit #${commitHash})`);
|
|
|
|
|
2018-06-15 08:48:10 -07:00
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
|
2018-05-17 08:20:46 -07:00
|
|
|
module.exports = Controller;
|