Add support for 'alarm_*' services

This commit is contained in:
Adrian Jagielak
2025-07-26 02:38:22 +02:00
parent dc4e676d38
commit 5d8ce20bff
66 changed files with 502 additions and 65 deletions

View File

@@ -1,5 +1,36 @@
export type EntityCategory = undefined | 'config' | 'diagnostic';
export type BinarySensorDeviceClass =
| 'battery'
| 'battery_charging'
| 'carbon_monoxide'
| 'cold'
| 'connectivity'
| 'door'
| 'garage_door'
| 'gas'
| 'heat'
| 'light'
| 'lock'
| 'moisture'
| 'motion'
| 'moving'
| 'occupancy'
| 'opening'
| 'plug'
| 'power'
| 'presence'
| 'problem'
| 'running'
| 'safety'
| 'smoke'
| 'sound'
| 'tamper'
| 'update'
| 'vibration'
| 'window'
| null;
export type CoverDeviceClass =
| 'awning'
| 'blind'

View File

@@ -1,3 +1,5 @@
import { BinarySensorDeviceClass } from "./_enums";
/**
* Represents a MQTT Binary Sensor component for Home Assistant MQTT Discovery.
*
@@ -41,9 +43,9 @@ export interface BinarySensorComponent {
/**
* Sets the [class of the device](https://www.home-assistant.io/integrations/binary_sensor/#device-class),
* changing the device state and icon that is displayed on the frontend.
* The `device_class` defaults to `null`.
* The `device_class` defaults to `null` (generic binary sensor).
*/
device_class?: string | null;
device_class?: BinarySensorDeviceClass;
/**
* The string that represents the `on` state.

View File

@@ -4,6 +4,7 @@ import {
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { log } from '../logger';
import { _alarm__components } from '../services/_alarm';
import { barrier_ctrl__components } from '../services/barrier_ctrl';
import { basic__components } from '../services/basic';
import { battery__components } from '../services/battery';
@@ -162,8 +163,24 @@ const serviceHandlers: {
topicPrefix: string,
device: VinculumPd7Device,
svc: VinculumPd7Service,
svcName: string,
) => ServiceComponentsCreationResult | undefined;
} = {
alarm_appliance: _alarm__components,
alarm_burglar: _alarm__components,
alarm_emergency: _alarm__components,
alarm_fire: _alarm__components,
alarm_gas: _alarm__components,
alarm_health: _alarm__components,
alarm_heat: _alarm__components,
alarm_lock: _alarm__components,
alarm_power: _alarm__components,
alarm_siren: _alarm__components,
alarm_system: _alarm__components,
alarm_time: _alarm__components,
alarm_water_valve: _alarm__components,
alarm_water: _alarm__components,
alarm_weather: _alarm__components,
barrier_ctrl: barrier_ctrl__components,
basic: basic__components,
battery: battery__components,
@@ -296,7 +313,12 @@ export function haPublishDevice(parameters: {
continue;
}
const result = handler(topicPrefix, parameters.vinculumDeviceData, svc);
const result = handler(
topicPrefix,
parameters.vinculumDeviceData,
svc,
svcName,
);
if (!result) {
log.error(
`Invalid service data prevented component creation: ${parameters.vinculumDeviceData} ${svc}`,

View File

@@ -175,10 +175,19 @@ const haStateCache: Record<
Record<string, Record<string, any>> // payload (addr → { attr → value })
> = {};
const attributeTypeKeyMap: Record<string, string> = {
alarm: 'event',
};
function getTypeKey(attrName: string): string {
// Default key is 'type', but override for certain attributes
return attributeTypeKeyMap[attrName] || 'type';
}
/**
* Helper function to process multiple values for an attribute, handling typed values
*/
function processAttributeValues(values: any[]): any {
function processAttributeValues(values: any[], attrName?: string): any {
if (!values || values.length === 0) {
return undefined;
}
@@ -190,9 +199,10 @@ function processAttributeValues(values: any[]): any {
return tsB - tsA; // Latest first
});
// Check if any value has a 'type' property in its val object
const typeKey = getTypeKey(attrName || '');
const hasTypedValues = sortedValues.some(
(v) => v.val && typeof v.val === 'object' && v.val.type,
(v) => v.val && typeof v.val === 'object' && v.val[typeKey],
);
if (!hasTypedValues) {
@@ -204,12 +214,11 @@ function processAttributeValues(values: any[]): any {
const typeMap: Record<string, any> = {};
for (const value of sortedValues) {
if (value.val && typeof value.val === 'object' && value.val.type) {
const type = value.val.type;
if (!typeMap[type]) {
// Create a copy without the 'type' property
const { type: _, ...valueWithoutType } = value.val;
typeMap[type] = valueWithoutType;
if (value.val && typeof value.val === 'object' && value.val[typeKey]) {
const key = value.val[typeKey];
if (!typeMap[key]) {
const { [typeKey]: _, ...valueWithoutType } = value.val;
typeMap[key] = valueWithoutType;
}
}
}
@@ -238,7 +247,10 @@ export function haUpdateState(parameters: {
const serviceState: Record<string, any> = {};
for (const attr of service.attributes || []) {
const processedValue = processAttributeValues(attr.values || []);
const processedValue = processAttributeValues(
attr.values || [],
attr.name,
);
if (processedValue !== undefined) {
serviceState[attr.name] = processedValue;
}
@@ -274,6 +286,7 @@ export function haUpdateStateValueReport(parameters: {
}) {
// Strip the FIMP envelope so we end up with "/rt:dev/…/ad:x_y"
const addr = parameters.topic.replace(/^pt:j1\/mt:evt/, '');
const typeKey = getTypeKey(parameters.attrName);
for (const [stateTopic, payload] of Object.entries(haStateCache)) {
if (!payload[addr]) continue;
@@ -282,11 +295,11 @@ export function haUpdateStateValueReport(parameters: {
if (
parameters.value &&
typeof parameters.value === 'object' &&
parameters.value.type
parameters.value[typeKey]
) {
// Handle typed value update
const type = parameters.value.type;
const { type: _, ...valueWithoutType } = parameters.value;
const key = parameters.value[typeKey];
const { [typeKey]: _, ...valueWithoutType } = parameters.value;
// Get current attribute value
const currentAttrValue = payload[addr][parameters.attrName];
@@ -299,12 +312,12 @@ export function haUpdateStateValueReport(parameters: {
// Current value is already a type map, update the specific type
payload[addr][parameters.attrName] = {
...currentAttrValue,
[type]: valueWithoutType,
[key]: valueWithoutType,
};
} else {
// Current value is not a type map, convert it to one
payload[addr][parameters.attrName] = {
[type]: valueWithoutType,
[key]: valueWithoutType,
};
}
} else {