diff --git a/lib/controller.js b/lib/controller.js index c1e4442b8..294503619 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -9,6 +9,7 @@ const homeassistant = require('./homeassistant'); const mqttConfigRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/config/\\w+`, 'g'); const mqttDeviceRegex = new RegExp(`${settings.get().mqtt.base_topic}/\\w+/set`, 'g'); +const mqttDevicePrefixRegex = new RegExp(`${settings.get().mqtt.base_topic}/\\w+/\\w+/set`, 'g'); class Controller { @@ -25,7 +26,11 @@ class Controller { if (error) { logger.error('Failed to start'); } else { - const subscriptions = [`${settings.get().mqtt.base_topic}/+/set`, `${settings.get().mqtt.base_topic}/bridge/config/+`]; + const subscriptions = [ + `${settings.get().mqtt.base_topic}/+/set`, + `${settings.get().mqtt.base_topic}/+/+/set`, + `${settings.get().mqtt.base_topic}/bridge/config/+` + ]; this.mqtt.connect(this.handleMQTTMessage, subscriptions, () => this.handleStarted()); } }); @@ -138,8 +143,8 @@ class Controller { handleMQTTMessage(topic, message) { if (topic.match(mqttConfigRegex)) { this.handleMQTTMessageConfig(topic, message); - } else if (topic.match(mqttDeviceRegex)) { - this.handleMQTTMessageDevice(topic, message); + } else if (topic.match(mqttDeviceRegex) || topic.match(mqttDevicePrefixRegex)) { + this.handleMQTTMessageDevice(topic, message, topic.match(mqttDevicePrefixRegex)); } else { logger.warn(`Cannot handle MQTT message with topic '${topic}' and message '${message}'`); } @@ -155,9 +160,9 @@ class Controller { } } - handleMQTTMessageDevice(topic, message) { + handleMQTTMessageDevice(topic, message, withPrefix) { const friendlyName = topic.split('/')[1]; - const topicPostfix = ''; + const topicPrefix = withPrefix ? topic.split('/')[2] : ''; // Map friendlyName to deviceID. const deviceID = Object.keys(settings.get().devices).find((id) => settings.getDevice(id).friendly_name === friendlyName); @@ -177,7 +182,7 @@ class Controller { // Find ep for this device const mappedModel = deviceMapping[this.zigbee.getDevice(deviceID).modelId]; - const ep = mappedModel.ep && mappedModel.ep[topicPostfix] ? mappedModel.ep[topicPostfix] : null; + const ep = mappedModel.ep && mappedModel.ep[topicPrefix] ? mappedModel.ep[topicPrefix] : null; Object.keys(json).forEach((key) => { // Find converter for this key. @@ -190,8 +195,10 @@ class Controller { const message = converter(json[key]); const callback = (error) => { // Devices do not report when they go off, this ensures state (on/off) is always in sync. - if (!error && key === 'state') { - this.mqttPublishDeviceState(deviceID, {state: json[key]}) + if (!error && key.startsWith('state')) { + const msg = {}; + msg[key] = json[key]; + this.mqttPublishDeviceState(deviceID, msg); } }; diff --git a/lib/converters/zigbee2mqtt.js b/lib/converters/zigbee2mqtt.js index b2ac0fcba..455edce47 100644 --- a/lib/converters/zigbee2mqtt.js +++ b/lib/converters/zigbee2mqtt.js @@ -16,6 +16,11 @@ const WXKG02LM = { 3: 'both', }; +const QBKG03LM = { + 2: 'l1', + 3: 'l2', +}; + const toPercentage = (value, min, max) => { if (value > max) { value = max; @@ -228,10 +233,23 @@ const parsers = [ } } }, + { + devices: ['QBKG03LM'], + cid: 'genOnOff', + type: 'attReport', + convert: (msg) => { + if (msg.data.data['61440']) { + const key = `state_${QBKG03LM[msg.endpoints[0].epId]}`; + const payload = {}; + payload[key] = msg.data.data['onOff'] === 1 ? "ON" : "OFF" + return payload; + } + } + }, // Ignore parsers (these message dont need parsing). { - devices: ['WXKG11LM', 'MCCGQ11LM', 'MCCGQ01LM', 'WXKG01LM', 'LED1545G12', '7146060PH', 'LED1537R6', 'ZNCZ02LM', 'QBCZ11LM', 'QBKG04LM'], + devices: ['WXKG11LM', 'MCCGQ11LM', 'MCCGQ01LM', 'WXKG01LM', 'LED1545G12', '7146060PH', 'LED1537R6', 'ZNCZ02LM', 'QBCZ11LM', 'QBKG04LM', 'QBKG03LM'], cid: 'genOnOff', type: 'devChange', convert: () => null diff --git a/lib/devices.js b/lib/devices.js index 907dca7e8..197623e55 100644 --- a/lib/devices.js +++ b/lib/devices.js @@ -38,6 +38,14 @@ const devices = { ep: {'': 2}, homeassistant: [homeassistant.switch] }, + 'lumi.ctrl_neutral2': { + model: 'QBKG03LM', + vendor: 'Xiaomi', + description: 'Aqara double key wired wall switch', + supports: 'l1 and l2 on/off', + ep: {'l1': 2, 'l2': 3}, + homeassistant: [homeassistant.switch_l1, homeassistant.switch_l2] + }, 'lumi.sens': { model: 'WSDCGQ01LM', vendor: 'Xiaomi', diff --git a/lib/homeassistant.js b/lib/homeassistant.js index 350f5133a..3c78823b4 100644 --- a/lib/homeassistant.js +++ b/lib/homeassistant.js @@ -130,6 +130,28 @@ const configurations = { command_topic: true } }, + 'switch_l1': { + type: 'switch', + object_id: 'switch_l1', + discovery_payload: { + payload_off: 'OFF', + payload_on: 'ON', + value_template: '{{ value_json.state_l1 }}', + command_topic: true, + command_topic_prefix: 'l1' + } + }, + 'switch_l2': { + type: 'switch', + object_id: 'switch_l2', + discovery_payload: { + payload_off: 'OFF', + payload_on: 'ON', + value_template: '{{ value_json.state_l2 }}', + command_topic: true, + command_topic_prefix: 'l2' + } + }, }; // A map of all discoverd devices @@ -157,7 +179,13 @@ function discover(deviceID, configs, mqtt) { } if (payload.command_topic) { - payload.command_topic = `${settings.get().mqtt.base_topic}/${friendlyName}/set`; + payload.command_topic = `${settings.get().mqtt.base_topic}/${friendlyName}/`; + + if (payload.command_topic_prefix) { + payload.command_topic += `${payload.command_topic_prefix}/`; + } + + payload.command_topic += 'set'; } mqtt.publish(topic, JSON.stringify(payload), true, null, 'homeassistant');