zigbee2mqtt/lib/controller.js

264 lines
9.3 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');
// Extensions
const ExtensionNetworkMap = require('./extension/networkMap');
const ExtensionSoftReset = require('./extension/softReset');
const ExtensionRouterPollXiaomi = require('./extension/routerPollXiaomi');
const ExtensionDevicePublish = require('./extension/devicePublish');
2018-11-16 12:23:11 -07:00
const ExtensionHomeAssistant = require('./extension/homeassistant');
const ExtensionDeviceConfigure = require('./extension/deviceConfigure');
const ExtensionDeviceReceive = require('./extension/deviceReceive');
const ExtensionMarkOnlineXiaomi = require('./extension/markOnlineXiaomi');
const ExtensionBridgeConfig = require('./extension/bridgeConfig');
const ExtensionGroups = require('./extension/groups');
const DeviceAvailability = require('./extension/deviceAvailability');
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.publishDeviceState = this.publishDeviceState.bind(this);
// Initialize extensions.
this.extensions = [
2018-11-16 12:23:11 -07:00
new ExtensionDeviceReceive(this.zigbee, this.mqtt, this.state, this.publishDeviceState),
new ExtensionDeviceConfigure(this.zigbee, this.mqtt, this.state, this.publishDeviceState),
new ExtensionDevicePublish(this.zigbee, this.mqtt, this.state, this.publishDeviceState),
new ExtensionNetworkMap(this.zigbee, this.mqtt, this.state, this.publishDeviceState),
new ExtensionRouterPollXiaomi(this.zigbee, this.mqtt, this.state, this.publishDeviceState),
new ExtensionMarkOnlineXiaomi(this.zigbee, this.mqtt, this.state, this.publishDeviceState),
new ExtensionBridgeConfig(this.zigbee, this.mqtt, this.state, this.publishDeviceState),
new ExtensionGroups(this.zigbee, this.mqtt, this.state, this.publishDeviceState),
];
2018-11-16 12:23:11 -07:00
if (settings.get().homeassistant) {
this.extensions.push(new ExtensionHomeAssistant(
this.zigbee, this.mqtt, this.state, this.publishDeviceState
));
}
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.publishDeviceState
));
2018-05-28 11:40:30 -07:00
}
if (settings.get().experimental.availablility_timeout) {
this.extensions.push(new DeviceAvailability(
this.zigbee, this.mqtt, this.state, this.publishDeviceState
));
}
}
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[0].device) {
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)}'` +
(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-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() {
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);
} 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
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-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)) {
this.publishDeviceState(device, this.state.get(device.ieeeAddr), false);
2018-07-24 09:25:16 -07:00
}
2018-11-16 12:23:11 -07:00
});
}
2018-11-16 12:23:11 -07:00
publishDeviceState(device, payload, cache) {
const deviceID = device.ieeeAddr;
const appSettings = settings.get();
let messagePayload = {...payload};
2018-11-16 12:23:11 -07:00
if (appSettings.advanced.cache_state) {
// Add cached state to payload
if (this.state.exists(deviceID)) {
messagePayload = objectAssignDeep.noMutate(this.state.get(deviceID), payload);
}
// Update state cache with new state.
if (cache) {
2018-10-16 08:34:46 -07:00
this.state.set(deviceID, messagePayload);
}
}
const deviceSettings = settings.getDevice(deviceID);
const friendlyName = deviceSettings ? deviceSettings.friendly_name : deviceID;
const options = {
retain: deviceSettings ? deviceSettings.retain : false,
qos: deviceSettings && deviceSettings.qos ? deviceSettings.qos : 0,
};
if (appSettings.mqtt.include_device_information) {
messagePayload.device = this.getDeviceInfoForMqtt(device);
}
this.mqtt.publish(friendlyName, JSON.stringify(messagePayload), options);
}
2018-06-15 08:48:10 -07:00
2018-11-16 12:23:11 -07:00
getDeviceInfoForMqtt(device) {
const {type, ieeeAddr, nwkAddr, manufId, manufName, powerSource, modelId, status} = device;
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;