From 72428897a7ab78057aa3aafa5d2a0e112e6a2a73 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Sat, 14 Dec 2024 14:24:34 +0100 Subject: [PATCH] fix: Fix group with only multi endpoints devices not being controllable (#25156) --- lib/extension/publish.ts | 3 +- test/extensions/availability.test.ts | 1 + test/extensions/bridge.test.ts | 100 +++++++++++++++++++++------ test/extensions/publish.test.ts | 17 ++++- test/mocks/data.ts | 6 ++ test/mocks/zigbeeHerdsman.ts | 22 ++++++ 6 files changed, 126 insertions(+), 23 deletions(-) diff --git a/lib/extension/publish.ts b/lib/extension/publish.ts index 56d535eaf..3bb83b8bb 100644 --- a/lib/extension/publish.ts +++ b/lib/extension/publish.ts @@ -202,7 +202,8 @@ export default class Publish extends Extension { /* istanbul ignore next */ // Match any key if the toZigbee converter defines no key. const converter = converters.find( - (c) => (!c.key || c.key.includes(key)) && (!c.endpoints || (endpointName && c.endpoints.includes(endpointName))), + (c) => + (!c.key || c.key.includes(key)) && (re instanceof Group || !c.endpoints || (endpointName && c.endpoints.includes(endpointName))), ); if (parsedTopic.type === 'set' && converter && usedConverters[endpointOrGroupID].includes(converter)) { diff --git a/test/extensions/availability.test.ts b/test/extensions/availability.test.ts index 8bf1eff49..3983e875e 100644 --- a/test/extensions/availability.test.ts +++ b/test/extensions/availability.test.ts @@ -25,6 +25,7 @@ returnDevices.push( devices.ZNCZ02LM.ieeeAddr, devices.GLEDOPTO_2ID.ieeeAddr, devices.QBKG03LM.ieeeAddr, + devices.hue_twilight.ieeeAddr, ); describe('Extension: Availability', () => { diff --git a/test/extensions/bridge.test.ts b/test/extensions/bridge.test.ts index 84b069235..b07f62158 100644 --- a/test/extensions/bridge.test.ts +++ b/test/extensions/bridge.test.ts @@ -85,7 +85,7 @@ describe('Extension: Bridge', () => { const zhVersion = await utils.getDependencyVersion('zigbee-herdsman'); const zhcVersion = await utils.getDependencyVersion('zigbee-herdsman-converters'); const directory = settings.get().advanced.log_directory; - // console.log(mockMQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/info')[1]) + // console.log(mockMQTT.publishAsync.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/info')![1]); expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/info', stringify({ @@ -118,36 +118,78 @@ describe('Extension: Bridge', () => { blocklist: [], device_options: {}, devices: { - '0x000b57fffec6a5b2': {description: 'this is my bulb', friendly_name: 'bulb', retain: true}, + '0x000b57cdfec6a5b3': {friendly_name: 'hue_twilight'}, + '0x000b57fffec6a5b2': { + description: 'this is my bulb', + friendly_name: 'bulb', + retain: true, + }, '0x000b57fffec6a5b3': {friendly_name: 'bulb_color', retain: false}, - '0x000b57fffec6a5b4': {friendly_name: 'bulb_color_2', retain: false}, + '0x000b57fffec6a5b4': { + friendly_name: 'bulb_color_2', + retain: false, + }, '0x000b57fffec6a5b7': {friendly_name: 'bulb_2', retain: false}, '0x0017880104a44559': {friendly_name: 'J1_cover'}, '0x0017880104e43559': {friendly_name: 'U202DST600ZB'}, '0x0017880104e44559': {friendly_name: '3157100_thermostat'}, '0x0017880104e45517': {friendly_name: 'remote', retain: true}, '0x0017880104e45520': {friendly_name: 'button', retain: false}, - '0x0017880104e45521': {friendly_name: 'button_double_key', retain: false}, - '0x0017880104e45522': {friendly_name: 'weather_sensor', qos: 1, retain: false}, - '0x0017880104e45523': {friendly_name: 'occupancy_sensor', retain: false}, + '0x0017880104e45521': { + friendly_name: 'button_double_key', + retain: false, + }, + '0x0017880104e45522': { + friendly_name: 'weather_sensor', + qos: 1, + retain: false, + }, + '0x0017880104e45523': { + friendly_name: 'occupancy_sensor', + retain: false, + }, '0x0017880104e45524': {friendly_name: 'power_plug', retain: false}, '0x0017880104e45526': {friendly_name: 'GL-S-007ZS'}, - '0x0017880104e45529': {friendly_name: 'unsupported2', retain: false}, - '0x0017880104e45530': {friendly_name: 'button_double_key_interviewing', retain: false}, + '0x0017880104e45529': { + friendly_name: 'unsupported2', + retain: false, + }, + '0x0017880104e45530': { + friendly_name: 'button_double_key_interviewing', + retain: false, + }, '0x0017880104e45540': {friendly_name: 'ikea_onoff'}, '0x0017880104e45541': {friendly_name: 'wall_switch', retain: false}, - '0x0017880104e45542': {friendly_name: 'wall_switch_double', retain: false}, - '0x0017880104e45543': {friendly_name: 'led_controller_1', retain: false}, - '0x0017880104e45544': {friendly_name: 'led_controller_2', retain: false}, - '0x0017880104e45545': {friendly_name: 'dimmer_wall_switch', retain: false}, + '0x0017880104e45542': { + friendly_name: 'wall_switch_double', + retain: false, + }, + '0x0017880104e45543': { + friendly_name: 'led_controller_1', + retain: false, + }, + '0x0017880104e45544': { + friendly_name: 'led_controller_2', + retain: false, + }, + '0x0017880104e45545': { + friendly_name: 'dimmer_wall_switch', + retain: false, + }, '0x0017880104e45547': {friendly_name: 'curtain', retain: false}, '0x0017880104e45548': {friendly_name: 'fan', retain: false}, '0x0017880104e45549': {friendly_name: 'siren', retain: false}, '0x0017880104e45550': {friendly_name: 'thermostat', retain: false}, '0x0017880104e45551': {friendly_name: 'smart vent', retain: false}, '0x0017880104e45552': {friendly_name: 'j1', retain: false}, - '0x0017880104e45553': {friendly_name: 'bulb_enddevice', retain: false}, - '0x0017880104e45559': {friendly_name: 'cc2530_router', retain: false}, + '0x0017880104e45553': { + friendly_name: 'bulb_enddevice', + retain: false, + }, + '0x0017880104e45559': { + friendly_name: 'cc2530_router', + retain: false, + }, '0x0017880104e45560': {friendly_name: 'livolo', retain: false}, '0x0017880104e45561': {friendly_name: 'temperature_sensor'}, '0x0017880104e45562': {friendly_name: 'heating_actuator'}, @@ -160,7 +202,10 @@ describe('Extension: Bridge', () => { '0x90fd9ffffe4b64aa': {friendly_name: 'SP600_OLD'}, '0x90fd9ffffe4b64ab': {friendly_name: 'SP600_NEW'}, '0x90fd9ffffe4b64ac': {friendly_name: 'MKS-CM-W5'}, - '0x90fd9ffffe4b64ae': {friendly_name: 'tradfri_remote', retain: false}, + '0x90fd9ffffe4b64ae': { + friendly_name: 'tradfri_remote', + retain: false, + }, '0x90fd9ffffe4b64af': {friendly_name: 'roller_shutter'}, '0x90fd9ffffe4b64ax': {friendly_name: 'ZNLDP12LM'}, '0xf4ce368a38be56a1': { @@ -187,6 +232,7 @@ describe('Extension: Bridge', () => { 12: {friendly_name: 'thermostat_group', retain: false}, 14: {friendly_name: 'switch_group', retain: false}, 15071: {friendly_name: 'group_tradfri_remote', retain: false}, + 19: {friendly_name: 'hue_twilight_group'}, 2: {friendly_name: 'group_2', retain: false}, 21: {friendly_name: 'gledopto_group'}, 9: {friendly_name: 'ha_discovery_group'}, @@ -195,8 +241,16 @@ describe('Extension: Bridge', () => { map_options: { graphviz: { colors: { - fill: {coordinator: '#e04e5d', enddevice: '#fff8ce', router: '#4ea3e0'}, - font: {coordinator: '#ffffff', enddevice: '#000000', router: '#ffffff'}, + fill: { + coordinator: '#e04e5d', + enddevice: '#fff8ce', + router: '#4ea3e0', + }, + font: { + coordinator: '#ffffff', + enddevice: '#000000', + router: '#ffffff', + }, line: {active: '#009900', inactive: '#994444'}, }, }, @@ -209,10 +263,10 @@ describe('Extension: Bridge', () => { server: 'mqtt://localhost', }, ota: { - disable_automatic_update_check: false, - update_check_interval: 1440, - image_block_response_delay: 250, default_maximum_data_size: 50, + disable_automatic_update_check: false, + image_block_response_delay: 250, + update_check_interval: 1440, }, passlist: [], serial: {disable_led: false, port: '/dev/dummy'}, @@ -2174,6 +2228,12 @@ describe('Extension: Bridge', () => { ], scenes: [{id: 4, name: 'Scene 4'}], }, + { + friendly_name: 'hue_twilight_group', + id: 19, + members: [{endpoint: 11, ieee_address: '0x000b57cdfec6a5b3'}], + scenes: [], + }, ]), {retain: true, qos: 0}, ); diff --git a/test/extensions/publish.test.ts b/test/extensions/publish.test.ts index 002c42594..8741bffd7 100644 --- a/test/extensions/publish.test.ts +++ b/test/extensions/publish.test.ts @@ -429,6 +429,19 @@ describe('Extension: Publish', () => { ); }); + it('Should publish messages to group with just 1 Hue Twilight in int (convers have an `endpoint` on it)', async () => { + // https://github.com/Koenkk/zigbee2mqtt/issues/24792 + const group = groups.hue_twilight_group; + await mockMQTTEvents.message('zigbee2mqtt/hue_twilight_group/set', stringify({state: 'ON'})); + await flushPromises(); + console.log(mockLogger.warning.mock.calls); + expect(group.command).toHaveBeenCalledTimes(1); + expect(group.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {}); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/hue_twilight_group'); + expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON'}); + }); + it('Should publish messages to groups with on and brightness', async () => { const group = groups.group_1; group.members.push(devices.bulb_color.getEndpoint(1)!); @@ -483,10 +496,10 @@ describe('Extension: Publish', () => { it('Should create and publish to group which is in configuration.yaml but not in zigbee-herdsman', async () => { settings.addGroup('group_12312', '12312'); - expect(Object.values(groups).length).toBe(10); + expect(Object.values(groups).length).toBe(11); await mockMQTTEvents.message('zigbee2mqtt/group_12312/set', stringify({state: 'ON'})); await flushPromises(); - expect(Object.values(groups).length).toBe(11); + expect(Object.values(groups).length).toBe(12); // group contains no device // @ts-expect-error runtime mock expect(groups.group_12312.command).toHaveBeenCalledTimes(0); diff --git a/test/mocks/data.ts b/test/mocks/data.ts index c2e1b1740..9f9169a13 100644 --- a/test/mocks/data.ts +++ b/test/mocks/data.ts @@ -201,6 +201,9 @@ export function writeDefaultConfiguration(config: unknown = undefined): void { '0x0017880104e45562': { friendly_name: 'heating_actuator', }, + '0x000b57cdfec6a5b3': { + friendly_name: 'hue_twilight', + }, }, groups: { 1: { @@ -233,6 +236,9 @@ export function writeDefaultConfiguration(config: unknown = undefined): void { 9: { friendly_name: 'ha_discovery_group', }, + 19: { + friendly_name: 'hue_twilight_group', + }, }, }; diff --git a/test/mocks/zigbeeHerdsman.ts b/test/mocks/zigbeeHerdsman.ts index 0135172f8..f96c2a43f 100644 --- a/test/mocks/zigbeeHerdsman.ts +++ b/test/mocks/zigbeeHerdsman.ts @@ -383,6 +383,26 @@ const bulb_2 = new Device( 'Mains (single phase)', 'TRADFRI bulb E27 WS opal 980lm', ); +const hue_twilight = new Device( + 'Router', + '0x000b57cdfec6a5b3', + 40399, + 4107, + [ + new Endpoint(1, [0, 3, 4, 5, 6, 8, 768, 2821, 4096], [5, 25, 32, 4096], '0x000b57cdfec6a5b3', [], { + lightingColorCtrl: {colorCapabilities: 254}, + }), + new Endpoint(11, [0, 3, 4, 5, 6, 8, 768, 2821, 4096], [5, 25, 32, 4096], '0x000b57cdfec6a5b3', [], { + lightingColorCtrl: {colorCapabilities: 254}, + }), + new Endpoint(12, [0, 3, 4, 5, 6, 8, 768, 2821, 4096], [5, 25, 32, 4096], '0x000b57cdfec6a5b3', [], { + lightingColorCtrl: {colorCapabilities: 254}, + }), + ], + true, + 'Mains (single phase)', + 'LGT003', +); const TS0601_thermostat = new Device( 'EndDevice', '0x0017882104a44559', @@ -488,6 +508,7 @@ export const groups = { gledopto_group: new Group(21, [GLEDOPTO_2ID.endpoints[3]]), default_bind_group: new Group(901, []), ha_discovery_group: new Group(9, [bulb_color_2.endpoints[0], bulb_2.endpoints[0], QBKG03LM.endpoints[2]]), + hue_twilight_group: new Group(19, [hue_twilight.endpoints[1]]), }; const groupMembersBackup = Object.fromEntries(Object.entries(groups).map((v) => [v[0], [...v[1].members]])); @@ -545,6 +566,7 @@ export const devices = { ), bulb_color: bulb_color, bulb_2: bulb_2, + hue_twilight, bulb_color_2: bulb_color_2, remote: new Device( 'EndDevice',