diff --git a/lib/extension/groups.ts b/lib/extension/groups.ts index 6645f874f..0b974873b 100644 --- a/lib/extension/groups.ts +++ b/lib/extension/groups.ts @@ -165,14 +165,12 @@ export default class Groups extends Extension { if (this.state.exists(device)) { const state = this.state.get(device); - const endpointNames = device.isDevice() && device.getEndpointNames(); - const stateKey = - endpointNames && - endpointNames.length >= member.ID && - device.definition?.meta?.multiEndpoint && - !device.definition.meta.multiEndpointSkip?.includes("state") - ? `state_${endpointNames[member.ID - 1]}` - : "state"; + // the endpoint ID is not a position in getEndpointNames(), look the name up instead + const endpointName = + device.isDevice() && device.definition?.meta?.multiEndpoint && !device.definition.meta.multiEndpointSkip?.includes("state") + ? device.endpointName(member) + : undefined; + const stateKey = endpointName ? `state_${endpointName}` : "state"; if (state[stateKey] === "ON" || state[stateKey] === "OPEN") { return false; diff --git a/test/extensions/groups.test.ts b/test/extensions/groups.test.ts index 078786395..280cc3c8f 100644 --- a/test/extensions/groups.test.ts +++ b/test/extensions/groups.test.ts @@ -20,6 +20,7 @@ returnDevices.push( devices.bulb_2.ieeeAddr, devices.GLEDOPTO_2ID.ieeeAddr, devices.InovelliVZM31SN.ieeeAddr, + devices.zigfred_plus.ieeeAddr, ); describe("Extension: Groups", () => { @@ -362,6 +363,36 @@ describe("Extension: Groups", () => { expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({state: "OFF"}), {retain: false, qos: 0}); }); + it.each([ + {name: "still on", l2: "ON", groupOff: false}, + {name: "off", l2: "OFF", groupOff: true}, + ])("Should check the state of the member endpoint by its name when its ID is not its position ($name)", async ({l2, groupOff}) => { + // ZFP-1A-CH maps l2 to endpoint 7, so its state is state_l2, not state_ + const device_1 = devices.bulb_color; + const endpoint_1 = device_1.getEndpoint(1)!; + const endpoint_2 = devices.zigfred_plus.getEndpoint(7)!; + const group = groups.group_1; + group.members.push(endpoint_1); + group.members.push(endpoint_2); + settings.set(["groups"], { + 1: {friendly_name: "group_1", retain: false}, + }); + await mockMQTTEvents.message("zigbee2mqtt/group_1/set", stringify({state: "ON"})); + await flushPromises(); + // @ts-expect-error private + controller.state.set(controller.zigbee.resolveEntity(devices.zigfred_plus.ieeeAddr)!, {state_l2: l2}); + mockMQTTPublishAsync.mockClear(); + + await mockMQTTEvents.message("zigbee2mqtt/bulb_color/set", stringify({state: "OFF"})); + await flushPromises(); + expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({state: "OFF"}), {retain: false, qos: 0}); + if (groupOff) { + expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({state: "OFF"}), {retain: false, qos: 0}); + } else { + expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({state: "OFF"}), {retain: false, qos: 0}); + } + }); + it("Should not publish state change off if any lights within are still on when changed via shared group", async () => { const device_1 = devices.bulb_color; const device_2 = devices.bulb;