Add support for 'siren_ctrl' service

This commit is contained in:
Adrian Jagielak 2025-07-25 21:10:57 +02:00
parent 3c82daf6e9
commit dc4e676d38
No known key found for this signature in database
GPG Key ID: 0818CF7AF6C62BFB
7 changed files with 453 additions and 2 deletions

View File

@ -118,7 +118,7 @@ todo: links to the .ts service implementations below
| sensor_wattemp | | ✅ |
| sensor_weight | | ✅ |
| sensor_wind | | ✅ |
| siren_ctrl | | |
| siren_ctrl | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | |
| thermostat | [Thermostat](https://www.futurehome.io/en_no/shop/thermostat-w) | ✅ |
| user_code | | |
| water_heater | | ✅ |

View File

@ -6,6 +6,7 @@
- Added support for 'media_player' service.
- Removed demo mode 'optimistic' override causing switches to look weird.
- Updated demo mode fake state handling.
- Added support for 'siren_ctrl' service (alarm sirens).
## 0.1.3 (25.07.2025)

View File

@ -117,7 +117,7 @@ todo: links to the .ts service implementations below
| sensor_wattemp | | ✅ |
| sensor_weight | | ✅ |
| sensor_wind | | ✅ |
| siren_ctrl | | |
| siren_ctrl | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | |
| thermostat | [Thermostat](https://www.futurehome.io/en_no/shop/thermostat-w) | ✅ |
| user_code | | |
| water_heater | | ✅ |

View File

@ -56,6 +56,7 @@ import { sensor_watpressure__components } from '../services/sensor_watpressure';
import { sensor_wattemp__components } from '../services/sensor_wattemp';
import { sensor_weight__components } from '../services/sensor_weight';
import { sensor_wind__components } from '../services/sensor_wind';
import { siren_ctrl__components } from '../services/siren_ctrl';
import { thermostat__components } from '../services/thermostat';
import { water_heater__components } from '../services/water_heater';
import { abbreviateHaMqttKeys } from './abbreviate_ha_mqtt_keys';
@ -215,6 +216,7 @@ const serviceHandlers: {
sensor_wattemp: sensor_wattemp__components,
sensor_weight: sensor_weight__components,
sensor_wind: sensor_wind__components,
siren_ctrl: siren_ctrl__components,
thermostat: thermostat__components,
water_heater: water_heater__components,
};

View File

@ -1995,5 +1995,175 @@
}
},
"metadata": null
},
{
"client": {
"name": "Futurehome SDCO Alarm"
},
"fimp": {
"adapter": "zwave-ad",
"address": "86",
"group": "ch_0"
},
"functionality": null,
"id": 73,
"lrn": true,
"model": "Futurehome SDCO Alarm",
"modelAlias": "Futurehome SDCO Alarm",
"param": {
"alarms": {
"fire": ["smoke_test"]
},
"batteryLevel": "ok",
"batteryPercentage": 80,
"humidity": 38,
"siren": "silence",
"smoke": false,
"supportedAlarms": {
"burglar": ["tamper_removed_cover"],
"fire": ["smoke", "smoke_test"],
"gas": ["CO"],
"heat": ["overheat"]
},
"temperature": 22.9799995422363,
"timestamp": "2020-01-30 07:23:39 +0100"
},
"problem": false,
"room": null,
"services": {
"alarm_burglar": {
"addr": "/rt:dev/rn:zw/ad:1/sv:alarm_burglar/ad:86_0",
"enabled": true,
"intf": ["cmd.alarm.get_report", "evt.alarm.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_events": ["tamper_removed_cover"]
}
},
"alarm_fire": {
"addr": "/rt:dev/rn:zw/ad:1/sv:alarm_fire/ad:86_0",
"enabled": true,
"intf": ["cmd.alarm.get_report", "evt.alarm.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_events": ["smoke", "smoke_test"]
}
},
"alarm_gas": {
"addr": "/rt:dev/rn:zw/ad:1/sv:alarm_gas/ad:86_0",
"enabled": true,
"intf": ["cmd.alarm.get_report", "evt.alarm.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_events": ["CO"]
}
},
"alarm_heat": {
"addr": "/rt:dev/rn:zw/ad:1/sv:alarm_heat/ad:86_0",
"enabled": true,
"intf": ["cmd.alarm.get_report", "evt.alarm.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_events": ["overheat"]
}
},
"battery": {
"addr": "/rt:dev/rn:zw/ad:1/sv:battery/ad:86_0",
"enabled": true,
"intf": ["cmd.lvl.get_report", "evt.alarm.report", "evt.lvl.report"],
"props": {
"is_secure": true,
"is_unsecure": false
}
},
"complex_alarm_system": {
"addr": "/rt:dev/rn:zw/ad:1/sv:complex_alarm_system/ad:86_0",
"enabled": true,
"intf": ["cmd.alarm.silence", "evt.alarm.silence"],
"props": {
"is_secure": true,
"is_unsecure": false
}
},
"dev_sys": {
"addr": "/rt:dev/rn:zw/ad:1/sv:dev_sys/ad:86_0",
"enabled": true,
"intf": [
"cmd.group.add_members",
"cmd.group.delete_members",
"cmd.group.get_members",
"cmd.ping.send",
"evt.group.members_report",
"evt.ping.report"
],
"props": {
"is_secure": true,
"is_unsecure": false
}
},
"indicator_ctrl": {
"addr": "/rt:dev/rn:zw/ad:1/sv:indicator_ctrl/ad:86_0",
"enabled": true,
"intf": ["cmd.indicator.set_visual_element"],
"props": {
"duration": "",
"is_secure": true,
"is_unsecure": false
}
},
"scene_ctrl": {
"addr": "/rt:dev/rn:zw/ad:1/sv:scene_ctrl/ad:86_0",
"enabled": true,
"intf": ["cmd.scene.get_report", "cmd.scene.set", "evt.scene.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_modes": ["on", "off"]
}
},
"sensor_humid": {
"addr": "/rt:dev/rn:zw/ad:1/sv:sensor_humid/ad:86_0",
"enabled": true,
"intf": ["cmd.sensor.get_report", "evt.sensor.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_units": ["g/m3", "%"]
}
},
"sensor_temp": {
"addr": "/rt:dev/rn:zw/ad:1/sv:sensor_temp/ad:86_0",
"enabled": true,
"intf": ["cmd.sensor.get_report", "evt.sensor.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_units": ["C", "F"]
}
},
"siren_ctrl": {
"addr": "/rt:dev/rn:zw/ad:1/sv:siren_ctrl/ad:86_0",
"enabled": true,
"intf": ["cmd.mode.get_report", "cmd.mode.set", "evt.mode.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_modes": ["on", "off", "fire", "CO"]
}
}
},
"supports": ["clear", "poll"],
"thing": 56,
"type": {
"subtype": null,
"supported": {
"fire_detector": []
},
"type": null
}
}
]

