From 67391615d96dc0058e65929c60d178aa69210e22 Mon Sep 17 00:00:00 2001 From: Colin McCambridge Date: Sun, 4 Jul 2021 13:30:33 -0500 Subject: [PATCH] Update to new Home Assistant MQTT Fan discovery (#7359) * Update to new Home Assistant MQTT Fan discovery - Support 3 generic speeds plus presets. * Customize Home Assistant discovery for 99432 * Revert "Update homeassistant.js" This reverts commit 87b0a93c7a43244a58741c72cb8dabc58e1dc87e. * Fix * fix * u * Update homeassistant.js Co-authored-by: Koen Kanters --- lib/extension/homeassistant.js | 69 +++++++++++++++++++++++++++++----- test/homeassistant.test.js | 18 +++++---- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index 12fc0c1a8..0231ae870 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -321,10 +321,51 @@ class HomeAssistant extends Extension { const speed = firstExpose.features.find((e) => e.name === 'mode'); if (speed) { - discoveryEntry.discovery_payload.speed_state_topic = true; - discoveryEntry.discovery_payload.speed_command_topic = true; - discoveryEntry.discovery_payload.speed_value_template = '{{ value_json.fan_mode }}'; - discoveryEntry.discovery_payload.speeds = speed.values; + // A fan entity in Home Assistant 2021.3 and above may have a speed, + // controlled by a percentage from 1 to 100, and/or non-speed presets. + // The MQTT Fan integration allows the speed percentage to be mapped + // to a narrower range of speeds (e.g. 1-3), and for these speeds to be + // translated to and from MQTT messages via templates. + // + // For the fixed fan modes in ZCL hvacFanCtrl, we model speeds "low", + // "medium", and "high" as three speeds covering the full percentage + // range as done in Home Assistant's zigpy fan integration, plus + // presets "on", "auto" and "smart" to cover the remaining modes in + // ZCL. This supports a generic ZCL HVAC Fan Control fan. "Off" is + // always a valid speed. + let speeds = + ['off'].concat(['low', 'medium', 'high'].filter((s) => speed.values.includes(s))); + let presets = ['on', 'auto', 'smart'].filter((s) => speed.values.includes(s)); + + if (['99432'].includes(definition.model)) { + // The Hampton Bay 99432 fan implements 4 speeds using the ZCL + // hvacFanCtrl values `low`, `medium`, `high`, and `on`, and + // 1 preset called "Comfort Breeze" using the ZCL value `smart`. + // ZCL value `auto` is unused. + speeds = ['off', 'low', 'medium', 'high', 'on']; + presets = ['smart']; + } + + const allowed = [...speeds, ...presets]; + speed.values.forEach((s) => assert(allowed.includes(s))); + const percentValues = speeds.map((s, i) => `'${s}':${i}`).join(', '); + const percentCommands = speeds.map((s, i) => `${i}:'${s}'`).join(', '); + const presetList = presets.map((s) => `'${s}'`).join(', '); + + discoveryEntry.discovery_payload.percentage_state_topic = true; + discoveryEntry.discovery_payload.percentage_command_topic = true; + discoveryEntry.discovery_payload.percentage_value_template = + `{{ {${percentValues}}[value_json.${speed.property}] | default('None') }}`; + discoveryEntry.discovery_payload.percentage_command_template = + `{{ {${percentCommands}}[value] | default('') }}`; + discoveryEntry.discovery_payload.speed_range_min = 1; + discoveryEntry.discovery_payload.speed_range_max = speeds.length - 1; + discoveryEntry.discovery_payload.preset_mode_state_topic = true; + discoveryEntry.discovery_payload.preset_mode_command_topic = true; + discoveryEntry.discovery_payload.preset_mode_value_template = + `{{ value_json.${speed.property} if value_json.${speed.property} in [${presetList}]` + + ` else 'None' | default('None') }}`; + discoveryEntry.discovery_payload.preset_modes = presets; } } else if (firstExpose.type === 'binary') { const lookup = { @@ -811,10 +852,6 @@ class HomeAssistant extends Extension { payload.temperature_high_state_topic = stateTopic; } - if (payload.speed_state_topic) { - payload.speed_state_topic = stateTopic; - } - if (payload.temperature_command_topic) { payload.temperature_command_topic = `${stateTopic}/set/${payload.temperature_command_topic}`; } @@ -835,8 +872,20 @@ class HomeAssistant extends Extension { payload.fan_mode_command_topic = `${stateTopic}/set/fan_mode`; } - if (payload.speed_command_topic) { - payload.speed_command_topic = `${stateTopic}/set/fan_mode`; + if (payload.percentage_state_topic) { + payload.percentage_state_topic = stateTopic; + } + + if (payload.percentage_command_topic) { + payload.percentage_command_topic = `${stateTopic}/set/fan_mode`; + } + + if (payload.preset_mode_state_topic) { + payload.preset_mode_state_topic = stateTopic; + } + + if (payload.preset_mode_command_topic) { + payload.preset_mode_command_topic = `${stateTopic}/set/fan_mode`; } if (payload.action_topic) { diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index 00e021266..22c8a3232 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -625,16 +625,18 @@ describe('HomeAssistant extension', () => { "state_topic":"zigbee2mqtt/fan", "state_value_template":"{{ value_json.fan_state }}", "command_topic":"zigbee2mqtt/fan/set/fan_state", - "speed_state_topic":"zigbee2mqtt/fan", - "speed_command_topic":"zigbee2mqtt/fan/set/fan_mode", - "speed_value_template":"{{ value_json.fan_mode }}", - "speeds":[ - "low", - "medium", - "high", - "on", + "percentage_state_topic":"zigbee2mqtt/fan", + "percentage_command_topic":"zigbee2mqtt/fan/set/fan_mode", + "percentage_value_template":"{{ {'off':0, 'low':1, 'medium':2, 'high':3, 'on':4}[value_json.fan_mode] | default('None') }}", + "percentage_command_template":"{{ {0:'off', 1:'low', 2:'medium', 3:'high', 4:'on'}[value] | default('') }}", + "preset_mode_state_topic":"zigbee2mqtt/fan", + "preset_mode_command_topic":"zigbee2mqtt/fan/set/fan_mode", + "preset_mode_value_template":"{{ value_json.fan_mode if value_json.fan_mode in ['smart'] else 'None' | default('None') }}", + "preset_modes":[ "smart" ], + "speed_range_min":1, + "speed_range_max":4, "json_attributes_topic":"zigbee2mqtt/fan", "name":"fan", "unique_id":"0x0017880104e45548_fan_zigbee2mqtt",