From fc7383b9401cb96faea5f8464bfc6b0926574fac Mon Sep 17 00:00:00 2001 From: Cestercian Date: Sun, 25 Oct 2026 12:02:17 +0530 Subject: [PATCH] fix: Home Assistant: don't treat light_type select as an endpoint light (#33472) --- lib/extension/homeassistant.ts | 6 ++-- test/extensions/homeassistant.test.ts | 43 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 992bbdc0a..bd69ad120 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -1565,9 +1565,11 @@ export class HomeAssistant extends Extension { } /* v8 ignore stop */ + const component = topicMatch[1]; const objectID = topicMatch[3]; - const lightMatch = /^light_(.*)/.exec(objectID); - const coverMatch = /^cover_(.*)/.exec(objectID); + // Only endpoint lights/covers (`light_l1`, `cover_left`, ...). A `light_type` select shares the prefix. + const lightMatch = component === "light" ? /^light_(.*)/.exec(objectID) : null; + const coverMatch = component === "cover" ? /^cover_(.*)/.exec(objectID) : null; const match = lightMatch || coverMatch; diff --git a/test/extensions/homeassistant.test.ts b/test/extensions/homeassistant.test.ts index c7505c09a..5d1235052 100644 --- a/test/extensions/homeassistant.test.ts +++ b/test/extensions/homeassistant.test.ts @@ -2962,6 +2962,49 @@ describe("Extension: HomeAssistant", () => { expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith("zigbee2mqtt/U202DST600ZB/l1", expect.any(String), expect.any(Object)); }); + it("Should not republish a light_type select as an endpoint light state", async () => { + const entity = getZ2MEntity(devices.bulb); + assert(entity.isDevice()); + const definition = entity.definition; + assert(definition); + const originalExposes = definition.exposes; + const lightType = new zhc.Enum("light_type", zhc.access.STATE_SET, ["led", "incandescent", "halogen"]); + definition.exposes = (device, options) => { + const exposes = typeof originalExposes === "function" ? originalExposes(device, options) : originalExposes; + return [...exposes, lightType]; + }; + + try { + await resetExtension(); + expect(mockMQTTPublishAsync).toHaveBeenCalledWith( + "homeassistant/select/0x000b57fffec6a5b2/light_type/config", + expect.any(String), + expect.any(Object), + ); + + mockMQTTPublishAsync.mockClear(); + await extension.onPublishEntityState({ + entity, + message: {state: "OFF", brightness: 254, light_type: "led", linkquality: 255}, + payload: {light_type: "led"}, + }); + + expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith("zigbee2mqtt/bulb/type", expect.any(String), expect.any(Object)); + + // Multi-endpoint covers still republish onto the endpoint topic. + const cover = getZ2MEntity(devices.TS130F_DUAL_COVER_SWITCH); + mockMQTTPublishAsync.mockClear(); + await extension.onPublishEntityState({ + entity: cover, + message: {state_left: "OPEN", position_left: 20}, + payload: {position_left: 20}, + }); + expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/0xa4c138018cf95021/left", stringify({state: "OPEN", position: 20}), {}); + } finally { + definition.exposes = originalExposes; + } + }); + it("Shouldnt crash in onPublishEntityState on group publish", async () => { mockLogger.error.mockClear(); mockMQTTPublishAsync.mockClear();