diff --git a/commands.js b/commands.js new file mode 100644 index 000000000..8cb2f5e14 --- /dev/null +++ b/commands.js @@ -0,0 +1,31 @@ +const commands = { + "state": (value) => { + return { + cId: 'genOnOff', + cmd: value.toLowerCase(), + zclData: {}, + } + }, + "brightness": (value) => { + return { + cId: 'genLevelCtrl', + cmd: 'moveToLevel', + zclData: { + level: value, + transtime: 1, + }, + } + }, + "color_temp": (value) => { + return { + cId: 'lightingColorCtrl', + cmd: 'moveToColorTemp', + zclData: { + colortemp: value, + transtime: 1, + }, + } + }, +} + +module.exports = commands; \ No newline at end of file diff --git a/devices.js b/devices.js index 3dd037ca9..775123ba7 100644 --- a/devices.js +++ b/devices.js @@ -1,24 +1,34 @@ const devices = { 'lumi.sensor_switch': { model: 'WXKG01LM', + vendor: 'Xiaomi', description: 'MiJia wireless switch', supports: 'single, double, triple, quadruple, many and long click', }, 'lumi.sens': { model: 'WSDCGQ01LM', + vendor: 'Xiaomi', description: 'MiJia temperature & humidity sensor ', supports: 'temperature and humidity', }, 'lumi.sensor_motion': { model: 'RTCGQ01LM', + vendor: 'Xiaomi', description: 'MiJia human body movement sensor', supports: 'occupancy, motion and no motion' }, 'lumi.sensor_magnet': { model: 'MCCGQ01LM', + vendor: 'Xiaomi', description: 'MiJia door & window contact sensor', supports: 'open and closed state', }, + 'TRADFRI bulb E27 WS opal 980lm': { + model: 'LED1545G12', + vendor: 'IKEA', + description: 'TRADFRI LED bulb E27 980 lumen, dimmable, white spectrum, opal white', + supports: 'on/off, dimming, color temperature', + }, } module.exports = devices; diff --git a/index.js b/index.js index e23e21e73..d994dff40 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,9 @@ -const debug = require('debug')('xiaomi-zb2mqtt') 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`; @@ -51,6 +51,7 @@ const shepherd = new ZShepherd( // Register callbacks client.on('connect', handleConnect); +client.on('message', handleMqttMessage); shepherd.on('ready', handleReady); shepherd.on('ind', handleMessage); process.on('SIGINT', handleQuit); @@ -76,9 +77,7 @@ shepherd.start((err) => { function handleReady() { logger.info('zigbee-shepherd ready'); - const devices = shepherd.list().filter((device) => { - return device.manufId === 4151 && device.type === 'EndDevice' - }); + const devices = shepherd.list().filter((device) => device.type !== 'Coordinator'); logger.info(`Currently ${devices.length} devices are joined:`); devices.forEach((device) => logger.info(getDeviceLogMessage(device))); @@ -108,10 +107,11 @@ function handleReady() { function handleConnect() { mqttPublish(`${settings.mqtt.base_topic}/bridge/state`, 'online', true); + client.subscribe(`${settings.mqtt.base_topic}/+/set`) } function handleMessage(msg) { - if (msg.type !== 'attReport') { + if (!msg.endpoints) { return; } @@ -227,3 +227,47 @@ function getDeviceLogMessage(device) { 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); + }); +} diff --git a/parsers.js b/parsers.js index 623a68bb3..ba02f2a9a 100644 --- a/parsers.js +++ b/parsers.js @@ -105,6 +105,21 @@ const parsers = [ devices: ['MCCGQ01LM'], cid: 'genOnOff', parse: (msg) => {return {state: msg.data.data['onOff'] ? 'open' : 'closed'}} + }, + { + devices: ['LED1545G12'], + cid: 'genOnOff', + parse: (msg) => null + }, + { + devices: ['LED1545G12'], + cid: 'genLevelCtrl', + parse: (msg) => {return {brightness: msg.data.data['currentLevel']}}, + }, + { + devices: ['LED1545G12'], + cid: 'lightingColorCtrl', + parse: (msg) => {return {color_temp: msg.data.data['colorTemperature']}}, } ];