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-11-16 12:23:11 -07:00
|
|
|
const zigbeeShepherdConverters = require('zigbee-shepherd-converters');
|
|
|
|
const objectAssignDeep = require('object-assign-deep');
|
2019-02-04 10:36:49 -07:00
|
|
|
const utils = require('./util/utils');
|
2018-11-16 12:23:11 -07:00
|
|
|
|
|
|
|
// Extensions
|
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');
|
2019-02-13 12:55:14 -07:00
|
|
|
const ExtensionXiaomi = require('./extension/xiaomi');
|
2018-11-05 13:55:30 -07:00
|
|
|
const ExtensionDevicePublish = require('./extension/devicePublish');
|
2018-11-16 12:23:11 -07:00
|
|
|
const ExtensionHomeAssistant = require('./extension/homeassistant');
|
|
|
|
const ExtensionDeviceConfigure = require('./extension/deviceConfigure');
|
2019-02-18 10:21:54 -07:00
|
|
|
const ExtensionDeviceGroupMembership = require('./extension/deviceGroupMembership');
|
2018-11-16 12:23:11 -07:00
|
|
|
const ExtensionDeviceReceive = require('./extension/deviceReceive');
|
|
|
|
const ExtensionBridgeConfig = require('./extension/bridgeConfig');
|
2018-12-21 16:07:53 -07:00
|
|
|
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');
|
2018-05-28 12:10:58 -07:00
|
|
|
|
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();
|
2018-08-05 09:38:33 -07:00
|
|
|
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);
|
2019-02-04 10:36:49 -07:00
|
|
|
this.publishEntityState = this.publishEntityState.bind(this);
|
2018-06-11 11:31:05 -07:00
|
|
|
|
2018-08-28 12:55:00 -07:00
|
|
|
// Initialize extensions.
|
|
|
|
this.extensions = [
|
2019-02-04 10:36:49 -07:00
|
|
|
new ExtensionDeviceReceive(this.zigbee, this.mqtt, this.state, this.publishEntityState),
|
2019-02-18 10:21:54 -07:00
|
|
|
new ExtensionDeviceGroupMembership(this.zigbee, this.mqtt, this.publishEntityState),
|
2019-02-04 10:36:49 -07:00
|
|
|
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),
|
2019-02-04 10:36:49 -07:00
|
|
|
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 ExtensionDeviceReport(this.zigbee, this.mqtt, this.state, this.publishEntityState),
|
2018-08-28 12:55:00 -07:00
|
|
|
];
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
if (settings.get().homeassistant) {
|
|
|
|
this.extensions.push(new ExtensionHomeAssistant(
|
2019-02-04 10:36:49 -07:00
|
|
|
this.zigbee, this.mqtt, this.state, this.publishEntityState
|
2018-11-16 12:23:11 -07:00
|
|
|
));
|
2018-05-16 10:29:47 -07:00
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
if (settings.get().advanced.soft_reset_timeout !== 0) {
|
|
|
|
this.extensions.push(new ExtensionSoftReset(
|
2019-02-04 10:36:49 -07:00
|
|
|
this.zigbee, this.mqtt, this.state, this.publishEntityState
|
2018-11-16 12:23:11 -07:00
|
|
|
));
|
2018-05-28 11:40:30 -07:00
|
|
|
}
|
2018-12-29 11:55:59 -07:00
|
|
|
|
2019-01-29 12:17:56 -07:00
|
|
|
if (settings.get().advanced.availability_timeout) {
|
2019-02-13 12:55:14 -07:00
|
|
|
this.extensions.push(new ExtensionDeviceAvailability(
|
2019-02-04 10:36:49 -07:00
|
|
|
this.zigbee, this.mqtt, this.state, this.publishEntityState
|
2018-12-29 11:55:59 -07:00
|
|
|
));
|
|
|
|
}
|
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-05-16 10:29:47 -07:00
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
onMQTTConnected() {
|
|
|
|
// Resend all cached states.
|
|
|
|
this.sendAllCachedStates();
|
2018-09-20 12:42:50 -07:00
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
// Call extensions
|
|
|
|
this.extensions.filter((e) => e.onMQTTConnected).forEach((e) => e.onMQTTConnected());
|
2018-09-20 12:42:50 -07:00
|
|
|
}
|
|
|
|
|
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-06-25 11:18:39 -07:00
|
|
|
|
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
|
2019-01-09 12:49:22 -07:00
|
|
|
if (message.endpoints && message.endpoints.length && message.endpoints[0].device) {
|
2018-11-16 12:23:11 -07:00
|
|
|
device = message.endpoints[0].device;
|
2018-04-23 12:44:06 -07:00
|
|
|
}
|
|
|
|
|
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)}'` +
|
|
|
|
(device ? ` of device '${device.modelId}' (${device.ieeeAddr})` : '')
|
|
|
|
);
|
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-05-21 04:21:18 -07:00
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
if (!results.includes(true)) {
|
|
|
|
logger.warn(`Cannot handle MQTT message on '${topic}' with data '${message}'`);
|
2018-04-20 10:53:40 -07:00
|
|
|
}
|
2018-11-16 12:23:11 -07:00
|
|
|
}
|
2018-04-20 10:53:40 -07:00
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
start() {
|
2018-11-28 11:34:37 -07:00
|
|
|
this.state.start();
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
this.startupLogVersion(() => {
|
|
|
|
this.zigbee.start(this.onZigbeeMessage, (error) => {
|
|
|
|
if (error) {
|
|
|
|
logger.error('Failed to start', error);
|
2019-02-18 11:24:26 -07:00
|
|
|
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-06-08 11:20:35 -07:00
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
// Wrap-up
|
2018-11-28 11:34:37 -07:00
|
|
|
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) {
|
|
|
|
const git = require('git-last-commit');
|
|
|
|
const packageJSON = require('../package.json');
|
|
|
|
const version = packageJSON.version;
|
2018-06-08 11:20:35 -07:00
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
git.getLastCommit((err, commit) => {
|
|
|
|
let commitHash = null;
|
2018-04-18 09:25:40 -07:00
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
if (err) {
|
|
|
|
try {
|
|
|
|
commitHash = require('../.hash.json').hash;
|
|
|
|
} catch (error) {
|
|
|
|
commitHash = 'unknown';
|
2018-07-29 13:09:20 -07:00
|
|
|
}
|
2018-11-16 12:23:11 -07:00
|
|
|
} else {
|
|
|
|
commitHash = commit.shortHash;
|
|
|
|
}
|
2018-07-29 13:09:20 -07:00
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
logger.info(`Starting zigbee2mqtt version ${version} (commit #${commitHash})`);
|
2018-04-18 09:25:40 -07:00
|
|
|
|
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 = device.type;
|
|
|
|
}
|
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)) {
|
2019-02-04 10:36:49 -07:00
|
|
|
this.publishEntityState(device.ieeeAddr, this.state.get(device.ieeeAddr), false);
|
2018-07-24 09:25:16 -07:00
|
|
|
}
|
2018-11-16 12:23:11 -07:00
|
|
|
});
|
2018-04-25 11:54:41 -07:00
|
|
|
}
|
|
|
|
|
2019-02-04 10:36:49 -07:00
|
|
|
publishEntityState(entityID, payload, cache) {
|
|
|
|
const entity = utils.resolveEntity(entityID);
|
2018-09-20 12:42:50 -07:00
|
|
|
const appSettings = settings.get();
|
|
|
|
let messagePayload = {...payload};
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
if (appSettings.advanced.cache_state) {
|
2018-05-28 12:10:58 -07:00
|
|
|
// Add cached state to payload
|
2019-02-04 10:36:49 -07:00
|
|
|
if (this.state.exists(entityID)) {
|
|
|
|
messagePayload = objectAssignDeep.noMutate(this.state.get(entityID), 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) {
|
2019-02-04 10:36:49 -07:00
|
|
|
this.state.set(entityID, 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
|
|
|
|
2019-02-04 10:36:49 -07:00
|
|
|
const entitySettings = entity.type === 'device' ? settings.getDevice(entityID) : settings.getGroup(entityID);
|
2018-05-15 09:42:26 -07:00
|
|
|
const options = {
|
2019-02-04 10:36:49 -07:00
|
|
|
retain: entitySettings ? entitySettings.retain : false,
|
|
|
|
qos: entitySettings && entitySettings.qos ? entitySettings.qos : 0,
|
2018-05-15 09:42:26 -07:00
|
|
|
};
|
|
|
|
|
2019-02-04 10:36:49 -07:00
|
|
|
if (entity.type === 'device' && appSettings.mqtt.include_device_information) {
|
|
|
|
messagePayload.device = this.getDeviceInfoForMqtt(entityID);
|
2018-09-20 12:42:50 -07:00
|
|
|
}
|
|
|
|
|
2019-02-14 10:13:51 -07:00
|
|
|
this.mqtt.publish(entity.friendlyName, JSON.stringify(messagePayload), options);
|
2018-04-21 00:13:14 -07:00
|
|
|
}
|
2018-06-15 08:48:10 -07:00
|
|
|
|
2019-02-04 10:36:49 -07:00
|
|
|
getDeviceInfoForMqtt(ieeeAddr) {
|
|
|
|
const device = this.zigbee.getDevice(ieeeAddr);
|
|
|
|
const {type, nwkAddr, manufId, manufName, powerSource, modelId, status} = 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,
|
|
|
|
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;
|