From 661f297b1fde98b95dfe991ffff75f39ec20849e Mon Sep 17 00:00:00 2001 From: Adrian Jagielak Date: Wed, 23 Jul 2025 23:19:06 +0200 Subject: [PATCH] Add support for 'fan_ctrl' service --- README.md | 2 +- futurehome/config.yaml | 2 +- futurehome/src/ha/publish_device.ts | 18 ++++++++- futurehome/src/services/fan_ctrl.ts | 63 +++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 futurehome/src/services/fan_ctrl.ts diff --git a/README.md b/README.md index 1370803..281bb52 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ todo periodical refresh of state | door_lock | | | | doorman | | | | fan | | | -| fan_ctrl | | | +| fan_ctrl | | ✅ | | fire_detector | | | | garage_door | | | | gas_detector | | | diff --git a/futurehome/config.yaml b/futurehome/config.yaml index bf42d1c..74ab011 100644 --- a/futurehome/config.yaml +++ b/futurehome/config.yaml @@ -1,6 +1,6 @@ # https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config name: Futurehome -version: "0.0.21" +version: "0.0.22" slug: futurehome description: Local Futurehome Smarthub integration url: "https://github.com/adrianjagielak/home-assistant-futurehome" diff --git a/futurehome/src/ha/publish_device.ts b/futurehome/src/ha/publish_device.ts index 0c77aca..22eefdd 100644 --- a/futurehome/src/ha/publish_device.ts +++ b/futurehome/src/ha/publish_device.ts @@ -3,6 +3,7 @@ import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_devi import { log } from "../logger"; import { basic__components } from "../services/basic"; import { battery__components } from "../services/battery"; +import { fan_ctrl__components } from "../services/fan_ctrl"; import { out_bin_switch__components } from "../services/out_bin_switch"; import { out_lvl_switch__components } from "../services/out_lvl_switch"; import { scene_ctrl__components } from "../services/scene_ctrl"; @@ -81,7 +82,7 @@ type HaDeviceConfig = { qos: number, } -export type HaComponent = SensorComponent | BinarySensorComponent | SwitchComponent | NumberComponent | ClimateComponent | SelectComponent; +export type HaComponent = SensorComponent | BinarySensorComponent | SwitchComponent | NumberComponent | ClimateComponent | SelectComponent | FanComponent; // Device class supported values: https://www.home-assistant.io/integrations/homeassistant/#device-class @@ -160,6 +161,20 @@ export type SelectComponent = { value_template: string; } +/// https://www.home-assistant.io/integrations/fan.mqtt/ +export type FanComponent = { + unique_id: string; + // platform + p: 'fan'; + command_topic: string; + optimistic: boolean; + preset_modes: string[]; + preset_mode_command_topic: string; + preset_mode_state_template: string; + state_value_template: string; + preset_mode_value_template: string; +} + export type ServiceComponentsCreationResult = { components: { [key: string]: HaComponent }; commandHandlers?: CommandHandlers; @@ -172,6 +187,7 @@ const serviceHandlers: { } = { basic: basic__components, battery: battery__components, + fan_ctrl: fan_ctrl__components, out_bin_switch: out_bin_switch__components, out_lvl_switch: out_lvl_switch__components, scene_ctrl: scene_ctrl__components, diff --git a/futurehome/src/services/fan_ctrl.ts b/futurehome/src/services/fan_ctrl.ts new file mode 100644 index 0000000..e9ee93b --- /dev/null +++ b/futurehome/src/services/fan_ctrl.ts @@ -0,0 +1,63 @@ +import { sendFimpMsg } from "../fimp/fimp"; +import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; +import { ServiceComponentsCreationResult } from "../ha/publish_device"; + +export function fan_ctrl__components( + topicPrefix: string, + device: VinculumPd7Device, + svc: VinculumPd7Service +): ServiceComponentsCreationResult | undefined { + const supModes: string[] = svc.props?.sup_modes ?? []; + + if (!supModes.length) return undefined; // nothing useful to expose + + const commandTopic = `${topicPrefix}${svc.addr}/command`; + + return { + components: { + [svc.addr]: { + unique_id: svc.addr, + p: "fan", + command_topic: commandTopic, + optimistic: true, + preset_modes: supModes, + preset_mode_command_topic: commandTopic, + preset_mode_state_template: `{{ value_json['${svc.addr}'].mode }}`, + // Fan is considered "on" if mode is not off/stop + state_value_template: `{{ 'ON' if value_json['${svc.addr}'].mode not in ['off', 'stop'] else 'OFF' }}`, + preset_mode_value_template: `{{ value_json['${svc.addr}'].mode }}`, + }, + }, + + commandHandlers: { + [commandTopic]: async (payload: string) => { + // Handle both on/off commands and preset mode commands + if (payload === 'ON' || payload === 'OFF') { + // For simple on/off, use the first available mode for "on" + const mode = payload === 'ON' ? supModes[0] : 'off'; + + if (supModes.includes(mode) || mode === 'off') { + await sendFimpMsg({ + address: svc.addr!, + service: "fan_ctrl", + cmd: "cmd.mode.set", + val: mode, + val_t: "string", + }); + } + } else { + // Treat as preset mode command + if (supModes.includes(payload)) { + await sendFimpMsg({ + address: svc.addr!, + service: "fan_ctrl", + cmd: "cmd.mode.set", + val: payload, + val_t: "string", + }); + } + } + }, + }, + }; +}