View File

@ -1314,5 +1314,164 @@
"name": "media_player"
}
]
},
{
"id": 73,
"services": [
{
"addr": "/rt:dev/rn:zw/ad:1/sv:alarm_burglar/ad:86_0",
"attributes": [
{
"name": "alarm",
"values": [
{
"ts": "2023-06-22 10:31:28 +0200",
"val": {
"event": "tamper_removed_cover",
"status": "deactiv"
},
"val_t": "str_map"
}
]
}
],
"name": "alarm_burglar"
},
{
"addr": "/rt:dev/rn:zw/ad:1/sv:technology_specific/ad:86_0",
"attributes": [
{
"name": "notification",
"values": [
{
"ts": "2023-06-25 22:04:42 +0200",
"val": {
"category": "smoke_alarm",
"domain": "zwave",
"subject": "",
"type": "state",
"value": "unknown_event_state"
},
"val_t": "str_map"
}
]
}
],
"name": "technology_specific"
},
{
"addr": "/rt:dev/rn:zw/ad:1/sv:battery/ad:86_0",
"attributes": [
{
"name": "lvl",
"values": [
{
"ts": "2023-06-29 09:00:25 +0200",
"val": 80,
"val_t": "int"
}
]
}
],
"name": "battery"
},
{
"addr": "/rt:dev/rn:zw/ad:1/sv:alarm_fire/ad:86_0",
"attributes": [
{
"name": "alarm",
"values": [
{
"ts": "2023-05-17 10:37:23 +0200",
"val": {
"event": "smoke_test",
"status": "deactiv"
},
"val_t": "str_map"
},
{
"ts": "2023-06-22 10:28:58 +0200",
"val": {
"event": "smoke",
"status": "deactiv"
},
"val_t": "str_map"
},
{
"ts": "2023-03-08 12:37:12 +0100",
"val": {
"event": "CO",
"status": "deactiv"
},
"val_t": "str_map"
},
{
"props": {
"silenced_by": "command"
},
"ts": "2023-06-22 17:53:19 +0200",
"val": {
"event": "silenced",
"status": "activ"
},
"val_t": "str_map"
}
]
}
],
"name": "alarm_fire"
},
{
"addr": "/rt:dev/rn:zw/ad:1/sv:dev_sys/ad:86_0",
"attributes": [
{
"name": "state",
"values": [
{
"ts": "2023-06-22 16:35:24 +0200",
"val": "UP",
"val_t": "string"
}
]
}
],
"name": "dev_sys"
},
{
"addr": "/rt:dev/rn:zw/ad:1/sv:siren_ctrl/ad:86_0",
"attributes": [
{
"name": "mode",
"values": [
{
"ts": "2023-06-22 15:49:51 +0200",
"val": "on",
"val_t": "string"
}
]
}
],
"nam,e": "siren_ctrl"
},
{
"addr": "/rt:dev/rn:zw/ad:1/sv:alarm_heat/ad:86_0",
"attributes": [
{
"name": "alarm",
"values": [
{
"ts": "2022-10-14 17:45:52 +0200",
"val": {
"event": "overheat",
"status": "deactiv"
},
"val_t": "str_map"
}
]
}
],
"name": "alarm_heat"
}
]
}
]

