HA: `homeassistant.switch.type: light` publishes `value_template` (switch key) → light entity stays `unknown` forever
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- backend
Research direction
The bug is in the switch case of lib/extension/homeassistant.ts, where the discovery payload uses value_template, and in the getConfigs override that changes type to light without renaming that key. Start by reading those two spots, then the test "Should discover devices with overridden user configuration affecting type and object_id" in test/extensions/homeassistant.test.ts. Done means a light-typed switch publishes state_value_template instead, and that test passes.
Written by the indexing model from the issue text.
Description
What happened?
Devices that are exposed as a Home Assistant light via the per-entity override
homeassistant.switch.type: light are permanently unknown in Home Assistant: the state never updates,
although turning them on/off from HA works.
Environment
- Zigbee2MQTT 2.14.2 (latest release)
- Home Assistant Core 2026.10.1
- Coordinator CC2652P (ZStack3x0), ~91 devices
Root cause
The switch expose builds its discovery payload with the switch key value_template
(lib/extension/homeassistant.ts, case "switch"):
discovery_payload: {
payload_off: state.value_off,
payload_on: state.value_on,
value_template: `{{ value_json["${property}"] }}`,
command_topic: true,
...
}
The per-entity override (getConfigs) only replaces object_id and type:
config.object_id = configOverride.object_id || config.object_id;
config.type = configOverride.type || config.type;
So the payload published to homeassistant/light/<ieee>/light/config still contains value_template
(and no state_value_template).
Home Assistant's MQTT light platform uses state_value_template; value_template is not part of the
light schema and is silently dropped (extra=probatio.REMOVE_EXTRA), see
homeassistant/components/mqtt/light/schema_basic.py (PLATFORM_SCHEMA_MODERN_BASIC,
DISCOVERY_SCHEMA_BASIC). The resulting entity has state_topic but no state template, so HA compares
the raw JSON payload ({"state":"ON","linkquality":29,...}) with payload_on/payload_off, never matches
and keeps the state at unknown. State changes from the device are received but never parsed.
Note that Z2M already uses state_value_template for fan components, so the correct key is used
elsewhere in the same file.
Steps to reproduce
- Any device with a
switchexpose (e.g. ZBMINIR2 / 01MINIZB / TS0001):devices: '0x1cc089fffe70d277': friendly_name: Terrassen Licht homeassistant: switch: type: light object_id: light - Inspect the retained discovery payload:
homeassistant/light/0x1cc089fffe70d277/light/configcontains
value_templateand nostate_value_template. - In HA the light entity is controllable, but its state stays
unknownforever (noon/offin the
recorder history).
Evidence (live system, 35 affected lights)
light.*(mqtt) entities with at least oneon/offstate in the HA recorder: 1 of 36 — and that one
is the single native light device (light expose,schema: json).switch.*(mqtt) entities of the same devices: 114 of 119 have states.- Manually adding
state_value_template(keeping everything else) to the 35 retained light payloads +
re-reading the device state (zigbee2mqtt/<name>/get) makes 33/35 entities reporton/off
immediately (the remaining 2 devices were offline). - Bindings/reportings are present (
genOnOff), the device answers read requests in seconds, and the MQTT user
used by HA can subscribe tozigbee2mqtt/#— so this is not a reporting/ACL/topic issue.
Proposed fix
Use the light state template key when a switch entry is overridden to type: "light":
diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts
index 27bfb3a..2217af9 100644
--- a/lib/extension/homeassistant.ts
+++ b/lib/extension/homeassistant.ts
@@ -1754,6 +1754,14 @@ export class HomeAssistant extends Extension {
if (configOverride) {
config.object_id = configOverride.object_id || config.object_id;
config.type = configOverride.type || config.type;
+
+ // A switch exposed as a Home Assistant light must use the light state template key:
+ // HA's MQTT light schema has no `value_template` and drops unknown keys, which makes
+ // the entity controllable but permanently "unknown".
+ if (config.type === "light" && config.discovery_payload.value_template !== undefined) {
+ config.discovery_payload.state_value_template = config.discovery_payload.value_template;
+ delete config.discovery_payload.value_template;
+ }
}
}
}
diff --git a/test/extensions/homeassistant.test.ts b/test/extensions/homeassistant.test.ts
index 8d185f5..5d669ae 100644
--- a/test/extensions/homeassistant.test.ts
+++ b/test/extensions/homeassistant.test.ts
@@ -1063,7 +1063,7 @@ describe("Extension: HomeAssistant", () => {
default_entity_id: "light.my_switch",
unique_id: "0x0017880104e45541_light_zigbee2mqtt",
origin: origin,
- value_template: '{{ value_json["state"] }}',
+ state_value_template: '{{ value_json["state"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/light/0x0017880104e45541/light/config", stringify(payload), {
The existing test "Should discover devices with overridden user configuration affecting type and object_id"
currently asserts the buggy behaviour (value_template in the light payload) and needs the second hunk.
Patch is against master 344b6e0 (== 2.14.2). I could not run the test suite locally (no Node/pnpm
toolchain on the reporting machine), so the patch is not verified in-tree — sorry about that.
Workaround (until fixed)
Re-publish the retained light discovery payload with state_value_template added (HA then parses the state
correctly); it has to be repeated after every Z2M restart/re-interview, or automated on the HA side.
Thanks for Z2M! 🐝
- Dominant language
- TypeScript
- Stars
- 15.8k
- Forks
- 2.2k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 38
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Koenkk/zigbee2mqtt
-
problem
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Koenkk/zigbee2mqtt#33565 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
new device support
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Koenkk/zigbee2mqtt#33562 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Koenkk/zigbee2mqtt#33542 ·
Maintainers usually reply within 1 day
-
Innr RB 287 C / RS 242 C: extend color_temp range to 50–1000 mired with firmware 1.9.64 / 1.9.54Openproblem
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Koenkk/zigbee2mqtt#33523 ·
Maintainers usually reply within 1 day
-
problem
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Koenkk/zigbee2mqtt#33516 ·
Maintainers usually reply within 1 day
All issues in Koenkk/zigbee2mqtt
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
kunchenguid/gh-axi#247 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
lidge-jun/opencodex#8101 · 2 comments ·
Maintainers usually reply within 1 day
-
effort:S P3 refactor
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
nightscout/nocturne#3243 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
JuliusBrussee/caveman#1449 ·
Maintainers usually reply within 1 day