zigbee2mqtt/lib/controller.js

310 lines
12 KiB
JavaScript
Raw Normal View History

2018-04-18 09:25:40 -07:00
const MQTT = require('./mqtt');
const Zigbee = require('./zigbee');
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-11-16 12:23:11 -07:00
const zigbeeShepherdConverters = require('zigbee-shepherd-converters');
const objectAssignDeep = require('object-assign-deep');
const utils = require('./util/utils');
2018-11-16 12:23:11 -07:00
// Extensions
const ExtensionNetworkMap = require('./extension/networkMap');
const ExtensionSoftReset = require('./extension/softReset');
2019-02-13 12:55:14 -07:00
const ExtensionXiaomi = require('./extension/xiaomi');
const ExtensionDevicePublish = require('./extension/devicePublish');
2018-11-16 12:23:11 -07:00
const ExtensionHomeAssistant = require('./extension/homeassistant');
const ExtensionDeviceConfigure = require('./extension/deviceConfigure');
const ExtensionDeviceGroupMembership = require('./extension/deviceGroupMembership');
2018-11-16 12:23:11 -07:00
const ExtensionDeviceReceive = require('./extension/deviceReceive');
const ExtensionBridgeConfig = require('./extension/bridgeConfig');
const ExtensionGroups = require('./extension/groups');
2019-02-13 12:55:14 -07:00
const ExtensionDeviceAvailability = require('./extension/deviceAvailability');
const ExtensionDeviceBind = require('./extension/deviceBind');
const ExtensionDeviceReport = require('./extension/deviceReport');
const ExtensionLivolo = require('./extension/livolo');
const ExtensionResponder = require('./extension/responder');
2018-05-17 08:20:46 -07:00
class Controller {
2018-04-18 09:25:40 -07:00
constructor() {
2018-11-16 12:23:11 -07:00
this.zigbee = new Zigbee();
2018-04-18 09:25:40 -07:00
this.mqtt = new MQTT();
this.state = new State();
2018-04-18 09:25:40 -07:00
2018-11-16 12:23:11 -07:00
// Bind methods
this.onMQTTConnected = this.onMQTTConnected.bind(this);
this.onZigbeeMessage = this.onZigbeeMessage.bind(this);
this.onMQTTMessage = this.onMQTTMessage.bind(this);
this.publishEntityState = this.publishEntityState.bind(this);
// Initialize extensions.
this.extensions = [
new ExtensionDeviceReceive(this.zigbee, this.mqtt, this.state, this.publishEntityState),
new ExtensionDeviceGroupMembership(this.zigbee, this.mqtt, this.publishEntityState),
new ExtensionDeviceConfigure(this.zigbee, this.mqtt, this.state, this.publishEntityState),
new ExtensionDevicePublish(this.zigbee, this.mqtt, this.state, this.publishEntityState),
new ExtensionNetworkMap(this.zigbee, this.mqtt, this.state, this.publishEntityState),
2019-02-13 12:55:14 -07:00
new ExtensionXiaomi(this.zigbee, this.mqtt, this.state, this.publishEntityState),
new ExtensionBridgeConfig(this.zigbee, this.mqtt, this.state, this.publishEntityState),
new ExtensionGroups(this.zigbee, this.mqtt, this.state, this.publishEntityState),
2019-02-13 12:55:14 -07:00
new ExtensionDeviceBind(this.zigbee, this.mqtt, this.state, this.publishEntityState),
new ExtensionResponder(this.zigbee, this.mqtt, this.state, this.publishEntityState),
];
2019-02-26 12:21:35 -07:00
if (settings.get().advanced.report) {
this.extensions.push(new ExtensionDeviceReport(
this.zigbee, this.mqtt, this.state, this.publishEntityState
));
}
2018-11-16 12:23:11 -07:00
if (settings.get().homeassistant) {
this.extensions.push(new ExtensionHomeAssistant(
this.zigbee, this.mqtt, this.state, this.publishEntityState
2018-11-16 12:23:11 -07:00
));
}
2018-11-16 12:23:11 -07:00
if (settings.get().advanced.soft_reset_timeout !== 0) {
this.extensions.push(new ExtensionSoftReset(
this.zigbee, this.mqtt, this.state, this.publishEntityState
2018-11-16 12:23:11 -07:00
));
2018-05-28 11:40:30 -07:00
}
if (settings.get().advanced.availability_timeout) {
2019-02-13 12:55:14 -07:00
this.extensions.push(new ExtensionDeviceAvailability(
this.zigbee, this.mqtt, this.state, this.publishEntityState
));
}
2019-02-04 10:39:45 -07:00
if (settings.get().experimental.livolo) {
// https://github.com/Koenkk/zigbee2mqtt/issues/592
2019-02-13 12:55:14 -07:00
this.extensions.push(new ExtensionLivolo(
2019-02-04 10:39:45 -07:00
this.zigbee, this.mqtt, this.state, this.publishEntityState
));
}
}
2018-11-16 12:23:11 -07:00
onMQTTConnected() {
// Resend all cached states.
this.sendAllCachedStates();
2018-11-16 12:23:11 -07:00
// Call extensions
this.extensions.filter((e) => e.onMQTTConnected).forEach((e) => e.onMQTTConnected());
}
2018-11-16 12:23:11 -07:00
onZigbeeStarted() {
// 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));
});
2018-05-21 02:49:02 -07:00
2018-11-16 12:23:11 -07:00
// 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-09-10 09:06:29 -07:00
}
2018-11-16 12:23:11 -07:00
this.zigbee.permitJoin(settings.get().permit_join);
2018-11-16 12:23:11 -07:00
// Connect to MQTT broker
this.mqtt.connect(this.onMQTTMessage, this.onMQTTConnected);
2018-04-21 03:45:22 -07:00
2018-11-16 12:23:11 -07:00
// Call extensions
this.extensions.filter((e) => e.onZigbeeStarted).forEach((e) => e.onZigbeeStarted());
}
2018-04-18 09:25:40 -07:00
2018-11-16 12:23:11 -07:00
onZigbeeMessage(message) {
// Variables
let device = null;
let mappedDevice = null;
2018-04-18 09:25:40 -07:00
2018-11-16 12:23:11 -07:00
// Check if message has a device
if (message.endpoints && message.endpoints.length && message.endpoints[0].device) {
2018-11-16 12:23:11 -07:00
device = message.endpoints[0].device;
}
2018-11-16 12:23:11 -07:00
// Retrieve modelId from message
if (device && device.modelId) {
mappedDevice = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
2018-04-18 09:25:40 -07:00
}
2018-11-16 12:23:11 -07:00
// Log
logger.debug(
`Received zigbee message of type '${message.type}' with data '${JSON.stringify(message.data)}'` +
2019-03-18 09:32:53 -07:00
(device ? ` of device '${device.modelId}' (${device.ieeeAddr})` : '') +
(message.groupid ? ` with groupID ${message.groupID}` : '') +
2019-03-27 09:31:25 -07:00
(message.endpoints && message.endpoints.length ? ` of endpoint ${message.endpoints[0].epId}` : '')
2018-11-16 12:23:11 -07:00
);
2018-04-18 09:25:40 -07:00
2018-11-16 12:23:11 -07:00
// Call extensions.
this.extensions
.filter((e) => e.onZigbeeMessage)
.forEach((e) => e.onZigbeeMessage(message, device, mappedDevice));
}
2018-04-18 09:25:40 -07:00
2018-11-16 12:23:11 -07:00
onMQTTMessage(topic, message) {
logger.debug(`Received MQTT message on '${topic}' with data '${message}'`);
2018-04-18 09:25:40 -07:00
2018-11-16 12:23:11 -07:00
// Call extensions
const results = this.extensions
.filter((e) => e.onMQTTMessage)
.map((e) => e.onMQTTMessage(topic, message));
2018-11-16 12:23:11 -07:00
if (!results.includes(true)) {
logger.warn(`Cannot handle MQTT message on '${topic}' with data '${message}'`);
}
2018-11-16 12:23:11 -07:00
}
2018-11-16 12:23:11 -07:00
start() {
2019-06-09 15:01:48 -07:00
logger.info(`Logging to directory: '${logger.directory}'`);
logger.cleanup();
this.state.start();
2018-11-28 11:34:37 -07:00
2018-11-16 12:23:11 -07:00
this.startupLogVersion(() => {
this.zigbee.start(this.onZigbeeMessage, (error) => {
if (error) {
logger.error('Failed to start', error);
logger.error('Exiting...');
process.exit(1);
2018-11-16 12:23:11 -07:00
} else {
this.onZigbeeStarted();
}
});
});
}
2018-04-20 14:39:20 -07:00
2018-11-16 12:23:11 -07:00
stop(callback) {
// Call extensions
this.extensions.filter((e) => e.stop).forEach((e) => e.stop());
2018-11-16 12:23:11 -07:00
// Wrap-up
this.state.stop();
2018-11-16 12:23:11 -07:00
this.mqtt.disconnect();
this.zigbee.stop(callback);
}
2018-04-18 09:25:40 -07:00
2018-11-16 12:23:11 -07:00
startupLogVersion(callback) {
utils.getZigbee2mqttVersion((info) => {
logger.info(`Starting zigbee2mqtt version ${info.version} (commit #${info.commitHash})`);
2018-11-16 12:23:11 -07:00
callback();
2018-04-18 09:25:40 -07:00
});
}
2018-11-16 12:23:11 -07:00
getDeviceStartupLogMessage(device) {
let friendlyName = 'unknown';
let type = 'unknown';
let friendlyDevice = {model: 'unkown', description: 'unknown'};
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
if (mappedModel) {
friendlyDevice = mappedModel;
2018-04-24 10:03:09 -07:00
}
2018-11-16 12:23:11 -07:00
if (settings.getDevice(device.ieeeAddr)) {
friendlyName = settings.getDevice(device.ieeeAddr).friendly_name;
}
2018-07-24 09:25:16 -07:00
2018-11-16 12:23:11 -07:00
if (device.type) {
type = utils.correctDeviceType(device);
2018-11-16 12:23:11 -07:00
}
2018-07-24 09:25:16 -07:00
2018-11-16 12:23:11 -07:00
return `${friendlyName} (${device.ieeeAddr}): ${friendlyDevice.model} - ` +
`${friendlyDevice.vendor} ${friendlyDevice.description} (${type})`;
}
2018-07-24 09:25:16 -07:00
2018-11-16 12:23:11 -07:00
sendAllCachedStates() {
this.zigbee.getAllClients().forEach((device) => {
if (this.state.exists(device.ieeeAddr)) {
this.publishEntityState(device.ieeeAddr, this.state.get(device.ieeeAddr));
2018-07-24 09:25:16 -07:00
}
2018-11-16 12:23:11 -07:00
});
}
publishEntityState(entityID, payload) {
const entity = settings.resolveEntity(entityID);
const appSettings = settings.get();
let messagePayload = {...payload};
const currentState = this.state.exists(entityID) ? this.state.get(entityID) : {};
const newState = objectAssignDeep.noMutate(currentState, payload);
// Update state cache with new state.
this.state.set(entityID, newState);
if (settings.get().advanced.cache_state) {
// Add cached state to payload
messagePayload = newState;
}
const entitySettings = entity.type === 'device' ? settings.getDevice(entityID) : settings.getGroup(entityID);
const options = {
retain: entitySettings ? entitySettings.retain : false,
qos: entitySettings && entitySettings.qos ? entitySettings.qos : 0,
};
if (entity.type === 'device' && appSettings.mqtt.include_device_information) {
messagePayload.device = this.getDeviceInfoForMqtt(entityID);
}
if (settings.get().experimental.output === 'json') {
this.mqtt.publish(entity.friendlyName, JSON.stringify(messagePayload), options);
} else if (settings.get().experimental.output === 'attribute') {
this.iteratePayloadForAttrOutput(entity.friendlyName+'/', messagePayload, options);
}
}
2018-06-15 08:48:10 -07:00
iteratePayloadForAttrOutput(topicRoot, payload, options) {
Object.keys(payload).forEach((key) => {
let subPayload = payload[key];
let message;
// Special cases
if (key === 'color' &&
subPayload.r !== undefined &&
subPayload.g !== undefined &&
subPayload.b !== undefined) {
subPayload = [subPayload.r, subPayload.g, subPayload.b];
}
// Check Array first, since it is also an Object
if (Array.isArray(subPayload)) {
message = subPayload.map((x) => `${x}`).join(',');
} else if (typeof subPayload === 'object') {
return this.iteratePayloadForAttrOutput(topicRoot+key+'-', subPayload, options);
} else {
message = typeof subPayload === 'string' ? subPayload : JSON.stringify(subPayload);
}
this.mqtt.publish(`${topicRoot}${key}`, message, options);
});
}
getDeviceInfoForMqtt(ieeeAddr) {
const device = this.zigbee.getDevice(ieeeAddr);
const {
type, nwkAddr, manufId, manufName, powerSource, modelId, hwVersion, swBuildId, status, dateCode,
} = device;
2018-11-16 12:23:11 -07:00
const deviceSettings = settings.getDevice(device.ieeeAddr);
2018-06-15 15:10:08 -07:00
2018-11-16 12:23:11 -07:00
return {
ieeeAddr,
2018-11-28 11:34:37 -07:00
friendlyName: deviceSettings ? (deviceSettings.friendly_name || '') : '',
2018-11-16 12:23:11 -07:00
type,
nwkAddr,
manufId,
manufName,
powerSource,
modelId,
hwVersion: hwVersion ? hwVersion : 'unknown',
swBuildId: swBuildId ? swBuildId : 'unknown',
dateCode: dateCode ? dateCode : 'unknown',
2018-11-16 12:23:11 -07:00
status,
};
2018-06-15 08:48:10 -07:00
}
2018-04-18 09:25:40 -07:00
}
2018-05-17 08:20:46 -07:00
module.exports = Controller;