View File

@ -0,0 +1,119 @@
import { sendFimpMsg } from '../fimp/fimp';
import {
VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { HaMqttComponent } from '../ha/mqtt_components/_component';
import { SirenComponent } from '../ha/mqtt_components/siren';
import {
CommandHandlers,
ServiceComponentsCreationResult,
} from '../ha/publish_device';
export function siren_ctrl__components(
topicPrefix: string,
device: VinculumPd7Device,
svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined {
const components: Record<string, HaMqttComponent> = {};
const commandHandlers: CommandHandlers = {};
// Extract supported modes from service properties
const supModes = svc.props?.sup_modes || [];
if (supModes.length === 0) {
// If no supported modes are defined, we can't create a functional siren
return undefined;
}
// Main siren component
const commandTopic = `${topicPrefix}${svc.addr}/command`;
// Determine available tones based on supported modes
// Filter out 'off' as it's handled separately by Home Assistant
const availableTones = supModes.filter((mode: string) => mode !== 'off');
const sirenComponent: SirenComponent = {
unique_id: svc.addr,
platform: 'siren',
name: 'Siren',
command_topic: commandTopic,
optimistic: false,
state_value_template: `{{ (value_json['${svc.addr}'].mode != "off") | iif('ON', 'OFF') }}`,
support_duration: false,
support_volume_set: false,
};
// Add available tones if there are specific tone modes
if (availableTones.length > 0) {
sirenComponent.available_tones = availableTones;
// Use command template to handle tone selection
sirenComponent.command_template = `{% if value == "ON" %}{% if tone is defined %}{{ tone }}{% else %}{{ available_tones[0] if available_tones else "on" }}{% endif %}{% else %}off{% endif %}`;
}
// Map Home Assistant state values to display values
sirenComponent.state_value_template = `{% set mode = value_json['${svc.addr}'].mode | default('off') %}{% if mode == 'off' %}OFF{% else %}ON{% endif %}`;
components[svc.addr] = sirenComponent;
// Command handler
commandHandlers[commandTopic] = async (payload: string) => {
let targetMode: string;
// Handle different payload formats
try {
// Try to parse as JSON first (for tone commands)
const jsonPayload = JSON.parse(payload);
if (jsonPayload.state === 'ON' || jsonPayload.state === true) {
// If tone is specified and supported, use it
if (jsonPayload.tone && supModes.includes(jsonPayload.tone)) {
targetMode = jsonPayload.tone;
} else {
// Use first available non-off mode or 'on' as fallback
targetMode = availableTones.length > 0 ? availableTones[0] : 'on';
}
} else {
targetMode = 'off';
}
} catch {
// Handle simple string payloads
switch (payload) {
case 'ON':
case 'on':
// Use first available tone or 'on' as fallback
targetMode = availableTones.length > 0 ? availableTones[0] : 'on';
break;
case 'OFF':
case 'off':
targetMode = 'off';
break;
default:
// Check if payload is a supported mode/tone
if (supModes.includes(payload)) {
targetMode = payload;
} else {
return; // Unsupported payload
}
}
}
// Only send command if the target mode is supported
if (!supModes.includes(targetMode)) {
return;
}
await sendFimpMsg({
address: svc.addr,
service: 'siren_ctrl',
cmd: 'cmd.mode.set',
val_t: 'string',
val: targetMode,
});
};
return {
components,
commandHandlers,
};
}