From 3939b37d888ccfc4a299d76effe34e5d138e1e55 Mon Sep 17 00:00:00 2001 From: Adrian Jagielak Date: Sat, 26 Jul 2025 23:06:00 +0200 Subject: [PATCH] Add support for 'sound_switch' service --- README.md | 16 +- futurehome/CHANGELOG.md | 3 +- futurehome/README.md | 16 +- futurehome/src/ha/publish_device.ts | 2 + futurehome/src/mqtt/demo_data/device.json | 76 +++++ futurehome/src/mqtt/demo_data/state.json | 45 +++ futurehome/src/services/sound_switch.ts | 351 ++++++++++++++++++++++ 7 files changed, 492 insertions(+), 17 deletions(-) create mode 100644 futurehome/src/services/sound_switch.ts diff --git a/README.md b/README.md index afeaeae..ccf1c09 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,8 @@ todo: service names and not just raw service identifiers? | Button | [scene_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/scene_ctrl.ts) | [Modusbryter](https://www.futurehome.io/en_no/shop/modeswitch-white) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Select](https://www.home-assistant.io/integrations/select/) | | Binary sensor | [sensor_contact](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_binary.ts), [sensor_presence](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_binary.ts) | | ✅ | [Binary sensor](https://www.home-assistant.io/integrations/binary_sensor/) | | Numeric sensor | [sensor_accelx](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_accely](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_accelz](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_airflow](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_airq](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_anglepos](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_atmo](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_baro](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_co](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_co2](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_current](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_dew](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_direct](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_distance](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_elresist](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_freq](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_gp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_gust](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_humid](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_lumin](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_moist](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_noise](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_power](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_rain](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_rotation](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_seismicint](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_seismicmag](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_solarrad](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_tank](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_temp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_tidelvl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_uv](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_veloc](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_voltage](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_watflow](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_watpressure](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_wattemp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_weight](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_wind](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/) | -| Siren | [siren_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/siren_ctrl.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Siren](https://www.home-assistant.io/integrations/siren/) | +| Siren | [siren_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/siren_ctrl.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Siren](https://www.home-assistant.io/integrations/siren/), [Select](https://www.home-assistant.io/integrations/siren/), [Siren](https://www.home-assistant.io/integrations/select/), [Number](https://www.home-assistant.io/integrations/number/), [Button](https://www.home-assistant.io/integrations/button/), [Sensor](https://www.home-assistant.io/integrations/sensor/) | +| Sound emitter | sound_switch | | ✅ | [Siren](https://www.home-assistant.io/integrations/siren/) | | Thermostat | [thermostat](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/thermostat.ts) | [Thermostat](https://www.futurehome.io/en_no/shop/thermostat-w) | ✅ | [Climate](https://www.home-assistant.io/integrations/climate/) | | ??? | [user_code](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/user_code.ts) | | | | | Water heater | [water_heater](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/water_heater.ts) | | ✅ | [Water heater](https://www.home-assistant.io/integrations/water_heater/) | @@ -68,14 +69,12 @@ todo: service names and not just raw service identifiers? | Service | Description | | --- | --- | -| schedule | | +| schedule | No devices or hub support this stub service. | | schedule_entry | | -| sound_switch | | -| time_parameters | | -| battery_charge_ctrl | | -| inverter_consumer_conn | | -| inverter_grid_conn | | -| inverter_solar_conn | | +| battery_charge_ctrl | No devices or hub support this stub service. | +| inverter_consumer_conn | No devices or hub support this stub service. | +| inverter_grid_conn | No devices or hub support this stub service. | +| inverter_solar_conn | No devices or hub support this stub service. | ## Virtual, unnecessary services (easily reproduced in stock Home Assistant) @@ -96,5 +95,6 @@ todo: service names and not just raw service identifiers? | Power regulator | [power_regulator](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/power_regulator.ts) | [16A Puck Relé](https://www.futurehome.io/en_no/shop/puck-relay-16a) | | | | technology_specific | | | | time | | | +| time_parameters | | | version | | | | dev_sys | | | diff --git a/futurehome/CHANGELOG.md b/futurehome/CHANGELOG.md index 9ee7b30..39d6c7c 100644 --- a/futurehome/CHANGELOG.md +++ b/futurehome/CHANGELOG.md @@ -4,7 +4,8 @@ - Improved MQTT components interfaces. - Refactored sensors. -- Added support for 'meter_*' services (electricity meter, gas meter, water meter, heating meter, cooling meter). +- Added support for 'meter_*' services (electricity meters, gas meters, water meters, heating meters, cooling meters). +- Added support for 'sound_switch' service (sound emitters). ## 0.1.5 (25.07.2025) diff --git a/futurehome/README.md b/futurehome/README.md index 69131b1..d1d4440 100644 --- a/futurehome/README.md +++ b/futurehome/README.md @@ -58,7 +58,8 @@ todo: service names and not just raw service identifiers? | Button | [scene_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/scene_ctrl.ts) | [Modusbryter](https://www.futurehome.io/en_no/shop/modeswitch-white) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Select](https://www.home-assistant.io/integrations/select/) | | Binary sensor | [sensor_contact](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_binary.ts), [sensor_presence](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_binary.ts) | | ✅ | [Binary sensor](https://www.home-assistant.io/integrations/binary_sensor/) | | Numeric sensor | [sensor_accelx](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_accely](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_accelz](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_airflow](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_airq](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_anglepos](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_atmo](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_baro](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_co](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_co2](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_current](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_dew](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_direct](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_distance](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_elresist](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_freq](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_gp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_gust](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_humid](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_lumin](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_moist](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_noise](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_power](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_rain](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_rotation](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_seismicint](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_seismicmag](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_solarrad](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_tank](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_temp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_tidelvl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_uv](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_veloc](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_voltage](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_watflow](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_watpressure](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_wattemp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_weight](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_wind](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/) | -| Siren | [siren_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/siren_ctrl.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Siren](https://www.home-assistant.io/integrations/siren/) | +| Siren | [siren_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/siren_ctrl.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Siren](https://www.home-assistant.io/integrations/siren/), [Select](https://www.home-assistant.io/integrations/siren/), [Siren](https://www.home-assistant.io/integrations/select/), [Number](https://www.home-assistant.io/integrations/number/), [Button](https://www.home-assistant.io/integrations/button/), [Sensor](https://www.home-assistant.io/integrations/sensor/) | +| Sound emitter | sound_switch | | ✅ | [Siren](https://www.home-assistant.io/integrations/siren/) | | Thermostat | [thermostat](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/thermostat.ts) | [Thermostat](https://www.futurehome.io/en_no/shop/thermostat-w) | ✅ | [Climate](https://www.home-assistant.io/integrations/climate/) | | ??? | [user_code](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/user_code.ts) | | | | | Water heater | [water_heater](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/water_heater.ts) | | ✅ | [Water heater](https://www.home-assistant.io/integrations/water_heater/) | @@ -67,14 +68,12 @@ todo: service names and not just raw service identifiers? | Service | Description | | --- | --- | -| schedule | | +| schedule | No devices or hub support this stub service. | | schedule_entry | | -| sound_switch | | -| time_parameters | | -| battery_charge_ctrl | | -| inverter_consumer_conn | | -| inverter_grid_conn | | -| inverter_solar_conn | | +| battery_charge_ctrl | No devices or hub support this stub service. | +| inverter_consumer_conn | No devices or hub support this stub service. | +| inverter_grid_conn | No devices or hub support this stub service. | +| inverter_solar_conn | No devices or hub support this stub service. | ## Virtual, unnecessary services (easily reproduced in stock Home Assistant) @@ -95,5 +94,6 @@ todo: service names and not just raw service identifiers? | Power regulator | [power_regulator](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/power_regulator.ts) | [16A Puck Relé](https://www.futurehome.io/en_no/shop/puck-relay-16a) | | | | technology_specific | | | | time | | | +| time_parameters | | | version | | | | dev_sys | | | diff --git a/futurehome/src/ha/publish_device.ts b/futurehome/src/ha/publish_device.ts index 14fcf2a..bc831fe 100644 --- a/futurehome/src/ha/publish_device.ts +++ b/futurehome/src/ha/publish_device.ts @@ -20,6 +20,7 @@ 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 { siren_ctrl__components } from '../services/siren_ctrl'; +import { sound_switch__components } from '../services/sound_switch'; import { thermostat__components } from '../services/thermostat'; import { water_heater__components } from '../services/water_heater'; import { abbreviateHaMqttKeys } from './abbreviate_ha_mqtt_keys'; @@ -201,6 +202,7 @@ const serviceHandlers: { sensor_weight: _sensor_numeric__components, sensor_wind: _sensor_numeric__components, siren_ctrl: siren_ctrl__components, + sound_switch: sound_switch__components, thermostat: thermostat__components, water_heater: water_heater__components, }; diff --git a/futurehome/src/mqtt/demo_data/device.json b/futurehome/src/mqtt/demo_data/device.json index 0846edb..92fd4be 100644 --- a/futurehome/src/mqtt/demo_data/device.json +++ b/futurehome/src/mqtt/demo_data/device.json @@ -2336,5 +2336,81 @@ "intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"] } } + }, + { + "client": { + "name": "Smart Doorbell" + }, + "id": 1002, + "model": "zw - Futurehome - Doorbell Pro", + "modelAlias": "Doorbell Pro", + "type": { + "subtype": "door", + "supported": { + "siren": [] + }, + "type": "siren" + }, + "locationRef": { + "id": "12", + "type": "Room" + }, + "thingRef": { + "id": "167" + }, + "origin": "056e610614c848c51a", + "services": { + "sound_switch": { + "name": "sound_switch", + "addr": "/rt:dev/rn:zw/ad:1/sv:sound_switch/ad:1002_0", + "enabled": true, + "props": { + "sup_tones": [ + { + "tone_id": 1, + "duration": 5, + "name": "01 Ding Dong" + }, + { + "tone_id": 2, + "duration": 9, + "name": "02 Ding Dong Tubular" + }, + { + "tone_id": 3, + "duration": 10, + "name": "03 Traditional Apartment Buzzer" + }, + { + "tone_id": 4, + "duration": 1, + "name": "04 Electric Apartment Buzzer" + }, + { + "tone_id": 5, + "duration": 3, + "name": "05 Westminster Chimes" + }, + { + "tone_id": 25, + "duration": 15, + "name": "25 Emergency Alert" + } + ], + "supports_play_with_volume": true + }, + "intf": [ + "cmd.config.get_report", + "cmd.config.set", + "evt.config.report", + "cmd.play.get_report", + "cmd.play.set", + "cmd.play.stop", + "evt.play.report" + ], + "metadata": null + } + }, + "metadata": null } ] diff --git a/futurehome/src/mqtt/demo_data/state.json b/futurehome/src/mqtt/demo_data/state.json index b8b9351..be7cc44 100644 --- a/futurehome/src/mqtt/demo_data/state.json +++ b/futurehome/src/mqtt/demo_data/state.json @@ -1727,5 +1727,50 @@ "name": "meter_cooling" } ] + }, + { + "id": 1002, + "services": [ + { + "addr": "/rt:dev/rn:zw/ad:1/sv:sound_switch/ad:1002_0", + "attributes": [ + { + "name": "config", + "values": [ + { + "ts": "2025-07-26 14:30:15 +0200", + "val": { + "default_tone_id": 1, + "volume": 75 + }, + "val_t": "int_map" + } + ] + }, + { + "name": "play", + "values": [ + { + "ts": "2025-07-26 14:32:45 +0200", + "val": { + "tone_id": 2, + "volume": 80 + }, + "val_t": "int_map" + }, + { + "ts": "2025-07-26 14:30:22 +0200", + "val": { + "tone_id": 1, + "volume": 75 + }, + "val_t": "int_map" + } + ] + } + ], + "name": "sound_switch" + } + ] } ] diff --git a/futurehome/src/services/sound_switch.ts b/futurehome/src/services/sound_switch.ts new file mode 100644 index 0000000..0e322e4 --- /dev/null +++ b/futurehome/src/services/sound_switch.ts @@ -0,0 +1,351 @@ +import { sendFimpMsg } from '../fimp/fimp'; +import { + VinculumPd7Device, + VinculumPd7Service, +} from '../fimp/vinculum_pd7_device'; +import { HaMqttComponent } from '../ha/mqtt_components/_component'; +import { + CommandHandlers, + ServiceComponentsCreationResult, +} from '../ha/publish_device'; + +export function sound_switch__components( + topicPrefix: string, + device: VinculumPd7Device, + svc: VinculumPd7Service, + _svcName: string, +): ServiceComponentsCreationResult | undefined { + const components: Record = {}; + const commandHandlers: CommandHandlers = {}; + + const stateTopic = `${topicPrefix}/state`; + const supTones = svc.props?.sup_tones || []; + const supportsPlayWithVolume = svc.props?.supports_play_with_volume === true; + + // Create a siren component for playing tones + if (svc.intf?.includes('cmd.play.set')) { + const sirenCommandTopic = `${topicPrefix}${svc.addr}/siren/command`; + + // Extract tone names for available_tones + const toneNames = supTones.map( + (tone: any) => tone.name || `Tone ${tone.tone_id}`, + ); + + const sirenComponent: HaMqttComponent = { + unique_id: `${svc.addr}_siren`, + platform: 'siren', + name: 'Sound Switch', + icon: 'mdi:volume-high', + command_topic: sirenCommandTopic, + state_topic: stateTopic, + state_value_template: `{{ 'ON' if value_json['${svc.addr}'].play.tone_id is defined else 'OFF' }}`, + optimistic: false, + support_duration: false, // FIMP doesn't support custom duration + support_volume_set: supportsPlayWithVolume, + }; + + if (toneNames.length > 0) { + sirenComponent.available_tones = toneNames; + } + + components[`${svc.addr}_siren`] = sirenComponent; + + commandHandlers[sirenCommandTopic] = async (payload: string) => { + try { + const command = JSON.parse(payload); + + if (command.state === 'OFF') { + // Stop playing + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.play.stop', + val_t: 'null', + val: null, + }); + } else if (command.state === 'ON') { + // Start playing + const playMap: any = {}; + + // Handle tone selection + if (command.tone && toneNames.includes(command.tone)) { + const toneIndex = toneNames.indexOf(command.tone); + const selectedTone = supTones[toneIndex]; + if (selectedTone?.tone_id) { + playMap.tone_id = selectedTone.tone_id; + } + } + + // Handle volume if supported + if (supportsPlayWithVolume && command.volume_level !== undefined) { + playMap.volume = Math.round(command.volume_level * 100); + } + + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.play.set', + val_t: 'int_map', + val: playMap, + }); + } + } catch (e) { + // Fallback for simple ON/OFF commands + if (payload === 'ON') { + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.play.set', + val_t: 'int_map', + val: {}, // Play with default settings + }); + } else if (payload === 'OFF') { + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.play.stop', + val_t: 'null', + val: null, + }); + } + } + }; + } + + // Create a select component for tone selection when not playing + if (supTones.length > 0 && svc.intf?.includes('cmd.play.set')) { + const toneSelectCommandTopic = `${topicPrefix}${svc.addr}/tone_select/command`; + const toneNames = supTones.map( + (tone: any) => tone.name || `Tone ${tone.tone_id}`, + ); + + components[`${svc.addr}_tone_select`] = { + unique_id: `${svc.addr}_tone_select`, + platform: 'select', + name: 'Tone Selection', + icon: 'mdi:music-note', + command_topic: toneSelectCommandTopic, + state_topic: stateTopic, + options: toneNames, + value_template: `{% set play = value_json['${svc.addr}'].play %}{% if play and play.tone_id %}{% for tone in ${JSON.stringify(supTones)} %}{% if tone.tone_id == play.tone_id %}{{ tone.name }}{% endif %}{% endfor %}{% else %}{{ '${toneNames[0]}' }}{% endif %}`, + optimistic: false, + }; + + commandHandlers[toneSelectCommandTopic] = async (payload: string) => { + if (!toneNames.includes(payload)) { + return; + } + + const toneIndex = toneNames.indexOf(payload); + const selectedTone = supTones[toneIndex]; + + if (selectedTone?.tone_id) { + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.play.set', + val_t: 'int_map', + val: { + tone_id: selectedTone.tone_id, + }, + }); + } + }; + } + + // Create a number component for volume control if supported in config + if (svc.intf?.includes('cmd.config.set') || supportsPlayWithVolume) { + const volumeCommandTopic = `${topicPrefix}${svc.addr}/volume/command`; + + components[`${svc.addr}_volume`] = { + unique_id: `${svc.addr}_volume`, + platform: 'number', + name: 'Volume', + icon: 'mdi:volume-high', + command_topic: volumeCommandTopic, + state_topic: stateTopic, + min: 0, + max: 100, + step: 1, + unit_of_measurement: '%', + value_template: `{{ value_json['${svc.addr}'].config.volume | default(50) }}`, + optimistic: false, + }; + + commandHandlers[volumeCommandTopic] = async (payload: string) => { + const volume = parseInt(payload, 10); + if (Number.isNaN(volume) || volume < 0 || volume > 100) { + return; + } + + if (svc.intf?.includes('cmd.config.set')) { + // Update configuration + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.config.set', + val_t: 'int_map', + val: { + volume: volume, + }, + }); + } + }; + } + + // Create a select component for default tone configuration + if (supTones.length > 0 && svc.intf?.includes('cmd.config.set')) { + const defaultToneCommandTopic = `${topicPrefix}${svc.addr}/default_tone/command`; + const toneNames = supTones.map( + (tone: any) => tone.name || `Tone ${tone.tone_id}`, + ); + + components[`${svc.addr}_default_tone`] = { + unique_id: `${svc.addr}_default_tone`, + platform: 'select', + name: 'Default Tone', + icon: 'mdi:music-note-outline', + entity_category: 'config', + command_topic: defaultToneCommandTopic, + state_topic: stateTopic, + options: toneNames, + value_template: `{% set config = value_json['${svc.addr}'].config %}{% if config and config.default_tone_id %}{% for tone in ${JSON.stringify(supTones)} %}{% if tone.tone_id == config.default_tone_id %}{{ tone.name }}{% endif %}{% endfor %}{% else %}{{ '${toneNames[0]}' }}{% endif %}`, + optimistic: false, + }; + + commandHandlers[defaultToneCommandTopic] = async (payload: string) => { + if (!toneNames.includes(payload)) { + return; + } + + const toneIndex = toneNames.indexOf(payload); + const selectedTone = supTones[toneIndex]; + + if (selectedTone?.tone_id) { + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.config.set', + val_t: 'int_map', + val: { + default_tone_id: selectedTone.tone_id, + }, + }); + } + }; + } + + // Create button components for stop action and configuration requests + if (svc.intf?.includes('cmd.play.stop')) { + const stopCommandTopic = `${topicPrefix}${svc.addr}/stop/command`; + + components[`${svc.addr}_stop`] = { + unique_id: `${svc.addr}_stop`, + platform: 'button', + name: 'Stop Sound', + icon: 'mdi:stop', + command_topic: stopCommandTopic, + }; + + commandHandlers[stopCommandTopic] = async (_payload: string) => { + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.play.stop', + val_t: 'null', + val: null, + }); + }; + } + + // Button to request current configuration + if (svc.intf?.includes('cmd.config.get_report')) { + const configRequestCommandTopic = `${topicPrefix}${svc.addr}/config_request/command`; + + components[`${svc.addr}_config_request`] = { + unique_id: `${svc.addr}_config_request`, + platform: 'button', + name: 'Refresh Configuration', + icon: 'mdi:refresh', + entity_category: 'diagnostic', + command_topic: configRequestCommandTopic, + }; + + commandHandlers[configRequestCommandTopic] = async (_payload: string) => { + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.config.get_report', + val_t: 'null', + val: null, + }); + }; + } + + // Button to request current play status + if (svc.intf?.includes('cmd.play.get_report')) { + const playRequestCommandTopic = `${topicPrefix}${svc.addr}/play_request/command`; + + components[`${svc.addr}_play_request`] = { + unique_id: `${svc.addr}_play_request`, + platform: 'button', + name: 'Refresh Play Status', + icon: 'mdi:refresh', + entity_category: 'diagnostic', + command_topic: playRequestCommandTopic, + }; + + commandHandlers[playRequestCommandTopic] = async (_payload: string) => { + await sendFimpMsg({ + address: svc.addr, + service: 'sound_switch', + cmd: 'cmd.play.get_report', + val_t: 'null', + val: null, + }); + }; + } + + // Create sensors for tone information if available + if (supTones.length > 0) { + // Sensor for current playing tone name + components[`${svc.addr}_current_tone`] = { + unique_id: `${svc.addr}_current_tone`, + platform: 'sensor', + name: 'Current Tone', + icon: 'mdi:music-note', + state_topic: stateTopic, + value_template: `{% set play = value_json['${svc.addr}'].play %}{% if play and play.tone_id %}{% for tone in ${JSON.stringify(supTones)} %}{% if tone.tone_id == play.tone_id %}{{ tone.name }}{% endif %}{% endfor %}{% else %}None{% endif %}`, + }; + + // Sensor for current playing tone duration + components[`${svc.addr}_tone_duration`] = { + unique_id: `${svc.addr}_tone_duration`, + platform: 'sensor', + name: 'Tone Duration', + icon: 'mdi:timer', + entity_category: 'diagnostic', + state_topic: stateTopic, + unit_of_measurement: 's', + value_template: `{% set play = value_json['${svc.addr}'].play %}{% if play and play.tone_id %}{% for tone in ${JSON.stringify(supTones)} %}{% if tone.tone_id == play.tone_id %}{{ tone.duration }}{% endif %}{% endfor %}{% else %}0{% endif %}`, + }; + } + + // Volume sensor if volume is reported in play state + if (supportsPlayWithVolume) { + components[`${svc.addr}_current_volume`] = { + unique_id: `${svc.addr}_current_volume`, + platform: 'sensor', + name: 'Current Volume', + icon: 'mdi:volume-high', + state_topic: stateTopic, + unit_of_measurement: '%', + value_template: `{{ value_json['${svc.addr}'].play.volume | default(0) }}`, + }; + } + + return { + components, + commandHandlers, + }; +}