HA: `homeassistant.switch.type: light` publishes `value_template` (switch key) → light entity stays `unknown` forever

Open Beginner friendly
#33,402 0 comments 0 reactions 0 assignees View on GitHub

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
  1. Any device with a switch expose (e.g. ZBMINIR2 / 01MINIZB / TS0001):
    devices:
      '0x1cc089fffe70d277':
        friendly_name: Terrassen Licht
        homeassistant:
          switch:
            type: light
            object_id: light
    
  2. Inspect the retained discovery payload: homeassistant/light/0x1cc089fffe70d277/light/config contains
    value_template and no state_value_template.
  3. In HA the light entity is controllable, but its state stays unknown forever (no on/off in the
    recorder history).
Evidence (live system, 35 affected lights)
  • light.* (mqtt) entities with at least one on/off state 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 report on/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 to zigbee2mqtt/# — 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

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Koenkk/zigbee2mqtt

All issues in Koenkk/zigbee2mqtt

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.