From 63533c019943fde5be55e71d2cb2ab2f8955bf21 Mon Sep 17 00:00:00 2001 From: Adrian Jagielak Date: Wed, 23 Jul 2025 23:12:15 +0200 Subject: [PATCH] Add support for 'scene_ctrl' service --- README.md | 8 +-- futurehome/config.yaml | 2 +- futurehome/src/ha/publish_device.ts | 15 ++++- futurehome/src/services/scene_ctrl.ts | 83 +++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 futurehome/src/services/scene_ctrl.ts diff --git a/README.md b/README.md index 134b541..1370803 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,6 @@ todo periodical refresh of state | garage_door | | | | gas_detector | | | | gate | | | -| gateway | | | | heat_detector | | | | heat_pump | | | | heater | | | @@ -82,8 +81,7 @@ todo periodical refresh of state | out_bin_switch | | ✅ | | out_lvl_switch | [Smart LED Dimmer](https://www.futurehome.io/en_no/shop/smart-led-dimmer-polar-white) | ✅ | | power_regulator | | | -| scene_ctrl | | | -| sensor | | | +| scene_ctrl | | ✅ | | sensor_accelx | | ✅ | | sensor_accely | | ✅ | | sensor_accelz | | ✅ | @@ -132,13 +130,15 @@ todo periodical refresh of state | virtual_meter_elec | | | | water_heater | | | | water_valve | | | + +| gateway | | | +| sensor | | | | association | | | | diagnostic | | | | indicator_ctrl | | | | ota | | | | parameters | | | | technology_specific | | | - | time | | | | version | | | | dev_sys | | | diff --git a/futurehome/config.yaml b/futurehome/config.yaml index 416fb2e..bf42d1c 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.20" +version: "0.0.21" 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 a6209e8..0c77aca 100644 --- a/futurehome/src/ha/publish_device.ts +++ b/futurehome/src/ha/publish_device.ts @@ -5,6 +5,7 @@ import { basic__components } from "../services/basic"; import { battery__components } from "../services/battery"; 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"; import { sensor_accelx__components } from "../services/sensor_accelx"; import { sensor_accely__components } from "../services/sensor_accely"; import { sensor_accelz__components } from "../services/sensor_accelz"; @@ -80,7 +81,7 @@ type HaDeviceConfig = { qos: number, } -export type HaComponent = SensorComponent | BinarySensorComponent | SwitchComponent | NumberComponent | ClimateComponent; +export type HaComponent = SensorComponent | BinarySensorComponent | SwitchComponent | NumberComponent | ClimateComponent | SelectComponent; // Device class supported values: https://www.home-assistant.io/integrations/homeassistant/#device-class @@ -148,6 +149,17 @@ export type ClimateComponent = { optimistic: boolean; } +/// https://www.home-assistant.io/integrations/select.mqtt/ +export type SelectComponent = { + unique_id: string; + // platform + p: 'select'; + options: string[]; + command_topic: string; + optimistic: boolean; + value_template: string; +} + export type ServiceComponentsCreationResult = { components: { [key: string]: HaComponent }; commandHandlers?: CommandHandlers; @@ -162,6 +174,7 @@ const serviceHandlers: { battery: battery__components, out_bin_switch: out_bin_switch__components, out_lvl_switch: out_lvl_switch__components, + scene_ctrl: scene_ctrl__components, sensor_accelx: sensor_accelx__components, sensor_accely: sensor_accely__components, sensor_accelz: sensor_accelz__components, diff --git a/futurehome/src/services/scene_ctrl.ts b/futurehome/src/services/scene_ctrl.ts new file mode 100644 index 0000000..01440e7 --- /dev/null +++ b/futurehome/src/services/scene_ctrl.ts @@ -0,0 +1,83 @@ +// Maps a Futurehome “scene_ctrl” service to MQTT entities +// ───────────────────────────────────────────────────────────── +// FIMP ➞ HA state paths used by the value templates +// value_json[svc.addr].scene – last reported scene name (string) +// value_json[svc.addr].lvl – last reported level (int) +// +// HA ➞ FIMP commands +// /scene/command → cmd.scene.set +// ───────────────────────────────────────────────────────────── + +import { sendFimpMsg } from "../fimp/fimp"; +import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; +import { + CommandHandlers, + HaComponent, + ServiceComponentsCreationResult, +} from "../ha/publish_device"; + +/** + * Creates MQTT components for a single *scene_ctrl* service. + */ +export function scene_ctrl__components( + topicPrefix: string, + _device: VinculumPd7Device, + svc: VinculumPd7Service +): ServiceComponentsCreationResult | undefined { + const components: Record = {}; + const handlers: CommandHandlers = {}; + + // ───────────── read-only entities ───────────── + if (svc.intf?.includes("evt.scene.report")) { + components[`${svc.addr}_scene`] = { + unique_id: `${svc.addr}_scene`, + p: "sensor", + unit_of_measurement: "", + value_template: `{{ value_json['${svc.addr}'].scene }}`, + }; + } + + if (svc.intf?.includes("evt.lvl.report")) { + components[`${svc.addr}_lvl`] = { + unique_id: `${svc.addr}_lvl`, + p: "sensor", + unit_of_measurement: "", + value_template: `{{ value_json['${svc.addr}'].lvl }}`, + }; + } + + // ───────────── writeable “select” (scene activator) ───────────── + const supScenes: string[] = svc.props?.sup_scenes ?? []; + if (svc.intf?.includes("cmd.scene.set") && supScenes.length) { + const commandTopic = `${topicPrefix}${svc.addr}/scene/command`; + + components[`${svc.addr}_select`] = { + unique_id: `${svc.addr}_select`, + p: "select", + options: supScenes, + command_topic: commandTopic, + optimistic: true, + value_template: `{{ value_json['${svc.addr}'].scene }}`, + }; + + handlers[commandTopic] = async (payload: string) => { + if (!supScenes.includes(payload)) return; // ignore bogus payloads + + await sendFimpMsg({ + address: svc.addr!, + service: "scene_ctrl", + cmd: "cmd.scene.set", + val_t: "string", + val: payload, + }); + }; + } + + // Nothing useful to expose? + if (!Object.keys(components).length) return undefined; + + return { + components, + commandHandlers: Object.keys(handlers).length ? handlers : undefined, + }; +}