From d06522d488612308a3c08bb746663d3ec1d71283 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Wed, 18 Apr 2018 18:25:40 +0200 Subject: [PATCH] Refactor and rename. #8 --- README.md | 8 +- docker/arm32v7/build.sh | 4 +- index.js | 273 +------------------ lib/controller.js | 141 ++++++++++ commands.js => lib/converters/mqtt2zigbee.js | 4 +- parsers.js => lib/converters/zigbee2mqtt.js | 22 +- devices.js => lib/devices.js | 0 lib/mqtt.js | 76 ++++++ lib/util/logger.js | 17 ++ lib/util/settings.js | 23 ++ lib/zigbee.js | 118 ++++++++ npm-shrinkwrap.json | 4 +- package.json | 13 +- doc.js => support/docgen.js | 27 +- 14 files changed, 430 insertions(+), 300 deletions(-) create mode 100644 lib/controller.js rename commands.js => lib/converters/mqtt2zigbee.js (92%) rename parsers.js => lib/converters/zigbee2mqtt.js (84%) rename devices.js => lib/devices.js (100%) create mode 100644 lib/mqtt.js create mode 100644 lib/util/logger.js create mode 100644 lib/util/settings.js create mode 100644 lib/zigbee.js rename doc.js => support/docgen.js (76%) diff --git a/README.md b/README.md index 9c1c9f3b..f19d0b66 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# xiaomi-zb2mqtt +# zigbee2mqtt ![Some Xiaomi sensors](xiaomi.png) -Allows you to use your Xiaomi Zigbee sensors and switches **without** Xiaomi's gateway. +Allows you to use your Zigbee devices **without** the vendors (Xiaomi/TRADFRI/Hue) bridge/gateway. -It bridges the events sent from the sensors and switches to MQTT. You can integrate the cheap and nice Zigbee sensors and switches with whatever smart home infrastructure you are using. +It bridges the events sent from the sensors and switches to MQTT. In this way you can integrate your Zigbee devices with whatever smart home infrastructure you are using. -The [wiki](https://github.com/Koenkk/xiaomi-zb2mqtt/wiki) provides you all the information needed to get up and running! +The [wiki](https://github.com/Koenkk/zigbee2mqtt/wiki) provides you all the information needed to get up and running! ### Contributors * [AndrewLinden](https://github.com/AndrewLinden) diff --git a/docker/arm32v7/build.sh b/docker/arm32v7/build.sh index b4afa449..489bbb72 100755 --- a/docker/arm32v7/build.sh +++ b/docker/arm32v7/build.sh @@ -1,4 +1,4 @@ #!/bin/bash -docker build -t koenkk/xiaomi-zb2mqtt:arm32v7 -f Dockerfile ../../ -docker push koenkk/xiaomi-zb2mqtt:arm32v7 +docker build -t koenkk/zigbee2mqtt:arm32v7 -f Dockerfile ../../ +docker push koenkk/zigbee2mqtt:arm32v7 diff --git a/index.js b/index.js index 8f8329ff..6accf7ba 100644 --- a/index.js +++ b/index.js @@ -1,273 +1,10 @@ -const util = require("util"); -const ZShepherd = require('zigbee-shepherd'); -const mqtt = require('mqtt') -const fs = require('fs'); -const parsers = require('./parsers'); -const commands = require('./commands'); -const deviceMapping = require('./devices'); -const config = require('yaml-config'); -const configFile = `${__dirname}/data/configuration.yaml`; -const winston = require('winston'); -let settings = config.readConfig(configFile, 'user'); -const stateCache = {}; +const Controller = require('./lib/controller'); -const logger = new (winston.Logger)({ - transports: [ - new (winston.transports.Console)({ - timestamp: () => new Date().toLocaleString(), - formatter: function(options) { - return options.timestamp() + ' ' + - winston.config.colorize(options.level, options.level.toUpperCase()) + ' ' + - (options.message ? options.message : '') + - (options.meta && Object.keys(options.meta).length ? '\n\t'+ JSON.stringify(options.meta) : '' ); - } - }) - ] -}); +const controller = new Controller(); +controller.start(); -// Create empty device array if not set yet. -if (!settings.devices) { - settings.devices = {}; - writeConfig(); -} - -// Setup client -logger.info(`Connecting to MQTT server at ${settings.mqtt.server}`) - -const options = {}; -if (settings.mqtt.user && settings.mqtt.password) { - options.username = settings.mqtt.user; - options.password = settings.mqtt.password; -} - -const client = mqtt.connect(settings.mqtt.server, options) -const shepherd = new ZShepherd( - settings.serial.port, - { - net: {panId: 0x1a62}, - dbPath: `${__dirname}/data/database.db` - } -); - -// Register callbacks -client.on('connect', handleConnect); -client.on('message', handleMqttMessage); -shepherd.on('ready', handleReady); -shepherd.on('ind', handleMessage); process.on('SIGINT', handleQuit); -// Check every interval if connected to MQTT server. -setInterval(() => { - if (client.reconnecting) { - logger.error('Not connected to MQTT server!'); - } -}, 10 * 1000); // seconds * 1000. - -// Start server -logger.info(`Starting zigbee-shepherd with device ${settings.serial.port}`) -shepherd.start((err) => { - if (err) { - logger.error('Error while starting zigbee-shepherd'); - logger.error(err); - } else { - logger.info('zigbee-shepherd started'); - } -}); - -function handleReady() { - logger.info('zigbee-shepherd ready'); - - const devices = shepherd.list().filter((device) => device.type !== 'Coordinator'); - - logger.info(`Currently ${devices.length} devices are joined:`); - devices.forEach((device) => logger.info(getDeviceLogMessage(device))); - - // Set all Xiaomi devices to be online, so shepherd won't try - // to query info from devices (which would fail because they go tosleep). - devices.forEach((device) => { - shepherd.find(device.ieeeAddr, 1).getDevice().update({ - status: 'online', - joinTime: Math.floor(Date.now()/1000) - }); - }); - - // Allow or disallow new devices to join the network. - if (settings.allowJoin) { - logger.warn('allowJoin set to true in configuration.yaml.') - logger.warn('Allowing new devices to join.'); - logger.warn('Remove this parameter once you joined all devices.'); - } - - shepherd.permitJoin(settings.allowJoin ? 255 : 0, (err) => { - if (err) { - logger.info(err); - } - }); -} - -function handleConnect() { - mqttPublish(`${settings.mqtt.base_topic}/bridge/state`, 'online', true); - client.subscribe(`${settings.mqtt.base_topic}/+/set`) -} - -function handleMessage(msg) { - if (!msg.endpoints) { - return; - } - - const device = msg.endpoints[0].device; - - // New device! - if (!settings.devices[device.ieeeAddr]) { - logger.info(`New device with address ${device.ieeeAddr} connected!`); - - settings.devices[device.ieeeAddr] = { - friendly_name: device.ieeeAddr, - retain: false, - }; - - writeConfig(); - } - - // We can't handle devices without modelId. - if (!device.modelId) { - return; - } - - // Map modelID to Xiaomi model. - const modelID = msg.endpoints[0].device.modelId; - const mappedModel = deviceMapping[modelID]; - - if (!mappedModel) { - logger.error(`Device with modelID '${modelID}' is not supported.`); - logger.error('Please create an issue on https://github.com/Koenkk/xiaomi-zb2mqtt/issues to add support for your device'); - return; - } - - // Find a parser for this modelID and cid. - const cid = msg.data.cid; - const _parsers = parsers.filter((p) => p.devices.includes(mappedModel.model) && p.cid === cid && p.type === msg.type); - - if (!_parsers.length) { - logger.error(`No parser available for '${mappedModel.model}' with cid '${cid}' and type '${msg.type}'`); - logger.error('Please create an issue on https://github.com/Koenkk/xiaomi-zb2mqtt/issues with this message.'); - return; - } - - // Parse generic information from message. - const friendlyName = settings.devices[device.ieeeAddr].friendly_name; - const retain = settings.devices[device.ieeeAddr].retain; - const topic = `${settings.mqtt.base_topic}/${friendlyName}`; - const publish = (payload) => { - if (stateCache[device.ieeeAddr]) { - payload = {...stateCache[device.ieeeAddr], ...payload}; - } - - mqttPublish(topic, JSON.stringify(payload), retain); - } - - // 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. - _parsers.forEach((parser) => { - const payload = parser.parse(msg, publish); - - if (payload) { - stateCache[device.ieeeAddr] = {...stateCache[device.ieeeAddr], ...payload}; - - if (!parser.disablePublish) { - publish(payload); - } - } - }); -} - function handleQuit() { - mqttPublish(`${settings.mqtt.base_topic}/bridge/state`, 'offline', true); - - shepherd.stop((err) => { - if (err) { - logger.error('Error while stopping zigbee-shepherd'); - } else { - logger.error('zigbee-shepherd stopped') - } - - process.exit(); - }); -} - -function mqttPublish(topic, payload, retain) { - if (client.reconnecting) { - logger.error(`Not connected to MQTT server!`); - logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`); - return; - } - - logger.info(`MQTT publish, topic: '${topic}', payload: '${payload}'`); - client.publish(topic, payload, {retain: retain}); -} - -function writeConfig() { - config.updateConfig(settings, configFile, 'user'); - settings = config.readConfig(configFile, 'user'); -} - -function getDeviceLogMessage(device) { - let friendlyName = 'unknown'; - let friendlyDevice = {model: 'unkown', description: 'unknown'}; - - if (deviceMapping[device.modelId]) { - friendlyDevice = deviceMapping[device.modelId]; - } - - if (settings.devices[device.ieeeAddr]) { - friendlyName = settings.devices[device.ieeeAddr].friendly_name - } - - return `${friendlyName} (${device.ieeeAddr}): ${friendlyDevice.model} - ${friendlyDevice.description}`; -} - -function handleMqttMessage(topic, message) { - const friendlyName = topic.split('/')[1]; - - // Find device id of this friendlyName - const deviceID = Object.keys(settings.devices).find((id) => settings.devices[id].friendly_name === friendlyName); - if (!deviceID) { - logger.error(`Cannot handle '${topic}' because ID of '${friendlyName}' cannot be found`); - } - - // Find device in zigbee-shepherd - const device = shepherd.find(deviceID, 1); - if (!device) { - logger.error(`Cannot handle '${topic}' because '${deviceID}' is not known by zigbee-shepherd`); - } - - logger.info(`Executing '${topic}' '${message.toString()}' `) - - const json = JSON.parse(message); - - // Iterate over all keys in the json. - Object.keys(json).forEach((key) => { - // Find parser for this key - const parser = commands[key]; - if (!parser) { - logger.error(`No parser available for '${key}' (${json[key]})`); - return; - } - - const parsed = parser(json[key]); - const callback = (err, rsp) => { - // Devices do not report when they go off. - // Ensure state (on/off) is always in sync. - if (!err && key === 'state') { - mqttPublish( - `${settings.mqtt.base_topic}/${friendlyName}`, - JSON.stringify({state: json[key]}), - true - ); - } - }; - device.functional(parsed.cId, parsed.cmd, parsed.zclData, callback); - }); -} + controller.stop(() => process.exit()); +} \ No newline at end of file diff --git a/lib/controller.js b/lib/controller.js new file mode 100644 index 00000000..b16f3cc0 --- /dev/null +++ b/lib/controller.js @@ -0,0 +1,141 @@ +const MQTT = require('./mqtt'); +const Zigbee = require('./zigbee'); +const logger = require('./util/logger'); +const deviceMapping = require('./devices'); +const zigbee2mqtt = require('./converters/zigbee2mqtt'); +const mqtt2zigbee = require('./converters/mqtt2zigbee'); + +class Controller { + + constructor() { + this.zigbee = new Zigbee(); + this.mqtt = new MQTT(); + this.stateCache = {}; + + this.handleZigbeeMessage = this.handleZigbeeMessage.bind(this); + this.handleMQTTMessage = this.handleMQTTMessage.bind(this); + } + + start() { + this.zigbee.start(this.handleZigbeeMessage, (error) => { + if (error) { + logger.error('Failed to start'); + } else { + this.mqtt.connect(this.handleMQTTMessage); + } + }); + } + + stop(callback) { + this.mqtt.disconnect(); + this.zigbee.stop(callback); + } + + handleZigbeeMessage(message) { + if (!message.endpoints) { + // We dont handle messages without endpoints. + return; + } + + const device = message.endpoints[0].device; + + // Check if this is a new device. + if (!settings.get().devices[device.ieeeAddr]) { + logger.info(`New device with address ${device.ieeeAddr} connected!`); + + settings.get().devices[device.ieeeAddr] = { + friendly_name: device.ieeeAddr, + retain: false, + }; + + settings.write(); + } + + // We can't handle devices without modelId. + if (!device.modelId) { + return; + } + + // Map Zigbee modelID to vendor modelID. + const modelID = msg.endpoints[0].device.modelId; + const mappedModel = deviceMapping[modelID]; + + if (!mappedModel) { + logger.error(`Device with modelID '${modelID}' is not supported.`); + logger.error('Please create an issue on https://github.com/Koenkk/zigbee2mqtt/issues to add support for your device'); + return; + } + + // Find a conveter for this message. + const cid = msg.data.cid; + const converters = zigbee2mqtt.filter((c) => c.devices.includes(mappedModel.model) && c.cid === cid && c.type === msg.type); + + if (!converters.length) { + logger.error(`No converter available for '${mappedModel.model}' with cid '${cid}' and type '${msg.type}'`); + logger.error('Please create an issue on https://github.com/Koenkk/zigbee2mqtt/issues with this message.'); + return; + } + + // Convert this Zigbee message to a MQTT message. + const friendlyName = settings.get().devices[device.ieeeAddr].friendly_name; + const retain = settings.get().devices[device.ieeeAddr].retain; + const topic = `${settings.get().mqtt.base_topic}/${friendlyName}`; + + const publish = (payload) => { + if (this.stateCache[device.ieeeAddr]) { + payload = {...this.stateCache[device.ieeeAddr], ...payload}; + } + + this.mqtt.publish(topic, JSON.stringify(payload), retain); + } + + // 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) => { + const payload = converter.convert(msg, publish); + + if (payload) { + this.stateCache[device.ieeeAddr] = {...this.stateCache[device.ieeeAddr], ...payload}; + + if (!converter.disablePublish) { + publish(payload); + } + } + }); + } + + handleMQTTMessage(topic, message) { + const friendlyName = topic.split('/')[1]; + + // Map friendlyName to deviceID. + const deviceID = Object.keys(settings.get().devices).find((id) => settings.get().devices[id].friendly_name === friendlyName); + if (!deviceID) { + logger.error(`Cannot handle '${topic}' because deviceID of '${friendlyName}' cannot be found`); + } + + // Convert the MQTT message to a Zigbee message. + const json = JSON.parse(message); + Object.keys(json).forEach((key) => { + // Find converter for this key. + const converter = mqtt2zigbee[key]; + if (!converter) { + logger.error(`No converter available for '${key}' (${json[key]})`); + return; + } + + const message = converter(json[key]); + const callback = (error, response) => { + // Devices do not report when they go off, this ensures state (on/off) is always in sync. + if (!error && key === 'state') { + this.mqtt.publish(friendlyName, JSON.stringify({state: json[key]}), true); + } + }; + + this.zigbee.publish(deviceID, message.cId, message.cmd, message.zclData, callback); + }); + } +} + +module.exports = Controller; \ No newline at end of file diff --git a/commands.js b/lib/converters/mqtt2zigbee.js similarity index 92% rename from commands.js rename to lib/converters/mqtt2zigbee.js index 8cb2f5e1..9c75a981 100644 --- a/commands.js +++ b/lib/converters/mqtt2zigbee.js @@ -1,4 +1,4 @@ -const commands = { +const converters = { "state": (value) => { return { cId: 'genOnOff', @@ -28,4 +28,4 @@ const commands = { }, } -module.exports = commands; \ No newline at end of file +module.exports = converters; \ No newline at end of file diff --git a/parsers.js b/lib/converters/zigbee2mqtt.js similarity index 84% rename from parsers.js rename to lib/converters/zigbee2mqtt.js index 879e503d..b89b64f5 100644 --- a/parsers.js +++ b/lib/converters/zigbee2mqtt.js @@ -29,7 +29,7 @@ const parsers = [ cid: 'genBasic', type: 'attReport', disablePublish: true, - parse: (msg, publish) => { + convert: (msg, publish) => { let voltage = null; if (msg.data.data['65281']) { @@ -47,7 +47,7 @@ const parsers = [ devices: ['WXKG01LM'], cid: 'genOnOff', type: 'attReport', - parse: (msg, publish) => { + convert: (msg, publish) => { const deviceID = msg.endpoints[0].device.ieeeAddr; const state = msg.data.data['onOff']; @@ -74,19 +74,19 @@ const parsers = [ devices: ['WSDCGQ01LM'], cid: 'msTemperatureMeasurement', type: 'attReport', - parse: (msg) => {return {temperature: parseFloat(msg.data.data['measuredValue']) / 100.0}} + convert: (msg) => {return {temperature: parseFloat(msg.data.data['measuredValue']) / 100.0}} }, { devices: ['WSDCGQ01LM'], cid: 'msRelativeHumidity', type: 'attReport', - parse: (msg) => {return {humidity: parseFloat(msg.data.data['measuredValue']) / 100.0}} + convert: (msg) => {return {humidity: parseFloat(msg.data.data['measuredValue']) / 100.0}} }, { devices: ['RTCGQ01LM'], cid: 'msOccupancySensing', type: 'attReport', - parse: (msg, publish) => { + convert: (msg, publish) => { // The occupancy sensor only sends a message when motion detected. // Therefore we need to publish the no_motion detected by ourselves. // no_motion is triggered after 3 minutes of no motion. @@ -102,7 +102,7 @@ const parsers = [ store[deviceID] = setTimeout(() => { publish({occupancy: 'no_motion'}) store[deviceID] = null; - }, noMotionTimeout * 60 * 1000); + }, noMotionTimeout * 60 * 1000); return {occupancy: 'motion'}; } }, @@ -110,19 +110,19 @@ const parsers = [ devices: ['MCCGQ01LM'], cid: 'genOnOff', type: 'attReport', - parse: (msg) => {return {state: msg.data.data['onOff'] ? 'open' : 'closed'}} + convert: (msg) => {return {state: msg.data.data['onOff'] ? 'open' : 'closed'}} }, { devices: ['LED1545G12'], cid: 'genLevelCtrl', type: 'devChange', - parse: (msg) => {return {brightness: msg.data.data['currentLevel']}}, + convert: (msg) => {return {brightness: msg.data.data['currentLevel']}}, }, { devices: ['LED1545G12'], cid: 'lightingColorCtrl', type: 'devChange', - parse: (msg) => {return {color_temp: msg.data.data['colorTemperature']}}, + convert: (msg) => {return {color_temp: msg.data.data['colorTemperature']}}, }, // Ignore the following messages: @@ -130,13 +130,13 @@ const parsers = [ devices: ['LED1545G12'], cid: 'genOnOff', type: 'devChange', - parse: (msg) => null + convert: (msg) => null }, { devices: ['WXKG01LM'], cid: 'genOnOff', type: 'devChange', - parse: (msg) => null + convert: (msg) => null }, ]; diff --git a/devices.js b/lib/devices.js similarity index 100% rename from devices.js rename to lib/devices.js diff --git a/lib/mqtt.js b/lib/mqtt.js new file mode 100644 index 00000000..fbe16494 --- /dev/null +++ b/lib/mqtt.js @@ -0,0 +1,76 @@ +const mqtt = require('mqtt'); +const logger = require('./util/logger'); +const settings = require('./util/settings'); + + +class MQTT { + + constructor() { + this.handleConnect = this.handleConnect.bind(this); + this.handleMessage = this.handleMessage.bind(this); + } + + connect(onMessage) { + const mqttSettings = settings.get().mqtt; + logger.info(`Connecting to MQTT server at ${mqttSettings.server}`); + + const options = {}; + if (mqttSettings.user && mqttSettings.password) { + options.username = mqttSettings.user; + options.password = mqttSettings.password; + } + + this.client = mqtt.connect(mqttSettings.server, options); + + // Register callbacks. + this.client.on('connect', this.handleConnect); + this.client.on('message', this.handleMessage); + + // Set timer at interval to check if connected to MQTT server. + const interval = 10 * 1000; // seconds * 1000. + this.connectionTimer = setInterval(() => { + if (this.client.reconnecting) { + logger.error('Not connected to MQTT server!'); + } + }, interval); + + this.onMessage = onMessage; + } + + disconnect() { + clearTimeout(this.connectionTimer); + this.connectionTimer = null; + + this.publish('bridge/state', 'offline', true); + + logger.info('Disconnecting from MQTT server'); + this.client.end(); + } + + handleConnect() { + logger.info('Connected to MQTT server'); + this.publish('bridge/state', 'online', true); + this.client.subscribe(`${settings.get().mqtt.base_topic}/+/set`); + } + + handleMessage(topic, message) { + if (this.onMessage) { + this.onMessage(topic, message); + } + } + + publish(topic, payload, retain) { + topic = `${settings.get().mqtt.base_topic}/${topic}`; + + if (!this.client || this.client.reconnecting) { + logger.error(`Not connected to MQTT server!`); + logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`); + return; + } + + logger.info(`MQTT publish, topic: '${topic}', payload: '${payload}'`); + this.client.publish(topic, payload, {retain: retain}); + } +} + +module.exports = MQTT; diff --git a/lib/util/logger.js b/lib/util/logger.js new file mode 100644 index 00000000..96ffd14e --- /dev/null +++ b/lib/util/logger.js @@ -0,0 +1,17 @@ +const winston = require('winston'); + +const logger = new (winston.Logger)({ + transports: [ + new (winston.transports.Console)({ + timestamp: () => new Date().toLocaleString(), + formatter: function(options) { + return options.timestamp() + ' ' + + winston.config.colorize(options.level, options.level.toUpperCase()) + ' ' + + (options.message ? options.message : '') + + (options.meta && Object.keys(options.meta).length ? '\n\t'+ JSON.stringify(options.meta) : '' ); + } + }) + ] +}); + +module.exports = logger; diff --git a/lib/util/settings.js b/lib/util/settings.js new file mode 100644 index 00000000..b7c6b652 --- /dev/null +++ b/lib/util/settings.js @@ -0,0 +1,23 @@ +const yamlConfig = require('yaml-config'); +const file = `${__dirname}/../../data/configuration.yaml`; +let settings = read(); + +// Create empty device array if not set yet. +if (!settings.devices) { + settings.devices = {}; + write(); +} + +function write() { + yamlConfig.updateConfig(settings, file, 'user'); + settings = read(); +} + +function read() { + return yamlConfig.readConfig(file, 'user'); +} + + +module.exports = { + get: () => settings, +} diff --git a/lib/zigbee.js b/lib/zigbee.js new file mode 100644 index 00000000..4a9a46a0 --- /dev/null +++ b/lib/zigbee.js @@ -0,0 +1,118 @@ +const ZShepherd = require('zigbee-shepherd'); +const logger = require('./util/logger'); +const settings = require('./util/settings'); +const deviceMapping = require('./devices'); + +const shepherdSettings = { + net: {panId: 0x1a62}, + dbPath: `${__dirname}/../data/database.db` +}; + +class Zigbee { + + constructor() { + this.handleReady = this.handleReady.bind(this); + this.handleMessage = this.handleMessage.bind(this); + } + + start(onMessage, callback) { + logger.info(`Starting zigbee-shepherd`); + + this.shepherd = new ZShepherd(settings.get().serial.port, shepherdSettings); + + this.shepherd.start((error) => { + if (error) { + logger.error('Error while starting zigbee-shepherd!'); + } else { + logger.info('zigbee-shepherd started'); + } + + callback(error); + }); + + // Register callbacks. + this.shepherd.on('ready', this.handleReady); + this.shepherd.on('ind', this.handleMessage); + + this.onMessage = onMessage; + } + + stop(callback) { + this.shepherd.stop((error) => { + if (error) { + logger.error('Error while stopping zigbee-shepherd'); + } else { + logger.error('zigbee-shepherd stopped') + } + + callback(error); + }); + } + + handleReady() { + logger.info('zigbee-shepherd ready'); + + const devices = this.shepherd.list().filter((device) => device.type !== 'Coordinator'); + + logger.info(`Currently ${devices.length} devices are joined:`); + devices.forEach((device) => logger.info(getDeviceLogMessage(device))); + + // Set all Xiaomi devices (manufId === 4151) to be online, so shepherd won't try + // to query info from devices (which would fail because they go tosleep). + devices.forEach((device) => { + if (device.manufId === 4151) { + this.shepherd.find(device.ieeeAddr, 1).getDevice().update({ + status: 'online', + joinTime: Math.floor(Date.now()/1000) + }); + } + }); + + // Allow or disallow new devices to join the network. + if (settings.get().allowJoin) { + logger.warn('allowJoin set to true in configuration.yaml.') + logger.warn('Allowing new devices to join.'); + logger.warn('Remove this parameter once you joined all devices.'); + } + + this.shepherd.permitJoin(settings.get().allowJoin ? 255 : 0, (error) => { + if (error) { + logger.info(error); + } + }); + } + + handleMessage(message) { + if (this.onMessage) { + this.onMessage(message); + } + } + + getDeviceLogMessage(device) { + let friendlyName = 'unknown'; + let friendlyDevice = {model: 'unkown', description: 'unknown'}; + + if (deviceMapping[device.modelId]) { + friendlyDevice = deviceMapping[device.modelId]; + } + + if (settings.get().devices[device.ieeeAddr]) { + friendlyName = settings.devices[device.ieeeAddr].friendly_name + } + + return `${friendlyName} (${device.ieeeAddr}): ${friendlyDevice.model} - ${friendlyDevice.description}`; + } + + publish(deviceID, cId, cmd, zclData, callback) { + // Find device in zigbee-shepherd + const device = this.shepherd.find(deviceID, 1); + if (!device) { + logger.error(`Zigbee cannot publish message to device because '${deviceID}' is not known by zigbee-shepherd`); + } + + logger.info(`Zigbee publish to '${deviceID}', ${cId} - ${cmd} - ${zclData}`); + device.functional(cId, cmd, zclData, callback); + } +} + +module.exports = Zigbee; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 1c967ceb..04aadf93 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { - "name": "xiaomi-zb2mqtt", - "version": "1.0.0", + "name": "zigbee2mqtt", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fafe95cd..2a465e6f 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,24 @@ { - "name": "xiaomi-zb2mqtt", - "version": "1.0.0", - "description": "Xiaomi Zigbee to MQTT bridge using zigbee-shepherd", + "name": "zigbee2mqtt", + "version": "0.1.0", + "description": "Zigbee to MQTT bridge using zigbee-shepherd", "main": "index.js", "repository": { "type": "git", - "url": "git+https://github.com/Koenkk/xiaomi-zb2mqtt.git" + "url": "git+https://github.com/Koenkk/zigbee2mqtt.git" }, "keywords": [ "xiaomi", + "tradfri", + "hue", + "bridge", "zigbee", "mqtt", "cc2531" ], "scripts": { "start": "node index.js", - "docs": "node doc.js" + "docgen": "node support/docgen.js" }, "author": "Koen Kanters", "license": "GPL-3.0", diff --git a/doc.js b/support/docgen.js similarity index 76% rename from doc.js rename to support/docgen.js index 939f7e82..6c74e1bf 100644 --- a/doc.js +++ b/support/docgen.js @@ -8,85 +8,100 @@ const plannedToSupport = [ model: 'WXKG11LM', description: 'Aqara wireless switch', supports: '-', + vendor: 'Xiaomi', }, { model: 'WSDCGQ11LM', description: 'Aqara temperature & humidity sensor', supports: '-', + vendor: 'Xiaomi', }, { model: 'MCCGQ11LM', description: 'Aqara door & window contact sensor', supports: '-', + vendor: 'Xiaomi', }, { model: 'RTCGQ11LM', description: 'Aqara human body movement and illuminance sensor', supports: '-', + vendor: 'Xiaomi', }, { model: 'SJCGQ11LM', description: 'Aqara water leak sensor', supports: '-', + vendor: 'Xiaomi', }, { model: 'MFKZQ01LM', description: 'Mi magic cube controller', supports: '-', + vendor: 'Xiaomi', }, { model: 'WXKG03LM', description: 'Aqara single key wireless wall switch', supports: '-', + vendor: 'Xiaomi', }, { model: 'WXKG02LM', description: 'Aqara double key wireless wall switch', supports: '-', + vendor: 'Xiaomi', }, { model: 'QBKG11LM', description: 'Aqara single key wired wall switch', supports: '-', + vendor: 'Xiaomi', }, { model: 'QBKG03LM', description: 'Aqara double key wired wall switch', supports: '-', + vendor: 'Xiaomi', }, { model: 'ZNCZ02LM', description: 'Mi power plug ZigBee', supports: '-', + vendor: 'Xiaomi', }, { model: 'QBCZ11LM', description: 'Aqara wall socket', supports: '-', + vendor: 'Xiaomi', }, { model: 'JTYJ-GD-01LM/BW', description: 'MiJia Honeywell smoke detector', supports: '-', + vendor: 'Xiaomi', }, { model: 'KTBL01LM', description: 'Aqara air conditioning companion', supports: '-', + vendor: 'Xiaomi', }, { model: 'KTBL02LM', description: 'Aqara air conditioning companion 2', supports: '-', + vendor: 'Xiaomi', }, ]; -const parsers = require('./parsers'); -const deviceMapping = require('./devices'); +const zigbee2mqtt = require('../lib/converters/zigbee2mqtt'); +const deviceMapping = require('../lib/devices'); // Sanity check if all supported devices are in deviceMapping const supportedDevices = new Set(); -parsers.forEach((p) => supportedDevices.add(...p.devices)); +zigbee2mqtt.forEach((p) => supportedDevices.add(...p.devices)); // Check if in deviceMapping. supportedDevices.forEach((s) => { @@ -99,15 +114,15 @@ const logDevices = (devices) => { console.log('| Model | Description | Picture |') console.log('| ------------- | ------------- | -------------------------- |') devices.forEach((device) => { - console.log(`| ${device.model} | ${device.description} (${device.supports}) | ![${device.model}](images/devices/${device.model.replace('/', '-')}.jpg) |`); + console.log(`| ${device.model} | ${device.vendor} ${device.description} (${device.supports}) | ![${device.model}](images/devices/${device.model.replace('/', '-')}.jpg) |`); }); } console.log('GENERATED') console.log('=============================') -console.log('*NOTE: Automatically generated by `npm run docs`*') +console.log('*NOTE: Automatically generated by `npm run docgen`*') console.log('') -console.log('In case your device is **NOT** listed here, please create an issue at: https://github.com/Koenkk/xiaomi-zb2mqtt/issues'); +console.log('In case your device is **NOT** listed here, please create an issue at: https://github.com/Koenkk/zigbee2mqtt/issues'); console.log('') logDevices(Object.values(deviceMapping)); console.log('');