From 8e4b2cba066a8cd151c75be5ea6dfc9168514257 Mon Sep 17 00:00:00 2001 From: Fergal Condron Date: Tue, 20 Oct 2026 16:57:46 +0100 Subject: [PATCH] fix: Home Assistant: map `emergency_heating` system mode to `heat` (#33453) Co-authored-by: Claude Opus 5.5 --- lib/extension/homeassistant.ts | 10 +++++++++ test/extensions/homeassistant.test.ts | 30 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index a7f48a97a..5156ad9fc 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -775,6 +775,16 @@ export class HomeAssistant extends Extension { discoveryEntry.discovery_payload.mode_state_topic = true; discoveryEntry.discovery_payload.mode_state_template = stateValueTemplate(mode.property); discoveryEntry.discovery_payload.modes = mode.values; + if (mode.values.includes("emergency_heating")) { + // 'emergency_heating' (e.g. Hive SLR boost) is not a valid Home Assistant HVAC mode, + // reporting it would make Home Assistant reject every state update. Report it as 'heat' + // and don't offer it as a mode; it can still be set via MQTT or the frontend. + discoveryEntry.discovery_payload.mode_state_template = stateValueTemplate( + mode.property, + `{% set mode = value_json["${mode.property}"] %}{{ 'heat' if mode == 'emergency_heating' else mode }}`, + ); + discoveryEntry.discovery_payload.modes = mode.values.filter((v) => v !== "emergency_heating"); + } discoveryEntry.discovery_payload.mode_command_topic = true; } diff --git a/test/extensions/homeassistant.test.ts b/test/extensions/homeassistant.test.ts index 7c09b4be6..9edc458b1 100644 --- a/test/extensions/homeassistant.test.ts +++ b/test/extensions/homeassistant.test.ts @@ -1876,6 +1876,36 @@ describe("Extension: HomeAssistant", () => { expect(climate!.discovery_payload).not.toHaveProperty("temperature_high_command_topic"); }); + it("Should map emergency_heating to heat for climate", () => { + const climateExpose = new zhc.Climate() + .withSetpoint("occupied_heating_setpoint", 5, 32, 0.5) + .withLocalTemperature() + .withSystemMode(["off", "auto", "heat", "emergency_heating"]) + .withEndpoint("water"); + const device = { + definition: {}, + isDevice: (): boolean => true, + isGroup: (): boolean => false, + endpoint: () => undefined, + options: {}, + exposes: (): zhc.Expose[] => [climateExpose], + zh: {endpoints: []}, + } as Device; + + // @ts-expect-error private + const configs = extension.getConfigs(device); + const climate = configs.find((c) => c.type === "climate" && c.object_id === "climate_water"); + expect(climate).toBeDefined(); + expect(climate!.discovery_payload.modes).toStrictEqual(["off", "auto", "heat"]); + expect(climate!.discovery_payload.mode_state_template).toStrictEqual( + `{% if "system_mode_water" in value_json %}{% set mode = value_json["system_mode_water"] %}{{ 'heat' if mode == 'emergency_heating' else mode }}{% endif %}`, + ); + // Expose itself is untouched so other consumers (e.g. frontend) can still use emergency_heating + expect(climateExpose.features.find((f) => f.name === "system_mode")).toMatchObject({ + values: ["off", "auto", "heat", "emergency_heating"], + }); + }); + it("Should discover devices with cover_position", () => { let payload;