diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index ed70fc79..e4bcb0bb 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -375,7 +375,7 @@ class Bridge extends Extension { } async publishDevices() { - const devices = this.zigbee.getClients().map((device) => { + const devices = this.zigbee.getDevices().map((device) => { const definition = zigbeeHerdsmanConverters.findByDevice(device); const resolved = this.zigbee.resolveEntity(device); const definitionPayload = definition ? { @@ -385,6 +385,33 @@ class Bridge extends Extension { supports: definition.supports, } : null; + const endpoints = {}; + for (const endpoint of device.endpoints) { + const data = { + bindings: [], + clusters: { + input: endpoint.getInputClusters().map((c) => c.name), + output: endpoint.getOutputClusters().map((c) => c.name), + }, + }; + + for (const bind of endpoint.binds) { + let target; + + if (bind.target.constructor.name === 'Endpoint') { + target = { + type: 'endpoint', ieee_address: bind.target.getDevice().ieeeAddr, endpoint: bind.target.ID, + }; + } else { + target = {type: 'group', id: bind.target.groupID}; + } + + data.bindings.push({cluster: bind.cluster.name, target}); + } + + endpoints[endpoint.ID] = data; + } + return { ieee_address: device.ieeeAddr, type: device.type, @@ -397,6 +424,7 @@ class Bridge extends Extension { date_code: device.dateCode, interviewing: device.interviewing, interview_completed: device.interviewCompleted, + endpoints, }; }); diff --git a/test/bridge.test.js b/test/bridge.test.js index f3cf107d..78d217e5 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -7,11 +7,12 @@ const Controller = require('../lib/controller'); const flushPromises = () => new Promise(setImmediate); const stringify = require('json-stable-stringify'); -const {coordinator, bulb, unsupported, WXKG11LM} = zigbeeHerdsman.devices; +const {coordinator, bulb, unsupported, WXKG11LM, remote} = zigbeeHerdsman.devices; zigbeeHerdsman.returnDevices.push(coordinator.ieeeAddr); zigbeeHerdsman.returnDevices.push(bulb.ieeeAddr); zigbeeHerdsman.returnDevices.push(unsupported.ieeeAddr); zigbeeHerdsman.returnDevices.push(WXKG11LM.ieeeAddr); +zigbeeHerdsman.returnDevices.push(remote.ieeeAddr); describe('Bridge', () => { let controller; @@ -48,7 +49,7 @@ describe('Bridge', () => { it('Should publish devices on startup', async () => { expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/devices', - stringify([{"ieee_address":"0x000b57fffec6a5b2","type":"Router","network_address":40369,"supported":true,"friendly_name":"bulb","definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"},"power_source":"Mains (single phase)","date_code":null,"interviewing":false,"interview_completed":true},{"ieee_address":"0x0017880104e45518","type":"EndDevice","network_address":6536,"supported":false,"friendly_name":"0x0017880104e45518","definition":null,"power_source":"Battery","date_code":null,"interviewing":false,"interview_completed":true},{"ieee_address":"0x0017880104e45520","type":"EndDevice","network_address":6537,"supported":true,"friendly_name":"button","definition":{"model":"WXKG11LM","vendor":"Xiaomi","description":"Aqara wireless switch","supports":"single, double click (and triple, quadruple, hold, release depending on model)"},"power_source":"Battery","date_code":null,"interviewing":false,"interview_completed":true}]), + stringify([{"date_code":null,"definition":null,"endpoints":{"1":{"bindings":[],"clusters":{"input":[],"output":[]}}},"friendly_name":"Coordinator","ieee_address":"0x00124b00120144ae","interview_completed":false,"interviewing":false,"network_address":0,"power_source":null,"supported":false,"type":"Coordinator"},{"date_code":null,"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"on/off, brightness, color temperature","vendor":"IKEA"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":["genScenes","genOta"]}}},"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2","interview_completed":true,"interviewing":false,"network_address":40369,"power_source":"Mains (single phase)","supported":true,"type":"Router"},{"date_code":null,"definition":{"description":"Hue dimmer switch","model":"324131092621","supports":"on/off, brightness, up/down/hold/release, click count","vendor":"Philips"},"endpoints":{"1":{"bindings":[{"cluster":"genLevelCtrl","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"genOnOff","target":{"id":1,"type":"group"}}],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]}},"2":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genOta","genOnOff"]}}},"friendly_name":"remote","ieee_address":"0x0017880104e45517","interview_completed":true,"interviewing":false,"network_address":6535,"power_source":"Battery","supported":true,"type":"EndDevice"},{"date_code":null,"definition":null,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]}}},"friendly_name":"0x0017880104e45518","ieee_address":"0x0017880104e45518","interview_completed":true,"interviewing":false,"network_address":6536,"power_source":"Battery","supported":false,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Aqara wireless switch","model":"WXKG11LM","supports":"single, double click (and triple, quadruple, hold, release depending on model)","vendor":"Xiaomi"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]}}},"friendly_name":"button","ieee_address":"0x0017880104e45520","interview_completed":true,"interviewing":false,"network_address":6537,"power_source":"Battery","supported":true,"type":"EndDevice"}]), { retain: true, qos: 0 }, expect.any(Function) ); diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index c210eca0..8bddc37b 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -1,6 +1,11 @@ const events = {}; const assert = require('assert'); +function getKeyByValue(object, value, fallback) { + const key = Object.keys(object).find((k) => object[k] === value); + return key != null ? key : fallback; +} + class Group { constructor(groupID, members) { this.groupID = groupID; @@ -38,6 +43,14 @@ class Endpoint { this.unbind = jest.fn(); this.configureReporting = jest.fn(); this.binds = binds; + this.getInputClusters = () => inputClusters.map((c) => { + return {ID: c, name: getKeyByValue(clusters, c)}; + }).filter((c) => c.name); + + this.getOutputClusters = () => outputClusters.map((c) => { + return {ID: c, name: getKeyByValue(clusters, c)}; + }).filter((c) => c.name); + this.supportsInputCluster = (cluster) => { assert(clusters[cluster] !== undefined, `Undefined '${cluster}'`); return this.inputClusters.includes(clusters[cluster]); @@ -114,7 +127,7 @@ const devices = { 'bulb_color': bulb_color, 'bulb_2': bulb_2, 'bulb_color_2': bulb_color_2, - 'remote': new Device('EndDevice', '0x0017880104e45517', 6535, 4107, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x0017880104e45517', [{target: bulb_color.endpoints[0]}]), new Endpoint(2, [0,1,3,15,64512], [25, 6])], true, "Battery", "RWL021"), + 'remote': new Device('EndDevice', '0x0017880104e45517', 6535, 4107, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x0017880104e45517', [{target: bulb_color.endpoints[0], cluster: {ID: 8, name: 'genLevelCtrl'}}, {target: new Group(1, []), cluster: {ID: 6, name: 'genOnOff'}}]), new Endpoint(2, [0,1,3,15,64512], [25, 6])], true, "Battery", "RWL021"), 'unsupported': new Device('EndDevice', '0x0017880104e45518', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "notSupportedModelID"), 'unsupported2': new Device('EndDevice', '0x0017880104e45529', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "notSupportedModelID"), 'interviewing': new Device('EndDevice', '0x0017880104e45530', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", undefined, true),