mirror of
https://github.com/adrianjagielak/home-assistant-futurehome.git
synced 2025-09-13 07:37:09 +00:00
Add support for 'sound_switch' service
This commit is contained in:
parent
a41b75da83
commit
3939b37d88
@ -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)
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -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,
|
||||
};
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
351
futurehome/src/services/sound_switch.ts
Normal file
351
futurehome/src/services/sound_switch.ts
Normal file
@ -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<string, HaMqttComponent> = {};
|
||||
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,
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user