diff --git a/devices.js b/devices.js new file mode 100644 index 00000000..92a39b67 --- /dev/null +++ b/devices.js @@ -0,0 +1,20 @@ +const devices = { + 'lumi.sensor_switch': { + model: 'WXKG01LM', + description: 'MiJia wireless switch', + }, + 'lumi.sens': { + model: 'WSDCGQ01LM', + description: 'MiJia temperature & humidity sensor ', + }, + 'lumi.sensor_motion': { + model: 'RTCGQ01LM', + description: 'MiJia human body movement sensor', + }, + 'lumi.sensor_magnet': { + model: 'MCCGQ01LM', + description: 'MiJia door & window contact sensor', + }, +} + +module.exports = devices; diff --git a/index.js b/index.js index 02cda720..a98d822d 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const ZShepherd = require('zigbee-shepherd'); const mqtt = require('mqtt') const fs = require('fs'); const parsers = require('./parsers'); +const deviceMapping = require('./devices'); const config = require('yaml-config'); const configFile = `${__dirname}/data/configuration.yaml` const settings = config.readConfig(configFile, 'user'); @@ -51,9 +52,7 @@ function handleReady() { }); console.log(`Currently ${devices.length} devices are joined:`); - devices.forEach((device) => { - console.log(`${device.ieeeAddr} ${device.nwkAddr} ${device.modelId}`); - }); + devices.forEach((device) => console.log(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). @@ -81,7 +80,7 @@ function handleReady() { } function handleConnect() { - client.publish('xiaomiZb', 'Bridge online'); + client.publish(`${settings.mqtt.base_topic}/bridge/state`, 'online'); } function handleMessage(msg) { @@ -90,31 +89,43 @@ function handleMessage(msg) { } const device = msg.endpoints[0].device; - - // Check if new device, add to config if new. + + // New device! if (!settings.devices[device.ieeeAddr]) { - console.log(`Detected new device: ${device.ieeeAddr} ${device.nwkAddr} ${device.modelId}`); + console.log(`New device with address ${device.ieeeAddr} connected!`); settings.devices[device.ieeeAddr] = { friendly_name: device.ieeeAddr }; + writeConfig(); } - // Check if we have a parser for this type of message. + // We can't handle devices without modelId. + if (!device.modelId) { + return; + } + + // Map modelID to Xiaomi model. const modelID = msg.endpoints[0].device.modelId; - const deviceID = msg.endpoints[0].devId; + const mappedModel = deviceMapping[modelID]; + + if (!mappedModel) { + console.log(` + WARNING: Device with modelID '${modelID}' is not supported. + Please create an issue on https://github.com/Koenkk/xiaomi-zb2mqtt/issues + to add support for your device`); + } + + // Find a parser for this modelID and cid. const cid = msg.data.cid; - const parser = parsers.find((p) => { - const device = p.supportedDevices.find((device) => device[0] === deviceID && device[1] === modelID); - return device && p.cid === cid; - }); + const parser = parsers.find((p) => p.devices.includes(mappedModel.model) && p.cid === cid); if (!parser) { console.log(` - WARNING: No parser available for (${deviceID}, '${modelID}') for cid: ${cid} - Please report on https://github.com/Koenkk/xiaomi-zb2mqtt/issues - to add support for your device`); + WARNING: No parser available for '${mappedModel.model}' with cid '${cid}' + Please create an issue on https://github.com/Koenkk/xiaomi-zb2mqtt/issues + with this message.`); return; } @@ -146,6 +157,7 @@ function handleQuit() { console.error('zigbee-shepherd stopped') } + client.publish(`${settings.mqtt.base_topic}/bridge/state`, 'offline'); process.exit(); }); } @@ -154,3 +166,18 @@ function handleQuit() { function writeConfig() { config.updateConfig(settings, 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}`; +} diff --git a/parsers.js b/parsers.js index 2ecfcf42..9fe29581 100644 --- a/parsers.js +++ b/parsers.js @@ -4,20 +4,12 @@ const clickLookup = { 4: 'quadruple', } -// Used as reference: (DeviceID, ModelID, Description) -const documentation = [ - [260, 'lumi.sensor_switch', 'WXKG01LM - button switch'], - [24321, 'lumi.sens', 'WSDCGQ01LM - temprature/humidity sensor'], - [260, 'lumi.sensor_motion', 'RTCGQ11LM - Human body sensor'], - [260, 'lumi.sensor_magnet', 'YTC4005CN - Magnet door/window sensor'], -] - // Global variable store that can be used by devices. const store = {} const parsers = [ { - supportedDevices: [[260, 'lumi.sensor_switch']], + devices: ['WXKG01LM'], cid: 'genOnOff', topic: 'switch', parse: (msg, publish) => { @@ -49,19 +41,19 @@ const parsers = [ } }, { - supportedDevices: [[24321, 'lumi.sens']], + devices: ['WSDCGQ01LM'], cid: 'msTemperatureMeasurement', topic: 'temperature', parse: (msg) => parseFloat(msg.data.data['measuredValue']) / 100.0 }, { - supportedDevices: [[24321, 'lumi.sens']], + devices: ['WSDCGQ01LM'], cid: 'msRelativeHumidity', topic: 'humidity', parse: (msg) => parseFloat(msg.data.data['measuredValue']) / 100.0 }, { - supportedDevices: [[260, 'lumi.sensor_motion']], + devices: ['RTCGQ01LM'], cid: 'msOccupancySensing', topic: 'occupancy', parse: (msg, publish) => { @@ -85,26 +77,11 @@ const parsers = [ } }, { - supportedDevices: [[260, 'lumi.sensor_magnet']], + devices: ['MCCGQ01LM'], cid: 'genOnOff', topic: 'state', parse: (msg) => msg.data.data['onOff'] ? 'open' : 'closed' } ]; -// Ensure consistency of documentation. -parsers.forEach((parser) => { - parser.supportedDevices.forEach(device => { - const docs = documentation.filter(doc => { - return device[0] === doc[0] && device[1] === doc[1]; - }); - - if (docs.length === 0) { - console.log("ERROR: Incosistent documentation"); - console.log(`Please add [${device[0]}, '${device[1]}', 'Add description here'], to documentation`); - process.exit() - } - }); -}); - module.exports = parsers;