From c29bfe1455147a81e8cd7d1d44df133b4e058baa Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Wed, 29 May 2019 20:11:40 +0200 Subject: [PATCH] Allow to specify endpoint when adding device to group. (#1515) * uu * Update --- lib/extension/groups.js | 50 +++++++++++++++----------- test/group.test.js | 79 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 20 deletions(-) diff --git a/lib/extension/groups.js b/lib/extension/groups.js index 3fccba00..aac5274d 100644 --- a/lib/extension/groups.js +++ b/lib/extension/groups.js @@ -1,6 +1,7 @@ const settings = require('../util/settings'); const logger = require('../util/logger'); const data = require('../util/data'); +const utils = require('../util/utils'); const fs = require('fs'); const diff = require('deep-diff'); @@ -44,7 +45,7 @@ class Groups { const groupID = diff.path[0]; if (diff.kind === 'N') { - diff.rhs.forEach((ieeeAddr) => this.updateDeviceGroup(ieeeAddr, 'add', groupID)); + diff.rhs.forEach((ID) => this.updateDeviceGroup(ID, 'add', groupID)); } else if (diff.kind === 'A') { if (diff.item.lhs) { this.updateDeviceGroup(diff.item.lhs, 'remove', groupID); @@ -52,7 +53,7 @@ class Groups { this.updateDeviceGroup(diff.item.rhs, 'add', groupID); } } else if (diff.kind === 'D') { - diff.lhs.forEach((ieeeAddr) => this.updateDeviceGroup(ieeeAddr, 'remove', groupID)); + diff.lhs.forEach((ID) => this.updateDeviceGroup(ID, 'remove', groupID)); } else if (diff.kind === 'E') { this.updateDeviceGroup(diff.rhs, 'add', groupID); this.updateDeviceGroup(diff.lhs, 'remove', groupID); @@ -61,9 +62,28 @@ class Groups { } } - getGroupsOfDevice(ieeeAddr) { + parseID(ID) { + let entityID = ID; + let endpointID = null; + const postfix = utils.getPostfixes().find((p) => entityID.endsWith(`/${p}`)); + if (postfix) { + // Found a postfix, retrieve the endpoint which correspodns to the postfix + entityID = entityID.substring(0, entityID.length - (postfix.length + 1)); + const endpoint = utils.getEndpointByEntityID(this.zigbee, entityID, postfix); + + if (!endpoint) { + return; + } + + endpointID = endpoint.epId; + } + + return {endpointID, entityID}; + } + + getGroupsOfDevice(entityID) { return Object.keys(settings.getGroups()).filter((groupID) => { - return settings.getGroup(groupID).devices.includes(ieeeAddr); + return settings.getGroup(groupID).devices.includes(entityID); }); } @@ -113,7 +133,7 @@ class Groups { return {friendly_name: topic, type}; } - updateDeviceGroup(ieeeAddr, cmd, groupID) { + updateDeviceGroup(ID, cmd, groupID) { let payload = null; const orignalCmd = cmd; if (cmd === 'add') { @@ -127,6 +147,9 @@ class Groups { cmd = 'removeAll'; } + const {entityID, endpointID} = this.parseID(ID); + const ieeeAddr = settings.resolveEntity(entityID).ID; + const cb = (error, rsp) => { if (error) { logger.error(`Failed to ${cmd} ${ieeeAddr} from ${groupID}`); @@ -176,7 +199,7 @@ class Groups { this.zigbee.publish( ieeeAddr, 'device', 'genGroups', cmd, 'functional', - payload, null, null, cb, + payload, null, endpointID, cb, ); } @@ -194,21 +217,8 @@ class Groups { return; } - if (groupID === 99) { - logger.error('Group 99 is reserved, please use a different groupID'); - return; - } - - // Map message to ieeeAddr and check if device exist. - message = message.toString(); - const ieeeAddr = settings.getIeeeAddrByFriendlyName(message) || message; - if (!this.zigbee.getDevice(ieeeAddr)) { - logger.error(`Failed to find device '${message}'`); - return; - } - // Send command to the device. - this.updateDeviceGroup(ieeeAddr, topic.type, groupID); + this.updateDeviceGroup(message.toString(), topic.type, groupID.toString()); return true; } diff --git a/test/group.test.js b/test/group.test.js index a7378524..67270259 100644 --- a/test/group.test.js +++ b/test/group.test.js @@ -1,4 +1,5 @@ const Groups = require('../lib/extension/groups'); +const settings = require('../lib/util/settings'); let groupExtension = null; let zigbee = null; @@ -175,4 +176,82 @@ describe('Groups', () => { {groupid: '2', groupname: ''}, null, null, expect.any(Function) ); }); + + it('Apply group updates add with postfix', async () => { + zigbee.publish.mockClear(); + zigbee.getDevice = () => ({modelId: 'lumi.ctrl_neutral2'}); + zigbee.getEndpoint = (entityID, ep) => ({epId: ep}); + const from = {}; + const to = {'1': ['0x12345689/right']}; + groupExtension.apply(from, to); + expect(zigbee.publish).toHaveBeenCalledTimes(1); + expect(zigbee.publish).toHaveBeenCalledWith( + '0x12345689', 'device', 'genGroups', 'add', 'functional', + {groupid: '1', groupname: ''}, null, 3, expect.any(Function) + ); + }); + + it('Apply group updates add and remove with postfix', async () => { + zigbee.publish.mockClear(); + zigbee.getDevice = () => ({modelId: 'lumi.ctrl_neutral2'}); + zigbee.getEndpoint = (entityID, ep) => ({epId: ep}); + const from = {'1': ['0x12345689/right']}; + const to = {'1': ['0x12345689'], '2': ['0x12345689/left']}; + groupExtension.apply(from, to); + expect(zigbee.publish).toHaveBeenCalledTimes(3); + expect(zigbee.publish).toHaveBeenCalledWith( + '0x12345689', 'device', 'genGroups', 'add', 'functional', + {groupid: '2', groupname: ''}, null, 2, expect.any(Function) + ); + expect(zigbee.publish).toHaveBeenCalledWith( + '0x12345689', 'device', 'genGroups', 'remove', 'functional', + {groupid: '1'}, null, 3, expect.any(Function) + ); + expect(zigbee.publish).toHaveBeenCalledWith( + '0x12345689', 'device', 'genGroups', 'add', 'functional', + {groupid: '1', groupname: ''}, null, null, expect.any(Function) + ); + }); + + it('Add to group via MQTT', async () => { + zigbee.publish.mockClear(); + zigbee.getDevice = () => ({modelId: 'lumi.ctrl_neutral2'}); + zigbee.getEndpoint = (entityID, ep) => ({epId: ep}); + jest.spyOn(settings, 'getGroupIDByFriendlyName').mockReturnValue(1); + jest.spyOn(settings, 'getIeeeAddrByFriendlyName').mockReturnValue('0x12345689'); + groupExtension.onMQTTMessage('zigbee2mqtt/bridge/group/my_group/add', 'my_switch'); + expect(zigbee.publish).toHaveBeenCalledTimes(1); + expect(zigbee.publish).toHaveBeenCalledWith( + '0x12345689', 'device', 'genGroups', 'add', 'functional', + {groupid: '1', groupname: ''}, null, null, expect.any(Function) + ); + }); + + it('Add to group via MQTT with postfix', async () => { + zigbee.publish.mockClear(); + zigbee.getDevice = () => ({modelId: 'lumi.ctrl_neutral2'}); + zigbee.getEndpoint = (entityID, ep) => ({epId: ep}); + jest.spyOn(settings, 'getGroupIDByFriendlyName').mockReturnValue(1); + jest.spyOn(settings, 'getIeeeAddrByFriendlyName').mockReturnValue('0x12345689'); + groupExtension.onMQTTMessage('zigbee2mqtt/bridge/group/my_group/add', 'my_switch/right'); + expect(zigbee.publish).toHaveBeenCalledTimes(1); + expect(zigbee.publish).toHaveBeenCalledWith( + '0x12345689', 'device', 'genGroups', 'add', 'functional', + {groupid: '1', groupname: ''}, null, 3, expect.any(Function) + ); + }); + + it('Remove from group via MQTT with postfix', async () => { + zigbee.publish.mockClear(); + zigbee.getDevice = () => ({modelId: 'lumi.ctrl_neutral2'}); + zigbee.getEndpoint = (entityID, ep) => ({epId: ep}); + jest.spyOn(settings, 'getGroupIDByFriendlyName').mockReturnValue(1); + jest.spyOn(settings, 'getIeeeAddrByFriendlyName').mockReturnValue('0x12345689'); + groupExtension.onMQTTMessage('zigbee2mqtt/bridge/group/my_group/remove', 'my_switch/left'); + expect(zigbee.publish).toHaveBeenCalledTimes(1); + expect(zigbee.publish).toHaveBeenCalledWith( + '0x12345689', 'device', 'genGroups', 'remove', 'functional', + {groupid: '1'}, null, 2, expect.any(Function) + ); + }); });