Format the codebase using prettier

This commit is contained in:
Adrian Jagielak 2025-07-24 16:34:58 +02:00
parent 7b4309a596
commit 3c56a30d01
No known key found for this signature in database
GPG Key ID: 0818CF7AF6C62BFB
69 changed files with 984 additions and 895 deletions

View File

@ -1,6 +1,6 @@
# https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config # https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config
name: Futurehome name: Futurehome
version: "0.0.33" version: "0.0.34"
slug: futurehome slug: futurehome
description: Local Futurehome Smarthub integration description: Local Futurehome Smarthub integration
url: "https://github.com/adrianjagielak/home-assistant-futurehome" url: "https://github.com/adrianjagielak/home-assistant-futurehome"

View File

@ -1,53 +1,56 @@
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from 'uuid';
import { IMqttClient } from "./mqtt/interface"; import { IMqttClient } from './mqtt/interface';
export function exposeSmarthubTools( export function exposeSmarthubTools(
ha: IMqttClient, ha: IMqttClient,
fimp: IMqttClient, fimp: IMqttClient,
hubAddr = "pt:j1/mt:cmd/rt:app/rn:zb/ad:1" hubAddr = 'pt:j1/mt:cmd/rt:app/rn:zb/ad:1',
) { ) {
const base = "homeassistant/switch/fh_zb_pairing"; const base = 'homeassistant/switch/fh_zb_pairing';
const device = { const device = {
identifiers: ["futurehome_hub"], identifiers: ['futurehome_hub'],
name: "Futurehome Hub", name: 'Futurehome Hub',
model: "Smarthub", model: 'Smarthub',
}; };
ha.publish( ha.publish(
`${base}/config`, `${base}/config`,
JSON.stringify({ JSON.stringify({
name: "Zigbee Pairing", name: 'Zigbee Pairing',
uniq_id: "fh_zb_pairing", uniq_id: 'fh_zb_pairing',
cmd_t: `${base}/set`, cmd_t: `${base}/set`,
stat_t: `${base}/state`, stat_t: `${base}/state`,
device, device,
}), }),
{ retain: true, qos: 2 } { retain: true, qos: 2 },
); );
// // keep last known state locally // // keep last known state locally
// let pairingOn = false; // let pairingOn = false;
ha.subscribe(`${base}/set`); ha.subscribe(`${base}/set`);
ha.on("message", (topic, payload) => { ha.on('message', (topic, payload) => {
if (topic !== `${base}/set`) return; if (topic !== `${base}/set`) return;
const turnOn = payload.toString() === "ON"; const turnOn = payload.toString() === 'ON';
// // optimistic update so the UI flips instantly // // optimistic update so the UI flips instantly
// pairingOn = turnOn; // pairingOn = turnOn;
ha.publish(`${base}/state`, turnOn ? "ON" : "OFF", { retain: true, qos: 2 }); ha.publish(`${base}/state`, turnOn ? 'ON' : 'OFF', {
retain: true,
qos: 2,
});
// placeholder FIMP message adjust to real API if different // placeholder FIMP message adjust to real API if different
fimp.publish( fimp.publish(
hubAddr, hubAddr,
JSON.stringify({ JSON.stringify({
type: "cmd.pairing_mode.set", type: 'cmd.pairing_mode.set',
service: "zigbee", service: 'zigbee',
uid: uuidv4(), uid: uuidv4(),
val_t: "str", val_t: 'str',
val: turnOn ? "start" : "stop", val: turnOn ? 'start' : 'stop',
}), }),
{ qos: 1 } { qos: 1 },
); );
}); });

View File

@ -1,26 +1,48 @@
import { DemoFimpMqttClient } from "./mqtt/demo_client"; import { DemoFimpMqttClient } from './mqtt/demo_client';
import { IMqttClient } from "./mqtt/interface"; import { IMqttClient } from './mqtt/interface';
import { RealMqttClient } from "./mqtt/real_client"; import { RealMqttClient } from './mqtt/real_client';
export function connectHub(opts: { hubIp: string; username: string; password: string; demo: boolean; }): Promise<IMqttClient> { export function connectHub(opts: {
hubIp: string;
username: string;
password: string;
demo: boolean;
}): Promise<IMqttClient> {
const url = `mqtt://${opts.hubIp}`; const url = `mqtt://${opts.hubIp}`;
return makeClient(url, 1884, opts.username, opts.password, opts.demo); return makeClient(url, 1884, opts.username, opts.password, opts.demo);
} }
export async function connectHA(opts: { mqttHost: string; mqttPort: number; mqttUsername: string; mqttPassword: string; }): Promise<{ ha: IMqttClient; retainedMessages: RetainedMessage[] }> { export async function connectHA(opts: {
mqttHost: string;
mqttPort: number;
mqttUsername: string;
mqttPassword: string;
}): Promise<{ ha: IMqttClient; retainedMessages: RetainedMessage[] }> {
const url = `mqtt://${opts.mqttHost}`; const url = `mqtt://${opts.mqttHost}`;
const ha = await makeClient(url, opts.mqttPort, opts.mqttUsername, opts.mqttPassword, false); const ha = await makeClient(
const retainedMessages = await waitForHARetainedMessages(ha) url,
opts.mqttPort,
opts.mqttUsername,
opts.mqttPassword,
false,
);
const retainedMessages = await waitForHARetainedMessages(ha);
return { ha, retainedMessages }; return { ha, retainedMessages };
} }
function makeClient(url: string, port: number, username: string, password: string, demo: boolean): Promise<IMqttClient> { function makeClient(
url: string,
port: number,
username: string,
password: string,
demo: boolean,
): Promise<IMqttClient> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const client = demo ? new DemoFimpMqttClient() : new RealMqttClient(); const client = demo ? new DemoFimpMqttClient() : new RealMqttClient();
client.connect(url, { port, username, password, protocolVersion: 4 }); client.connect(url, { port, username, password, protocolVersion: 4 });
client.once("connect", () => resolve(client)); client.once('connect', () => resolve(client));
client.once("error", reject); client.once('error', reject);
}); });
} }
@ -28,14 +50,18 @@ export type RetainedMessage = { topic: string; message: string };
async function waitForHARetainedMessages( async function waitForHARetainedMessages(
client: IMqttClient, client: IMqttClient,
timeoutMs = 3000 timeoutMs = 3000,
): Promise<RetainedMessage[]> { ): Promise<RetainedMessage[]> {
const topicPattern = /^homeassistant\/device\/futurehome.*$/; const topicPattern = /^homeassistant\/device\/futurehome.*$/;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const retainedMessages: RetainedMessage[] = []; const retainedMessages: RetainedMessage[] = [];
const messageHandler = (topic: string, message: Buffer, packet: { retain?: boolean }) => { const messageHandler = (
topic: string,
message: Buffer,
packet: { retain?: boolean },
) => {
if (packet.retain && topicPattern.test(topic)) { if (packet.retain && topicPattern.test(topic)) {
retainedMessages.push({ topic, message: message.toString() }); retainedMessages.push({ topic, message: message.toString() });
} }

View File

@ -1,6 +1,6 @@
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from 'uuid';
import { log } from "../logger"; import { log } from '../logger';
import { IMqttClient } from "../mqtt/interface"; import { IMqttClient } from '../mqtt/interface';
let fimp: IMqttClient | undefined = undefined; let fimp: IMqttClient | undefined = undefined;
@ -21,7 +21,21 @@ export type FimpResponse = {
ver?: any; ver?: any;
}; };
type FimpValueType = 'string' | 'int' | 'float' | 'bool' | 'null' | 'str_array' | 'int_array' | 'float_array' | 'str_map' | 'int_map' | 'float_map' | 'bool_map' | 'object' | 'bin'; type FimpValueType =
| 'string'
| 'int'
| 'float'
| 'bool'
| 'null'
| 'str_array'
| 'int_array'
| 'float_array'
| 'str_map'
| 'int_map'
| 'float_map'
| 'bool_map'
| 'object'
| 'bin';
export async function sendFimpMsg({ export async function sendFimpMsg({
address, address,
@ -40,27 +54,27 @@ export async function sendFimpMsg({
}): Promise<FimpResponse> { }): Promise<FimpResponse> {
const uid = uuidv4(); const uid = uuidv4();
const topic = `pt:j1/mt:cmd${address}`; const topic = `pt:j1/mt:cmd${address}`;
const message = JSON.stringify( const message = JSON.stringify({
{ corid: null,
corid: null, ctime: new Date().toISOString(),
ctime: new Date().toISOString(), props: {},
props: {}, resp_to: 'pt:j1/mt:rsp/rt:app/rn:ha-futurehome/ad:addon',
resp_to: 'pt:j1/mt:rsp/rt:app/rn:ha-futurehome/ad:addon', serv: service,
serv: service, src: 'ha-futurehome',
src: 'ha-futurehome', tags: [],
tags: [], type: cmd,
'type': cmd, uid: uid,
uid: uid, val: val,
val: val, val_t: val_t,
val_t: val_t, ver: '1',
ver: '1', });
},
);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
fimp?.removeListener('message', onResponse); fimp?.removeListener('message', onResponse);
const error = new Error(`Timeout waiting for FIMP response (uid: ${uid}, service: ${service}, cmd: ${cmd})`); const error = new Error(
`Timeout waiting for FIMP response (uid: ${uid}, service: ${service}, cmd: ${cmd})`,
);
log.warn(error.message, error.stack); log.warn(error.message, error.stack);
reject(error); reject(error);
}, timeoutMs); }, timeoutMs);
@ -72,13 +86,17 @@ export async function sendFimpMsg({
if (msg.type === 'evt.error.report') { if (msg.type === 'evt.error.report') {
fimp?.removeListener('message', onResponse); fimp?.removeListener('message', onResponse);
const error = new Error(`Received FIMP response for message ${uid}: error (evt.error.report) (matched using uid)`); const error = new Error(
`Received FIMP response for message ${uid}: error (evt.error.report) (matched using uid)`,
);
log.warn(error.message, error.stack); log.warn(error.message, error.stack);
reject(error); reject(error);
return; return;
} }
log.debug(`Received FIMP response for message ${uid} (matched using uid).`); log.debug(
`Received FIMP response for message ${uid} (matched using uid).`,
);
clearTimeout(timeout); clearTimeout(timeout);
fimp?.removeListener('message', onResponse); fimp?.removeListener('message', onResponse);
@ -90,13 +108,17 @@ export async function sendFimpMsg({
if (msg.type === 'evt.error.report') { if (msg.type === 'evt.error.report') {
fimp?.removeListener('message', onResponse); fimp?.removeListener('message', onResponse);
const error = new Error(`Received FIMP response for message ${uid}: error (evt.error.report) (matched using topic)`); const error = new Error(
`Received FIMP response for message ${uid}: error (evt.error.report) (matched using topic)`,
);
log.warn(error.message, error.stack); log.warn(error.message, error.stack);
reject(error); reject(error);
return; return;
} }
log.debug(`Received FIMP response for message ${uid} (matched using topic).`); log.debug(
`Received FIMP response for message ${uid} (matched using topic).`,
);
clearTimeout(timeout); clearTimeout(timeout);
fimp?.removeListener('message', onResponse); fimp?.removeListener('message', onResponse);
@ -107,13 +129,23 @@ export async function sendFimpMsg({
const hasValidType = msg.type != null && msg.type.startsWith('evt.'); const hasValidType = msg.type != null && msg.type.startsWith('evt.');
const reqCmdParts = cmd.split('.'); const reqCmdParts = cmd.split('.');
const resCmdParts = msg.type?.split('.') ?? []; const resCmdParts = msg.type?.split('.') ?? [];
const hasThreeParts = resCmdParts.length === 3 && reqCmdParts.length === 3; const hasThreeParts =
resCmdParts.length === 3 && reqCmdParts.length === 3;
const middlePartMatches = resCmdParts[1] === reqCmdParts[1]; const middlePartMatches = resCmdParts[1] === reqCmdParts[1];
const endsWithLastPart = cmd.endsWith(resCmdParts.at(-1)!); const endsWithLastPart = cmd.endsWith(resCmdParts.at(-1)!);
const reqEndsWithSetAndResEndsWithReport = reqCmdParts[2] === 'set' && resCmdParts[2] === 'report' const reqEndsWithSetAndResEndsWithReport =
reqCmdParts[2] === 'set' && resCmdParts[2] === 'report';
const sameService = msg.serv === service; const sameService = msg.serv === service;
if (hasValidType && hasThreeParts && middlePartMatches && (endsWithLastPart || reqEndsWithSetAndResEndsWithReport) && sameService) { if (
log.debug(`Received FIMP response for message ${uid} (matched using event name).`); hasValidType &&
hasThreeParts &&
middlePartMatches &&
(endsWithLastPart || reqEndsWithSetAndResEndsWithReport) &&
sameService
) {
log.debug(
`Received FIMP response for message ${uid} (matched using event name).`,
);
clearTimeout(timeout); clearTimeout(timeout);
fimp?.removeListener('message', onResponse); fimp?.removeListener('message', onResponse);

View File

@ -1,10 +1,12 @@
/// Returns the adapter address of the device associated with the given service address. /// Returns the adapter address of the device associated with the given service address.
/// The service address may belong to any service on the device. /// The service address may belong to any service on the device.
export function adapterAddressFromServiceAddress(serviceAddress: string): string { export function adapterAddressFromServiceAddress(
serviceAddress: string,
): string {
const parts = serviceAddress.split('/'); const parts = serviceAddress.split('/');
if (parts.length < 4) { if (parts.length < 4) {
throw new Error("Invalid address format"); throw new Error('Invalid address format');
} }
const adapterPart = parts[2]; // e.g., "rn:zigbee" const adapterPart = parts[2]; // e.g., "rn:zigbee"
@ -16,11 +18,13 @@ export function adapterAddressFromServiceAddress(serviceAddress: string): string
/// Returns the adapter service name of the device associated with the given service address. /// Returns the adapter service name of the device associated with the given service address.
/// The service address may belong to any service on the device. /// The service address may belong to any service on the device.
export function adapterServiceFromServiceAddress(serviceAddress: string): string { export function adapterServiceFromServiceAddress(
serviceAddress: string,
): string {
const parts = serviceAddress.split('/'); const parts = serviceAddress.split('/');
if (parts.length < 3) { if (parts.length < 3) {
throw new Error("Invalid address format"); throw new Error('Invalid address format');
} }
const adapterPart = parts[2]; // e.g., "rn:zigbee" const adapterPart = parts[2]; // e.g., "rn:zigbee"
@ -31,5 +35,4 @@ export function adapterServiceFromServiceAddress(serviceAddress: string): string
} }
return adapterName; return adapterName;
} }

View File

@ -1,27 +1,31 @@
import { log } from "../logger"; import { log } from '../logger';
import { sendFimpMsg } from "./fimp"; import { sendFimpMsg } from './fimp';
export type InclusionReport = { export type InclusionReport = {
address?: string | null; // Household device ID address?: string | null; // Household device ID
product_name?: string | null; // e.g. "SWITCH-ZR03-1" product_name?: string | null; // e.g. "SWITCH-ZR03-1"
product_hash?: string | null; // e.g. "zb - eWeLink - SWITCH-ZR03-1" product_hash?: string | null; // e.g. "zb - eWeLink - SWITCH-ZR03-1"
product_id?: string | null; // e.g. "SWITCH-ZR03-1" product_id?: string | null; // e.g. "SWITCH-ZR03-1"
manufacturer_id?: string | null; // e.g. "eWeLink" manufacturer_id?: string | null; // e.g. "eWeLink"
device_id?: string | null; // e.g. "b4:0e:cf:d1:bc:2a:00:00" device_id?: string | null; // e.g. "b4:0e:cf:d1:bc:2a:00:00"
hw_ver?: string | null; // e.g. "0" hw_ver?: string | null; // e.g. "0"
sw_ver?: string | null; // e.g. "0x0" sw_ver?: string | null; // e.g. "0x0"
comm_tech?: string | null; // e.g. "zigbee" comm_tech?: string | null; // e.g. "zigbee"
power_source?: string | null; // e.g. "battery" power_source?: string | null; // e.g. "battery"
services?: InclusionReportService[] | null; services?: InclusionReportService[] | null;
}; };
export type InclusionReportService = { export type InclusionReportService = {
name?: string | null; name?: string | null;
address?: string | null; address?: string | null;
props?: { [key: string]: any } | null props?: { [key: string]: any } | null;
}; };
export async function getInclusionReport(parameters: { adapterAddress: string; adapterService: string; deviceId: string }): Promise<InclusionReport | undefined> { export async function getInclusionReport(parameters: {
adapterAddress: string;
adapterService: string;
deviceId: string;
}): Promise<InclusionReport | undefined> {
try { try {
const inclusionReport = await sendFimpMsg({ const inclusionReport = await sendFimpMsg({
address: parameters.adapterAddress, address: parameters.adapterAddress,
@ -33,6 +37,9 @@ export async function getInclusionReport(parameters: { adapterAddress: string; a
return inclusionReport.val; return inclusionReport.val;
} catch (e) { } catch (e) {
log.error(`Failed getting inclusion report for adapterAddress: ${parameters.adapterAddress}, adapterService: ${parameters.adapterService}, deviceId: ${parameters.deviceId}`, e) log.error(
`Failed getting inclusion report for adapterAddress: ${parameters.adapterAddress}, adapterService: ${parameters.adapterService}, deviceId: ${parameters.deviceId}`,
e,
);
} }
} }

View File

@ -1,20 +1,34 @@
export type DeviceState = { export type DeviceState = {
id?: number | null; id?: number | null;
services?: DeviceStateService[] | null services?: DeviceStateService[] | null;
}; };
export type DeviceStateService = { export type DeviceStateService = {
addr?: string; addr?: string;
attributes?: Attribute[]; attributes?: Attribute[];
name?: string; name?: string;
} };
export type Attribute = { export type Attribute = {
name: string; name: string;
values: AttributeValue[]; values: AttributeValue[];
} };
export type AttributeValue = StringValue | IntValue | FloatValue | BoolValue | NullValue | StrArrayValue | IntArrayValue | FloatArrayValue | StrMapValue | IntMapValue | FloatMapValue | BoolMapValue | ObjectValue | BinValue; export type AttributeValue =
| StringValue
| IntValue
| FloatValue
| BoolValue
| NullValue
| StrArrayValue
| IntArrayValue
| FloatArrayValue
| StrMapValue
| IntMapValue
| FloatMapValue
| BoolMapValue
| ObjectValue
| BinValue;
export type Timestamp = string; export type Timestamp = string;
@ -22,49 +36,49 @@ export type StringValue = {
ts: Timestamp; ts: Timestamp;
val: string; val: string;
val_t: 'string'; val_t: 'string';
} };
export type IntValue = { export type IntValue = {
ts: Timestamp; ts: Timestamp;
val: number; val: number;
val_t: 'int'; val_t: 'int';
} };
export type FloatValue = { export type FloatValue = {
ts: Timestamp; ts: Timestamp;
val: number; val: number;
val_t: 'float'; val_t: 'float';
} };
export type BoolValue = { export type BoolValue = {
ts: Timestamp; ts: Timestamp;
val: boolean; val: boolean;
val_t: 'bool'; val_t: 'bool';
} };
export type NullValue = { export type NullValue = {
ts: Timestamp; ts: Timestamp;
val?: null; val?: null;
val_t: 'null'; val_t: 'null';
} };
export type StrArrayValue = { export type StrArrayValue = {
ts: Timestamp; ts: Timestamp;
val: string[]; val: string[];
val_t: 'str_array'; val_t: 'str_array';
} };
export type IntArrayValue = { export type IntArrayValue = {
ts: Timestamp; ts: Timestamp;
val: number[]; val: number[];
val_t: 'int_array'; val_t: 'int_array';
} };
export type FloatArrayValue = { export type FloatArrayValue = {
ts: Timestamp; ts: Timestamp;
val: number[]; val: number[];
val_t: 'float_array'; val_t: 'float_array';
} };
export type StrMapValue = { export type StrMapValue = {
ts: Timestamp; ts: Timestamp;
@ -72,7 +86,7 @@ export type StrMapValue = {
[key: string]: string; [key: string]: string;
}; };
val_t: 'str_map'; val_t: 'str_map';
} };
export type IntMapValue = { export type IntMapValue = {
ts: Timestamp; ts: Timestamp;
@ -80,7 +94,7 @@ export type IntMapValue = {
[key: string]: number; [key: string]: number;
}; };
val_t: 'int_map'; val_t: 'int_map';
} };
export type FloatMapValue = { export type FloatMapValue = {
ts: Timestamp; ts: Timestamp;
@ -88,7 +102,7 @@ export type FloatMapValue = {
[key: string]: number; [key: string]: number;
}; };
val_t: 'float_map'; val_t: 'float_map';
} };
export type BoolMapValue = { export type BoolMapValue = {
ts: Timestamp; ts: Timestamp;
@ -96,7 +110,7 @@ export type BoolMapValue = {
[key: string]: boolean; [key: string]: boolean;
}; };
val_t: 'bool_map'; val_t: 'bool_map';
} };
export type ObjectValue = { export type ObjectValue = {
ts: Timestamp; ts: Timestamp;
@ -104,10 +118,10 @@ export type ObjectValue = {
[key: string]: any; [key: string]: any;
}; };
val_t: 'object'; val_t: 'object';
} };
export type BinValue = { export type BinValue = {
ts: Timestamp; ts: Timestamp;
val: string; val: string;
val_t: 'bin'; val_t: 'bin';
} };

View File

@ -8,7 +8,20 @@ export type VinculumPd7Device = {
model?: string | null; model?: string | null;
// "Model alias", e.g. "TS0202" // "Model alias", e.g. "TS0202"
modelAlias?: string | null; modelAlias?: string | null;
functionality?: 'appliance' | 'climate' | 'energy' | 'ev_charger' | 'lighting' | 'media' | 'other' | 'power' | 'safety' | 'security' | 'shading' | string | null; functionality?:
| 'appliance'
| 'climate'
| 'energy'
| 'ev_charger'
| 'lighting'
| 'media'
| 'other'
| 'power'
| 'safety'
| 'security'
| 'shading'
| string
| null;
services?: Record<string, VinculumPd7Service> | null; services?: Record<string, VinculumPd7Service> | null;
type?: { type?: {
// User-defined device type (e.g. "sensor", "chargepoint", or "light") // User-defined device type (e.g. "sensor", "chargepoint", or "light")

View File

@ -1,5 +1,5 @@
import { IMqttClient } from "../mqtt/interface"; import { IMqttClient } from '../mqtt/interface';
import { CommandHandlers } from "./publish_device"; import { CommandHandlers } from './publish_device';
export let ha: IMqttClient | undefined = undefined; export let ha: IMqttClient | undefined = undefined;

View File

@ -274,7 +274,8 @@ export function haPublishDevice(parameters: {
}, },
origin: { origin: {
name: 'futurehome', name: 'futurehome',
support_url: 'https://github.com/adrianjagielak/home-assistant-futurehome', support_url:
'https://github.com/adrianjagielak/home-assistant-futurehome',
}, },
components: components, components: components,
state_topic: stateTopic, state_topic: stateTopic,
@ -283,7 +284,10 @@ export function haPublishDevice(parameters: {
}; };
log.debug(`Publishing HA device "${configTopic}"`); log.debug(`Publishing HA device "${configTopic}"`);
ha?.publish(configTopic, JSON.stringify(abbreviateHaMqttKeys(config)), { retain: true, qos: 2 }); ha?.publish(configTopic, JSON.stringify(abbreviateHaMqttKeys(config)), {
retain: true,
qos: 2,
});
return { commandHandlers: handlers }; return { commandHandlers: handlers };
} }

View File

@ -1,5 +1,5 @@
import { log } from "../logger"; import { log } from '../logger';
import { ha } from "./globals"; import { ha } from './globals';
/** /**
* Example raw FIMP availaility (from evt.network.all_nodes_report) input: * Example raw FIMP availaility (from evt.network.all_nodes_report) input:
@ -20,11 +20,15 @@ topic: homeassistant/device/futurehome_123456_1/availability
online online
``` ```
*/ */
export function haUpdateAvailability(parameters: { hubId: string, deviceAvailability: any }) { export function haUpdateAvailability(parameters: {
const availabilityTopic = `homeassistant/device/futurehome_${parameters.hubId}_${parameters.deviceAvailability.address?.toString()}/availability` hubId: string;
deviceAvailability: any;
}) {
const availabilityTopic = `homeassistant/device/futurehome_${parameters.hubId}_${parameters.deviceAvailability.address?.toString()}/availability`;
const availability = parameters.deviceAvailability?.status === "UP" ? "online" : "offline"; const availability =
parameters.deviceAvailability?.status === 'UP' ? 'online' : 'offline';
log.debug(`Publishing HA availability "${availabilityTopic}"`) log.debug(`Publishing HA availability "${availabilityTopic}"`);
ha?.publish(availabilityTopic, availability, { retain: true, qos: 2 }); ha?.publish(availabilityTopic, availability, { retain: true, qos: 2 });
} }

View File

@ -1,6 +1,6 @@
import { DeviceState } from "../fimp/state"; import { DeviceState } from '../fimp/state';
import { log } from "../logger"; import { log } from '../logger';
import { ha } from "./globals"; import { ha } from './globals';
/** /**
* Example raw FIMP state input: * Example raw FIMP state input:
@ -77,8 +77,8 @@ topic: homeassistant/device/futurehome_123456_1/state
*/ */
const haStateCache: Record< const haStateCache: Record<
string, // state topic string, // state topic
Record<string, Record<string, any>> // payload (addr → { attr → value }) Record<string, Record<string, any>> // payload (addr → { attr → value })
> = {}; > = {};
/** /**
@ -88,7 +88,10 @@ const haStateCache: Record<
* Example MQTT topic produced for hub 123456 and device id 1: * Example MQTT topic produced for hub 123456 and device id 1:
* homeassistant/device/futurehome_123456_1/state * homeassistant/device/futurehome_123456_1/state
*/ */
export function haUpdateState(parameters: { hubId: string; deviceState: DeviceState }) { export function haUpdateState(parameters: {
hubId: string;
deviceState: DeviceState;
}) {
const stateTopic = `homeassistant/device/futurehome_${parameters.hubId}_${parameters.deviceState.id?.toString()}/state`; const stateTopic = `homeassistant/device/futurehome_${parameters.hubId}_${parameters.deviceState.id?.toString()}/state`;
const haState: Record<string, Record<string, any>> = {}; const haState: Record<string, Record<string, any>> = {};
@ -126,9 +129,13 @@ export function haUpdateState(parameters: { hubId: string; deviceState: DeviceSt
* The prefix "pt:j1/mt:evt" is removed before matching so that the remainder * The prefix "pt:j1/mt:evt" is removed before matching so that the remainder
* exactly matches the address keys stored in the cached HA payloads. * exactly matches the address keys stored in the cached HA payloads.
*/ */
export function haUpdateStateSensorReport(parameters: { topic: string; value: any, attrName: string }) { export function haUpdateStateSensorReport(parameters: {
topic: string;
value: any;
attrName: string;
}) {
// Strip the FIMP envelope so we end up with "/rt:dev/…/ad:x_y" // Strip the FIMP envelope so we end up with "/rt:dev/…/ad:x_y"
const sensorAddr = parameters.topic.replace(/^pt:j1\/mt:evt/, ""); const sensorAddr = parameters.topic.replace(/^pt:j1\/mt:evt/, '');
for (const [stateTopic, payload] of Object.entries(haStateCache)) { for (const [stateTopic, payload] of Object.entries(haStateCache)) {
if (!payload[sensorAddr]) continue; if (!payload[sensorAddr]) continue;
@ -136,7 +143,9 @@ export function haUpdateStateSensorReport(parameters: { topic: string; value: an
// Update the reading inplace // Update the reading inplace
payload[sensorAddr][parameters.attrName] = parameters.value; payload[sensorAddr][parameters.attrName] = parameters.value;
log.debug(`Publishing updated sensor value for "${sensorAddr}" to "${stateTopic}"`); log.debug(
`Publishing updated sensor value for "${sensorAddr}" to "${stateTopic}"`,
);
ha?.publish(stateTopic, JSON.stringify(payload), { retain: true, qos: 2 }); ha?.publish(stateTopic, JSON.stringify(payload), { retain: true, qos: 2 });
haStateCache[stateTopic] = payload; haStateCache[stateTopic] = payload;

View File

@ -1,15 +1,15 @@
import { connectHub, connectHA, RetainedMessage } from "./client"; import { connectHub, connectHA, RetainedMessage } from './client';
import { log } from "./logger"; import { log } from './logger';
import { FimpResponse, sendFimpMsg, setFimp } from "./fimp/fimp"; import { FimpResponse, sendFimpMsg, setFimp } from './fimp/fimp';
import { haCommandHandlers, setHa, setHaCommandHandlers } from "./ha/globals"; import { haCommandHandlers, setHa, setHaCommandHandlers } from './ha/globals';
import { CommandHandlers, haPublishDevice } from "./ha/publish_device"; import { CommandHandlers, haPublishDevice } from './ha/publish_device';
import { haUpdateState, haUpdateStateSensorReport } from "./ha/update_state"; import { haUpdateState, haUpdateStateSensorReport } from './ha/update_state';
import { VinculumPd7Device } from "./fimp/vinculum_pd7_device"; import { VinculumPd7Device } from './fimp/vinculum_pd7_device';
import { haUpdateAvailability } from "./ha/update_availability"; import { haUpdateAvailability } from './ha/update_availability';
import { delay } from "./utils"; import { delay } from './utils';
(async () => { (async () => {
const hubIp = process.env.FH_HUB_IP || "futurehome-smarthub.local"; const hubIp = process.env.FH_HUB_IP || 'futurehome-smarthub.local';
const hubUsername = process.env.FH_USERNAME || ''; const hubUsername = process.env.FH_USERNAME || '';
const hubPassword = process.env.FH_PASSWORD || ''; const hubPassword = process.env.FH_PASSWORD || '';
const demoMode = (process.env.DEMO_MODE || '').toLowerCase().includes('true'); const demoMode = (process.env.DEMO_MODE || '').toLowerCase().includes('true');
@ -20,13 +20,20 @@ import { delay } from "./utils";
const mqttPassword = process.env.MQTT_PWD || ''; const mqttPassword = process.env.MQTT_PWD || '';
// 1) Connect to HA broker (for discovery + state + availability + commands) // 1) Connect to HA broker (for discovery + state + availability + commands)
log.info("Connecting to HA broker..."); log.info('Connecting to HA broker...');
const { ha, retainedMessages } = await connectHA({ mqttHost, mqttPort, mqttUsername, mqttPassword, }); const { ha, retainedMessages } = await connectHA({
mqttHost,
mqttPort,
mqttUsername,
mqttPassword,
});
setHa(ha); setHa(ha);
log.info("Connected to HA broker"); log.info('Connected to HA broker');
if (!demoMode && (!hubUsername || !hubPassword)) { if (!demoMode && (!hubUsername || !hubPassword)) {
log.info("Empty username or password in non-demo mode. Removing all Futurehome devices from Home Assistant..."); log.info(
'Empty username or password in non-demo mode. Removing all Futurehome devices from Home Assistant...',
);
const publishWithDelay = (messages: RetainedMessage[], index = 0) => { const publishWithDelay = (messages: RetainedMessage[], index = 0) => {
if (index >= messages.length) return; if (index >= messages.length) return;
@ -42,17 +49,22 @@ import { delay } from "./utils";
} }
// 2) Connect to Futurehome hub (FIMP traffic) // 2) Connect to Futurehome hub (FIMP traffic)
log.info("Connecting to Futurehome hub..."); log.info('Connecting to Futurehome hub...');
const fimp = await connectHub({ hubIp, username: hubUsername, password: hubPassword, demo: demoMode }); const fimp = await connectHub({
fimp.subscribe("#"); hubIp,
username: hubUsername,
password: hubPassword,
demo: demoMode,
});
fimp.subscribe('#');
setFimp(fimp); setFimp(fimp);
log.info("Connected to Futurehome hub"); log.info('Connected to Futurehome hub');
const house = await sendFimpMsg({ const house = await sendFimpMsg({
address: '/rt:app/rn:vinculum/ad:1', address: '/rt:app/rn:vinculum/ad:1',
service: 'vinculum', service: 'vinculum',
cmd: 'cmd.pd7.request', cmd: 'cmd.pd7.request',
val: { cmd: "get", component: null, param: { components: ['house'] } }, val: { cmd: 'get', component: null, param: { components: ['house'] } },
val_t: 'object', val_t: 'object',
timeoutMs: 30000, timeoutMs: 30000,
}); });
@ -62,16 +74,20 @@ import { delay } from "./utils";
address: '/rt:app/rn:vinculum/ad:1', address: '/rt:app/rn:vinculum/ad:1',
service: 'vinculum', service: 'vinculum',
cmd: 'cmd.pd7.request', cmd: 'cmd.pd7.request',
val: { cmd: "get", component: null, param: { components: ['device'] } }, val: { cmd: 'get', component: null, param: { components: ['device'] } },
val_t: 'object', val_t: 'object',
timeoutMs: 30000, timeoutMs: 30000,
}); });
const haConfig = retainedMessages.filter(msg => msg.topic.endsWith("/config")); const haConfig = retainedMessages.filter((msg) =>
msg.topic.endsWith('/config'),
);
const regex = new RegExp(`^homeassistant/device/futurehome_${hubId}_([a-zA-Z0-9]+)/config$`); const regex = new RegExp(
`^homeassistant/device/futurehome_${hubId}_([a-zA-Z0-9]+)/config$`,
);
for (const haDevice of haConfig) { for (const haDevice of haConfig) {
log.debug('Found existing HA device', haDevice.topic) log.debug('Found existing HA device', haDevice.topic);
const match = haDevice.topic.match(regex); const match = haDevice.topic.match(regex);
@ -80,15 +96,18 @@ import { delay } from "./utils";
const idNumber = Number(deviceId); const idNumber = Number(deviceId);
if (!isNaN(idNumber)) { if (!isNaN(idNumber)) {
const basicDeviceData: { services?: { [key: string]: any } } = devices.val.param.device.find((d: any) => d?.id === idNumber); const basicDeviceData: { services?: { [key: string]: any } } =
const firstServiceAddr = basicDeviceData?.services ? Object.values(basicDeviceData.services)[0]?.addr : undefined;; devices.val.param.device.find((d: any) => d?.id === idNumber);
const firstServiceAddr = basicDeviceData?.services
? Object.values(basicDeviceData.services)[0]?.addr
: undefined;
if (!basicDeviceData || !firstServiceAddr) { if (!basicDeviceData || !firstServiceAddr) {
log.debug('Device was removed, removing from HA.'); log.debug('Device was removed, removing from HA.');
ha?.publish(haDevice.topic, '', { retain: true, qos: 2 }); ha?.publish(haDevice.topic, '', { retain: true, qos: 2 });
await delay(50); await delay(50);
} }
} else if (deviceId.toLowerCase() === "hub") { } else if (deviceId.toLowerCase() === 'hub') {
// Hub admin tools, ignore // Hub admin tools, ignore
} else { } else {
log.debug('Invalid format, removing.'); log.debug('Invalid format, removing.');
@ -106,11 +125,15 @@ import { delay } from "./utils";
const commandHandlers: CommandHandlers = {}; const commandHandlers: CommandHandlers = {};
for (const device of devices.val.param.device) { for (const device of devices.val.param.device) {
try { try {
const vinculumDeviceData: VinculumPd7Device = device const vinculumDeviceData: VinculumPd7Device = device;
const deviceId = vinculumDeviceData.id.toString() const deviceId = vinculumDeviceData.id.toString();
const firstServiceAddr = vinculumDeviceData.services ? Object.values(vinculumDeviceData.services)[0]?.addr : undefined;; const firstServiceAddr = vinculumDeviceData.services
? Object.values(vinculumDeviceData.services)[0]?.addr
: undefined;
if (!firstServiceAddr) { continue; } if (!firstServiceAddr) {
continue;
}
// This is problematic when the adapter doesn't respond, so we are not getting the inclusion report for now. I'm leaving it here since we might want it in the future. // This is problematic when the adapter doesn't respond, so we are not getting the inclusion report for now. I'm leaving it here since we might want it in the future.
// // Get additional metadata like manufacutrer or sw/hw version directly from the adapter // // Get additional metadata like manufacutrer or sw/hw version directly from the adapter
@ -119,14 +142,27 @@ import { delay } from "./utils";
// const deviceInclusionReport = await getInclusionReport({ adapterAddress, adapterService, deviceId }); // const deviceInclusionReport = await getInclusionReport({ adapterAddress, adapterService, deviceId });
const deviceInclusionReport = undefined; const deviceInclusionReport = undefined;
const result = haPublishDevice({ hubId, vinculumDeviceData, deviceInclusionReport }); const result = haPublishDevice({
hubId,
vinculumDeviceData,
deviceInclusionReport,
});
await delay(50); await delay(50);
Object.assign(commandHandlers, result.commandHandlers); Object.assign(commandHandlers, result.commandHandlers);
if (!retainedMessages.some(msg => msg.topic === `homeassistant/device/futurehome_${hubId}_${deviceId}/availability`)) { if (
!retainedMessages.some(
(msg) =>
msg.topic ===
`homeassistant/device/futurehome_${hubId}_${deviceId}/availability`,
)
) {
// Set initial availability // Set initial availability
haUpdateAvailability({ hubId, deviceAvailability: { address: deviceId, status: 'UP' } }); haUpdateAvailability({
hubId,
deviceAvailability: { address: deviceId, status: 'UP' },
});
await delay(50); await delay(50);
} }
} catch (e) { } catch (e) {
@ -142,10 +178,12 @@ import { delay } from "./utils";
// todo // todo
// exposeSmarthubTools(); // exposeSmarthubTools();
fimp.on("message", async (topic, buf) => { fimp.on('message', async (topic, buf) => {
try { try {
const msg: FimpResponse = JSON.parse(buf.toString()); const msg: FimpResponse = JSON.parse(buf.toString());
log.debug(`Received FIMP message on topic "${topic}":\n${JSON.stringify(msg, null, 0)}`); log.debug(
`Received FIMP message on topic "${topic}":\n${JSON.stringify(msg, null, 0)}`,
);
switch (msg.type) { switch (msg.type) {
case 'evt.pd7.response': { case 'evt.pd7.response': {
@ -163,8 +201,12 @@ import { delay } from "./utils";
if (devices) { if (devices) {
const newDeviceIds = new Set(devices.map((d: any) => d?.id)); const newDeviceIds = new Set(devices.map((d: any) => d?.id));
const addedDeviceIds = [...newDeviceIds].filter(id => !knownDeviceIds.has(id)); const addedDeviceIds = [...newDeviceIds].filter(
const removedDeviceIds = [...knownDeviceIds].filter(id => !newDeviceIds.has(id)); (id) => !knownDeviceIds.has(id),
);
const removedDeviceIds = [...knownDeviceIds].filter(
(id) => !newDeviceIds.has(id),
);
log.info(`Added devices: ${addedDeviceIds}`); log.info(`Added devices: ${addedDeviceIds}`);
log.info(`Removed devices: ${removedDeviceIds}`); log.info(`Removed devices: ${removedDeviceIds}`);
@ -190,14 +232,19 @@ import { delay } from "./utils";
case 'evt.open.report': case 'evt.open.report':
case 'evt.lvl.report': case 'evt.lvl.report':
case 'evt.alarm.report': case 'evt.alarm.report':
case 'evt.binary.report': case 'evt.binary.report': {
{ haUpdateStateSensorReport({
haUpdateStateSensorReport({ topic, value: msg.val, attrName: msg.type.split('.')[1] }) topic,
break; value: msg.val,
} attrName: msg.type.split('.')[1],
});
break;
}
case 'evt.network.all_nodes_report': { case 'evt.network.all_nodes_report': {
const devicesAvailability = msg.val; const devicesAvailability = msg.val;
if (!devicesAvailability) { return; } if (!devicesAvailability) {
return;
}
for (const deviceAvailability of devicesAvailability) { for (const deviceAvailability of devicesAvailability) {
haUpdateAvailability({ hubId, deviceAvailability }); haUpdateAvailability({ hubId, deviceAvailability });
await delay(50); await delay(50);
@ -206,50 +253,59 @@ import { delay } from "./utils";
} }
} }
} catch (e) { } catch (e) {
log.warn("Bad FIMP JSON", e, topic, buf); log.warn('Bad FIMP JSON', e, topic, buf);
} }
}); });
const pollState = () => { const pollState = () => {
log.debug("Refreshing Vinculum state after 30 seconds..."); log.debug('Refreshing Vinculum state after 30 seconds...');
sendFimpMsg({ sendFimpMsg({
address: '/rt:app/rn:vinculum/ad:1', address: '/rt:app/rn:vinculum/ad:1',
service: 'vinculum', service: 'vinculum',
cmd: 'cmd.pd7.request', cmd: 'cmd.pd7.request',
val: { cmd: "get", component: null, param: { components: ['state'] } }, val: { cmd: 'get', component: null, param: { components: ['state'] } },
val_t: 'object', val_t: 'object',
timeoutMs: 30000, timeoutMs: 30000,
}).catch(e => log.warn("Failed to request state", e)); }).catch((e) => log.warn('Failed to request state', e));
}; };
// Request initial state // Request initial state
pollState(); pollState();
// Then poll every 30 seconds // Then poll every 30 seconds
if (!demoMode) { setInterval(pollState, 30 * 1000); } if (!demoMode) {
setInterval(pollState, 30 * 1000);
}
const pollDevices = () => { const pollDevices = () => {
log.debug("Refreshing Vinculum devices after 30 minutes..."); log.debug('Refreshing Vinculum devices after 30 minutes...');
sendFimpMsg({ sendFimpMsg({
address: '/rt:app/rn:vinculum/ad:1', address: '/rt:app/rn:vinculum/ad:1',
service: 'vinculum', service: 'vinculum',
cmd: 'cmd.pd7.request', cmd: 'cmd.pd7.request',
val: { cmd: "get", component: null, param: { components: ['device'] } }, val: { cmd: 'get', component: null, param: { components: ['device'] } },
val_t: 'object', val_t: 'object',
timeoutMs: 30000, timeoutMs: 30000,
}).catch(e => log.warn("Failed to request state", e)); }).catch((e) => log.warn('Failed to request state', e));
}; };
// Poll devices every 30 minutes (1800000 ms) // Poll devices every 30 minutes (1800000 ms)
if (!demoMode) { setInterval(pollDevices, 30 * 60 * 1000); } if (!demoMode) {
setInterval(pollDevices, 30 * 60 * 1000);
}
ha.on('message', (topic, buf) => { ha.on('message', (topic, buf) => {
// Handle Home Assistant command messages // Handle Home Assistant command messages
const handler = haCommandHandlers?.[topic]; const handler = haCommandHandlers?.[topic];
if (handler) { if (handler) {
log.debug(`Handling Home Assistant command topic: ${topic}, payload: ${buf.toString()}`); log.debug(
`Handling Home Assistant command topic: ${topic}, payload: ${buf.toString()}`,
);
handler(buf.toString()).catch((e) => { handler(buf.toString()).catch((e) => {
log.warn(`Failed executing handler for topic: ${topic}, payload: ${buf.toString()}`, e); log.warn(
`Failed executing handler for topic: ${topic}, payload: ${buf.toString()}`,
e,
);
}); });
} }
}) });
})(); })();

View File

@ -11,26 +11,37 @@ export class DemoFimpMqttClient implements IMqttClient {
private onceConnectHandlers: (() => void)[] = []; private onceConnectHandlers: (() => void)[] = [];
private onceErrorHandlers: OnErrorCallback[] = []; private onceErrorHandlers: OnErrorCallback[] = [];
connect(_url: string, _options: { connect(
port: number; _url: string,
username: string; _options: {
password: string; port: number;
protocolVersion: 4; username: string;
}): void { password: string;
protocolVersion: 4;
},
): void {
setTimeout(() => { setTimeout(() => {
this.onceConnectHandlers.forEach((h) => h()); this.onceConnectHandlers.forEach((h) => h());
}, 100); }, 100);
} }
subscribe(topicObject: string, opts?: { qos: 0 | 1 | 2 }, callback?: (err: Error | null) => void): void; subscribe(
subscribe(_topic: string, _opts?: any, _callback?: any): void { } topicObject: string,
opts?: { qos: 0 | 1 | 2 },
callback?: (err: Error | null) => void,
): void;
subscribe(_topic: string, _opts?: any, _callback?: any): void {}
publish(topic: string, value: string, _options: { publish(
retain?: boolean; topic: string,
qos: 0 | 1 | 2; value: string,
}): void { _options: {
retain?: boolean;
qos: 0 | 1 | 2;
},
): void {
setTimeout(() => { setTimeout(() => {
const msg = JSON.parse(value) const msg = JSON.parse(value);
const sendResponse = (response: FimpResponse) => { const sendResponse = (response: FimpResponse) => {
response.corid = response.corid ?? msg.uid; response.corid = response.corid ?? msg.uid;
@ -38,14 +49,35 @@ export class DemoFimpMqttClient implements IMqttClient {
for (const handler of this.messageHandlers) { for (const handler of this.messageHandlers) {
handler(topic, buffer, { retain: false } as any); handler(topic, buffer, { retain: false } as any);
} }
} };
if (msg.serv == 'vinculum' && msg.type == 'cmd.pd7.request' && msg.val?.param?.components?.includes('house')) { if (
sendResponse({ type: 'evt.pd7.response', val: { param: { house: { hubId: '000000004c38b232' } } } }) msg.serv == 'vinculum' &&
} else if (msg.serv == 'vinculum' && msg.type == 'cmd.pd7.request' && msg.val?.param?.components?.includes('device')) { msg.type == 'cmd.pd7.request' &&
sendResponse({ type: 'evt.pd7.response', val: { param: { device: demo_data__device } } }); msg.val?.param?.components?.includes('house')
} else if (msg.serv == 'vinculum' && msg.type == 'cmd.pd7.request' && msg.val?.param?.components?.includes('state')) { ) {
sendResponse({ type: 'evt.pd7.response', val: { param: { state: { devices: demo_data__state } } } }) sendResponse({
type: 'evt.pd7.response',
val: { param: { house: { hubId: '000000004c38b232' } } },
});
} else if (
msg.serv == 'vinculum' &&
msg.type == 'cmd.pd7.request' &&
msg.val?.param?.components?.includes('device')
) {
sendResponse({
type: 'evt.pd7.response',
val: { param: { device: demo_data__device } },
});
} else if (
msg.serv == 'vinculum' &&
msg.type == 'cmd.pd7.request' &&
msg.val?.param?.components?.includes('state')
) {
sendResponse({
type: 'evt.pd7.response',
val: { param: { state: { devices: demo_data__state } } },
});
} }
}, 100); }, 100);
} }

File diff suppressed because it is too large Load Diff

View File

@ -474,9 +474,7 @@
{ {
"ts": "2022-02-19 16:19:10 +0100", "ts": "2022-02-19 16:19:10 +0100",
"val": { "val": {
"members": [ "members": ["3_1"]
"3_1"
]
}, },
"val_t": "object" "val_t": "object"
} }
@ -972,9 +970,7 @@
{ {
"ts": "2023-01-05 00:31:29 +0100", "ts": "2023-01-05 00:31:29 +0100",
"val": { "val": {
"members": [ "members": ["3_1"]
"3_1"
]
}, },
"val_t": "object" "val_t": "object"
} }

View File

@ -1,20 +1,31 @@
import { IPublishPacket, OnErrorCallback, OnMessageCallback } from "mqtt/*"; import { IPublishPacket, OnErrorCallback, OnMessageCallback } from 'mqtt/*';
export interface IMqttClient { export interface IMqttClient {
connect(url: string, options: { connect(
port: number; url: string,
username: string; options: {
password: string; port: number;
protocolVersion: 4; username: string;
}): void; password: string;
protocolVersion: 4;
},
): void;
subscribe(topic: string): void; subscribe(topic: string): void;
subscribe(topicObject: string, opts?: { qos: 0 | 1 | 2 }, callback?: (err: Error | null) => void): void; subscribe(
topicObject: string,
opts?: { qos: 0 | 1 | 2 },
callback?: (err: Error | null) => void,
): void;
publish(topic: string, value: string, options: { publish(
retain?: boolean; topic: string,
qos: 0 | 1 | 2; value: string,
}): void; options: {
retain?: boolean;
qos: 0 | 1 | 2;
},
): void;
on(event: 'message', handler: OnMessageCallback): void; on(event: 'message', handler: OnMessageCallback): void;
on(event: 'error', handler: OnErrorCallback): void; on(event: 'error', handler: OnErrorCallback): void;
@ -22,7 +33,10 @@ export interface IMqttClient {
off(event: 'message', handler: OnMessageCallback): void; off(event: 'message', handler: OnMessageCallback): void;
off(event: 'error', handler: OnErrorCallback): void; off(event: 'error', handler: OnErrorCallback): void;
removeListener(event: 'message', handler: (topic: string, payload: Buffer, packet: IPublishPacket) => void): void; removeListener(
event: 'message',
handler: (topic: string, payload: Buffer, packet: IPublishPacket) => void,
): void;
once(event: 'connect', handler: () => void): void; once(event: 'connect', handler: () => void): void;
once(event: 'error', handler: OnErrorCallback): void; once(event: 'error', handler: OnErrorCallback): void;

View File

@ -8,16 +8,23 @@ export class RealMqttClient implements IMqttClient {
this.client = {} as MqttClient; // gets initialized in connect() this.client = {} as MqttClient; // gets initialized in connect()
} }
connect(url: string, options: { connect(
port: number; url: string,
username: string; options: {
password: string; port: number;
protocolVersion: 4; username: string;
}): void { password: string;
protocolVersion: 4;
},
): void {
this.client = connect(url, options); this.client = connect(url, options);
} }
subscribe(topicObject: string, opts?: { qos: 0 | 1 | 2 }, callback?: (err: Error | null) => void): void; subscribe(
topicObject: string,
opts?: { qos: 0 | 1 | 2 },
callback?: (err: Error | null) => void,
): void;
subscribe(topic: string, opts?: any, callback?: any): void { subscribe(topic: string, opts?: any, callback?: any): void {
if (opts) { if (opts) {
this.client.subscribe(topic, opts, callback); this.client.subscribe(topic, opts, callback);
@ -26,10 +33,14 @@ export class RealMqttClient implements IMqttClient {
} }
} }
publish(topic: string, value: string, options: { publish(
retain?: boolean; topic: string,
qos: 0 | 1 | 2; value: string,
}): void { options: {
retain?: boolean;
qos: 0 | 1 | 2;
},
): void {
this.client.publish(topic, value, options); this.client.publish(topic, value, options);
} }

View File

@ -7,14 +7,17 @@
// command_topic → cmd.lvl.set // command_topic → cmd.lvl.set
// ─────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────
import { sendFimpMsg } from "../fimp/fimp"; import { sendFimpMsg } from '../fimp/fimp';
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function basic__components( export function basic__components(
topicPrefix: string, topicPrefix: string,
_device: VinculumPd7Device, _device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
// MQTT topic that HA will publish commands to // MQTT topic that HA will publish commands to
const cmdTopic = `${topicPrefix}${svc.addr}/command`; const cmdTopic = `${topicPrefix}${svc.addr}/command`;
@ -45,9 +48,9 @@ export function basic__components(
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "basic", service: 'basic',
cmd: "cmd.lvl.set", cmd: 'cmd.lvl.set',
val_t: "int", val_t: 'int',
val: lvl, val: lvl,
}); });
}, },

View File

@ -1,11 +1,14 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { HaMqttComponent } from "../ha/mqtt_components/_component"; VinculumPd7Device,
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { HaMqttComponent } from '../ha/mqtt_components/_component';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function battery__components( export function battery__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const components: Record<string, HaMqttComponent> = {}; const components: Record<string, HaMqttComponent> = {};

View File

@ -1,12 +1,18 @@
import { sendFimpMsg } from "../fimp/fimp"; import { sendFimpMsg } from '../fimp/fimp';
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { LightComponent } from "../ha/mqtt_components/light"; VinculumPd7Device,
import { ServiceComponentsCreationResult, CommandHandlers } from "../ha/publish_device"; VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { LightComponent } from '../ha/mqtt_components/light';
import {
ServiceComponentsCreationResult,
CommandHandlers,
} from '../ha/publish_device';
export function color_ctrl__components( export function color_ctrl__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const supComponents: string[] = svc.props?.sup_components ?? []; const supComponents: string[] = svc.props?.sup_components ?? [];
@ -15,7 +21,10 @@ export function color_ctrl__components(
} }
// Check if we have RGB support (minimum requirement for a useful light) // Check if we have RGB support (minimum requirement for a useful light)
const hasRgb = supComponents.includes('red') && supComponents.includes('green') && supComponents.includes('blue'); const hasRgb =
supComponents.includes('red') &&
supComponents.includes('green') &&
supComponents.includes('blue');
if (!hasRgb) { if (!hasRgb) {
return undefined; // No RGB support, skip this service return undefined; // No RGB support, skip this service
} }
@ -76,7 +85,10 @@ export function color_ctrl__components(
lightComponent.color_temp_value_template = `{{ (1000000 / value_json['${svc.addr}'].temp) | round(0) }}`; // Convert Kelvin to mireds lightComponent.color_temp_value_template = `{{ (1000000 / value_json['${svc.addr}'].temp) | round(0) }}`; // Convert Kelvin to mireds
lightComponent.min_mireds = 153; // ~6500K lightComponent.min_mireds = 153; // ~6500K
lightComponent.max_mireds = 370; // ~2700K lightComponent.max_mireds = 370; // ~2700K
} else if (supComponents.includes('warm_w') && supComponents.includes('cold_w')) { } else if (
supComponents.includes('warm_w') &&
supComponents.includes('cold_w')
) {
// Z-Wave style - warm/cold white mix // Z-Wave style - warm/cold white mix
lightComponent.color_temp_command_topic = colorTempCommandTopic; lightComponent.color_temp_command_topic = colorTempCommandTopic;
lightComponent.color_temp_state_topic = stateTopic; lightComponent.color_temp_state_topic = stateTopic;
@ -101,23 +113,23 @@ export function color_ctrl__components(
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "color_ctrl", service: 'color_ctrl',
cmd: "cmd.color.set", cmd: 'cmd.color.set',
val_t: "int_map", val_t: 'int_map',
val: colorMap, val: colorMap,
}); });
} else if (payload === 'OFF') { } else if (payload === 'OFF') {
// Turn off (all components to 0) // Turn off (all components to 0)
const colorMap: Record<string, number> = {}; const colorMap: Record<string, number> = {};
supComponents.forEach(component => { supComponents.forEach((component) => {
colorMap[component] = 0; colorMap[component] = 0;
}); });
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "color_ctrl", service: 'color_ctrl',
cmd: "cmd.color.set", cmd: 'cmd.color.set',
val_t: "int_map", val_t: 'int_map',
val: colorMap, val: colorMap,
}); });
} }
@ -132,8 +144,17 @@ export function color_ctrl__components(
const green = parseInt(parts[1], 10); const green = parseInt(parts[1], 10);
const blue = parseInt(parts[2], 10); const blue = parseInt(parts[2], 10);
if (Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue)) return; if (Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue))
if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) return; return;
if (
red < 0 ||
red > 255 ||
green < 0 ||
green > 255 ||
blue < 0 ||
blue > 255
)
return;
const colorMap: Record<string, number> = { const colorMap: Record<string, number> = {
red, red,
@ -143,9 +164,9 @@ export function color_ctrl__components(
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "color_ctrl", service: 'color_ctrl',
cmd: "cmd.color.set", cmd: 'cmd.color.set',
val_t: "int_map", val_t: 'int_map',
val: colorMap, val: colorMap,
}); });
}, },
@ -167,12 +188,15 @@ export function color_ctrl__components(
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "color_ctrl", service: 'color_ctrl',
cmd: "cmd.color.set", cmd: 'cmd.color.set',
val_t: "int_map", val_t: 'int_map',
val: colorMap, val: colorMap,
}); });
} else if (supComponents.includes('warm_w') && supComponents.includes('cold_w')) { } else if (
supComponents.includes('warm_w') &&
supComponents.includes('cold_w')
) {
// Z-Wave style - convert mireds to warm/cold white mix // Z-Wave style - convert mireds to warm/cold white mix
// Linear interpolation between cold (153 mireds) and warm (370 mireds) // Linear interpolation between cold (153 mireds) and warm (370 mireds)
const warmRatio = (mireds - 153) / (370 - 153); const warmRatio = (mireds - 153) / (370 - 153);
@ -189,9 +213,9 @@ export function color_ctrl__components(
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "color_ctrl", service: 'color_ctrl',
cmd: "cmd.color.set", cmd: 'cmd.color.set',
val_t: "int_map", val_t: 'int_map',
val: colorMap, val: colorMap,
}); });
} }

View File

@ -1,11 +1,14 @@
import { sendFimpMsg } from "../fimp/fimp"; import { sendFimpMsg } from '../fimp/fimp';
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function fan_ctrl__components( export function fan_ctrl__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const supModes: string[] = svc.props?.sup_modes ?? []; const supModes: string[] = svc.props?.sup_modes ?? [];
@ -38,10 +41,10 @@ export function fan_ctrl__components(
if (supModes.includes(mode) || mode === 'off') { if (supModes.includes(mode) || mode === 'off') {
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "fan_ctrl", service: 'fan_ctrl',
cmd: "cmd.mode.set", cmd: 'cmd.mode.set',
val: mode, val: mode,
val_t: "string", val_t: 'string',
}); });
} }
} else { } else {
@ -49,10 +52,10 @@ export function fan_ctrl__components(
if (supModes.includes(payload)) { if (supModes.includes(payload)) {
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "fan_ctrl", service: 'fan_ctrl',
cmd: "cmd.mode.set", cmd: 'cmd.mode.set',
val: payload, val: payload,
val_t: "string", val_t: 'string',
}); });
} }
} }

View File

@ -1,11 +1,14 @@
import { sendFimpMsg } from "../fimp/fimp"; import { sendFimpMsg } from '../fimp/fimp';
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function out_bin_switch__components( export function out_bin_switch__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const commandTopic = `${topicPrefix}${svc.addr}/command`; const commandTopic = `${topicPrefix}${svc.addr}/command`;
@ -29,6 +32,6 @@ export function out_bin_switch__components(
val_t: 'bool', val_t: 'bool',
}); });
}, },
} },
}; };
} }

View File

@ -1,11 +1,14 @@
import { sendFimpMsg } from "../fimp/fimp"; import { sendFimpMsg } from '../fimp/fimp';
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function out_lvl_switch__components( export function out_lvl_switch__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const commandTopic = `${topicPrefix}${svc.addr}/command`; const commandTopic = `${topicPrefix}${svc.addr}/command`;
@ -35,10 +38,10 @@ export function out_lvl_switch__components(
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "out_lvl_switch", service: 'out_lvl_switch',
cmd: "cmd.lvl.set", cmd: 'cmd.lvl.set',
val: lvl, val: lvl,
val_t: "int", val_t: 'int',
}); });
}, },
}, },

View File

@ -8,13 +8,16 @@
// <topicPrefix><addr>/scene/command → cmd.scene.set // <topicPrefix><addr>/scene/command → cmd.scene.set
// ───────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────
import { sendFimpMsg } from "../fimp/fimp"; import { sendFimpMsg } from '../fimp/fimp';
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { HaMqttComponent } from "../ha/mqtt_components/_component"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { HaMqttComponent } from '../ha/mqtt_components/_component';
import { import {
CommandHandlers, CommandHandlers,
ServiceComponentsCreationResult, ServiceComponentsCreationResult,
} from "../ha/publish_device"; } from '../ha/publish_device';
/** /**
* Creates MQTT components for a single *scene_ctrl* service. * Creates MQTT components for a single *scene_ctrl* service.
@ -22,33 +25,33 @@ import {
export function scene_ctrl__components( export function scene_ctrl__components(
topicPrefix: string, topicPrefix: string,
_device: VinculumPd7Device, _device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const components: Record<string, HaMqttComponent> = {}; const components: Record<string, HaMqttComponent> = {};
const handlers: CommandHandlers = {}; const handlers: CommandHandlers = {};
// ───────────── read-only entities ───────────── // ───────────── read-only entities ─────────────
if (svc.intf?.includes("evt.scene.report")) { if (svc.intf?.includes('evt.scene.report')) {
components[`${svc.addr}_scene`] = { components[`${svc.addr}_scene`] = {
unique_id: `${svc.addr}_scene`, unique_id: `${svc.addr}_scene`,
platform: 'sensor', platform: 'sensor',
unit_of_measurement: "", unit_of_measurement: '',
value_template: `{{ value_json['${svc.addr}'].scene }}`, value_template: `{{ value_json['${svc.addr}'].scene }}`,
}; };
} }
if (svc.intf?.includes("evt.lvl.report")) { if (svc.intf?.includes('evt.lvl.report')) {
components[`${svc.addr}_lvl`] = { components[`${svc.addr}_lvl`] = {
unique_id: `${svc.addr}_lvl`, unique_id: `${svc.addr}_lvl`,
platform: 'sensor', platform: 'sensor',
unit_of_measurement: "", unit_of_measurement: '',
value_template: `{{ value_json['${svc.addr}'].lvl }}`, value_template: `{{ value_json['${svc.addr}'].lvl }}`,
}; };
} }
// ───────────── writeable “select” (scene activator) ───────────── // ───────────── writeable “select” (scene activator) ─────────────
const supScenes: string[] = svc.props?.sup_scenes ?? []; const supScenes: string[] = svc.props?.sup_scenes ?? [];
if (svc.intf?.includes("cmd.scene.set") && supScenes.length) { if (svc.intf?.includes('cmd.scene.set') && supScenes.length) {
const commandTopic = `${topicPrefix}${svc.addr}/scene/command`; const commandTopic = `${topicPrefix}${svc.addr}/scene/command`;
components[`${svc.addr}_select`] = { components[`${svc.addr}_select`] = {
@ -65,9 +68,9 @@ export function scene_ctrl__components(
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "scene_ctrl", service: 'scene_ctrl',
cmd: "cmd.scene.set", cmd: 'cmd.scene.set',
val_t: "string", val_t: 'string',
val: payload, val: payload,
}); });
}; };

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_accelx__components( export function sensor_accelx__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'm/s2'; const unit = svc.props?.sup_units?.[0] ?? 'm/s2';
@ -18,6 +21,6 @@ export function sensor_accelx__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_accely__components( export function sensor_accely__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'm/s2'; const unit = svc.props?.sup_units?.[0] ?? 'm/s2';
@ -18,6 +21,6 @@ export function sensor_accely__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_accelz__components( export function sensor_accelz__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'm/s2'; const unit = svc.props?.sup_units?.[0] ?? 'm/s2';
@ -18,6 +21,6 @@ export function sensor_accelz__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_airflow__components( export function sensor_airflow__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'm3/h'; const unit = svc.props?.sup_units?.[0] ?? 'm3/h';
@ -18,6 +21,6 @@ export function sensor_airflow__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_airq__components( export function sensor_airq__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'aqi'; const device_class = 'aqi';
const unit = svc.props?.sup_units?.[0] ?? 'pm25'; const unit = svc.props?.sup_units?.[0] ?? 'pm25';
@ -18,6 +21,6 @@ export function sensor_airq__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_anglepos__components( export function sensor_anglepos__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? '%'; const unit = svc.props?.sup_units?.[0] ?? '%';
@ -18,6 +21,6 @@ export function sensor_anglepos__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_atmo__components( export function sensor_atmo__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'atmospheric_pressure'; const device_class = 'atmospheric_pressure';
const unit = svc.props?.sup_units?.[0] ?? 'kPa'; const unit = svc.props?.sup_units?.[0] ?? 'kPa';
@ -18,6 +21,6 @@ export function sensor_atmo__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_baro__components( export function sensor_baro__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'atmospheric_pressure'; const device_class = 'atmospheric_pressure';
const unit = svc.props?.sup_units?.[0] ?? 'kPa'; const unit = svc.props?.sup_units?.[0] ?? 'kPa';
@ -18,6 +21,6 @@ export function sensor_baro__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_co__components( export function sensor_co__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'carbon_monoxide'; const device_class = 'carbon_monoxide';
const unit = svc.props?.sup_units?.[0] ?? 'mol/m3'; const unit = svc.props?.sup_units?.[0] ?? 'mol/m3';
@ -18,6 +21,6 @@ export function sensor_co__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_co2__components( export function sensor_co2__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'carbon_dioxide'; const device_class = 'carbon_dioxide';
const unit = svc.props?.sup_units?.[0] ?? 'ppm'; const unit = svc.props?.sup_units?.[0] ?? 'ppm';
@ -18,6 +21,6 @@ export function sensor_co2__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,12 +1,15 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_contact__components( export function sensor_contact__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'opening' const device_class = 'opening';
return { return {
components: { components: {

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_current__components( export function sensor_current__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'current'; const device_class = 'current';
const unit = svc.props?.sup_units?.[0] ?? 'A'; const unit = svc.props?.sup_units?.[0] ?? 'A';
@ -18,6 +21,6 @@ export function sensor_current__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,13 +1,16 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_dew__components( export function sensor_dew__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'temperature' const device_class = 'temperature';
let unit = svc.props?.sup_units?.[0] ?? "°C"; let unit = svc.props?.sup_units?.[0] ?? '°C';
if (unit === 'C') unit = '°C'; if (unit === 'C') unit = '°C';
if (unit === 'F') unit = '°F'; if (unit === 'F') unit = '°F';

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_direct__components( export function sensor_direct__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'wind_direction'; const device_class = 'wind_direction';
const unit = svc.props?.sup_units?.[0] ?? '°'; const unit = svc.props?.sup_units?.[0] ?? '°';
@ -18,6 +21,6 @@ export function sensor_direct__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_distance__components( export function sensor_distance__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'distance'; const device_class = 'distance';
const unit = svc.props?.sup_units?.[0] ?? 'm'; const unit = svc.props?.sup_units?.[0] ?? 'm';
@ -18,6 +21,6 @@ export function sensor_distance__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_elresist__components( export function sensor_elresist__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'ohm/m'; const unit = svc.props?.sup_units?.[0] ?? 'ohm/m';
@ -18,6 +21,6 @@ export function sensor_elresist__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_freq__components( export function sensor_freq__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'frequency'; const device_class = 'frequency';
const unit = svc.props?.sup_units?.[0] ?? 'Hz'; const unit = svc.props?.sup_units?.[0] ?? 'Hz';
@ -18,6 +21,6 @@ export function sensor_freq__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_gp__components( export function sensor_gp__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? '%'; const unit = svc.props?.sup_units?.[0] ?? '%';
@ -18,6 +21,6 @@ export function sensor_gp__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,14 +1,17 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_gust__components( export function sensor_gust__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
let unit = svc.props?.sup_units?.[0] ?? 'km/h'; let unit = svc.props?.sup_units?.[0] ?? 'km/h';
if (unit === 'kph') unit = 'km/h' if (unit === 'kph') unit = 'km/h';
return { return {
components: { components: {
@ -19,6 +22,6 @@ export function sensor_gust__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_humid__components( export function sensor_humid__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'humidity'; const device_class = 'humidity';
const unit = svc.props?.sup_units?.[0] ?? '%'; const unit = svc.props?.sup_units?.[0] ?? '%';
@ -18,6 +21,6 @@ export function sensor_humid__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_lumin__components( export function sensor_lumin__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'illuminance'; const device_class = 'illuminance';
const unit = svc.props?.sup_units?.[0] ?? 'Lux'; const unit = svc.props?.sup_units?.[0] ?? 'Lux';
@ -18,6 +21,6 @@ export function sensor_lumin__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_moist__components( export function sensor_moist__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'moisture'; const device_class = 'moisture';
const unit = svc.props?.sup_units?.[0] ?? '%'; const unit = svc.props?.sup_units?.[0] ?? '%';
@ -18,6 +21,6 @@ export function sensor_moist__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_noise__components( export function sensor_noise__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'sound_pressure'; const device_class = 'sound_pressure';
const unit = svc.props?.sup_units?.[0] ?? 'dB'; const unit = svc.props?.sup_units?.[0] ?? 'dB';
@ -18,6 +21,6 @@ export function sensor_noise__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_power__components( export function sensor_power__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'power'; const device_class = 'power';
const unit = svc.props?.sup_units?.[0] ?? 'W'; const unit = svc.props?.sup_units?.[0] ?? 'W';
@ -18,6 +21,6 @@ export function sensor_power__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,12 +1,15 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_presence__components( export function sensor_presence__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'occupancy' const device_class = 'occupancy';
return { return {
components: { components: {

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_rain__components( export function sensor_rain__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'precipitation_intensity'; const device_class = 'precipitation_intensity';
const unit = svc.props?.sup_units?.[0] ?? 'mm/h'; const unit = svc.props?.sup_units?.[0] ?? 'mm/h';
@ -18,6 +21,6 @@ export function sensor_rain__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_rotation__components( export function sensor_rotation__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'rpm'; const unit = svc.props?.sup_units?.[0] ?? 'rpm';
@ -18,6 +21,6 @@ export function sensor_rotation__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_seismicint__components( export function sensor_seismicint__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'EMCRO'; const unit = svc.props?.sup_units?.[0] ?? 'EMCRO';
@ -18,6 +21,6 @@ export function sensor_seismicint__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_seismicmag__components( export function sensor_seismicmag__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'MB'; const unit = svc.props?.sup_units?.[0] ?? 'MB';
@ -18,6 +21,6 @@ export function sensor_seismicmag__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_solarrad__components( export function sensor_solarrad__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'W/m2'; const unit = svc.props?.sup_units?.[0] ?? 'W/m2';
@ -18,6 +21,6 @@ export function sensor_solarrad__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_tank__components( export function sensor_tank__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'volume_storage'; const device_class = 'volume_storage';
const unit = svc.props?.sup_units?.[0] ?? 'l'; const unit = svc.props?.sup_units?.[0] ?? 'l';
@ -18,6 +21,6 @@ export function sensor_tank__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,13 +1,16 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_temp__components( export function sensor_temp__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'temperature' const device_class = 'temperature';
let unit = svc.props?.sup_units?.[0] ?? "°C"; let unit = svc.props?.sup_units?.[0] ?? '°C';
if (unit === 'C') unit = '°C'; if (unit === 'C') unit = '°C';
if (unit === 'F') unit = '°F'; if (unit === 'F') unit = '°F';

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_tidelvl__components( export function sensor_tidelvl__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'm'; const unit = svc.props?.sup_units?.[0] ?? 'm';
@ -18,6 +21,6 @@ export function sensor_tidelvl__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_uv__components( export function sensor_uv__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'index'; const unit = svc.props?.sup_units?.[0] ?? 'index';
@ -18,6 +21,6 @@ export function sensor_uv__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_veloc__components( export function sensor_veloc__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = undefined; const device_class = undefined;
const unit = svc.props?.sup_units?.[0] ?? 'm/2'; const unit = svc.props?.sup_units?.[0] ?? 'm/2';
@ -18,6 +21,6 @@ export function sensor_veloc__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_voltage__components( export function sensor_voltage__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'voltage'; const device_class = 'voltage';
const unit = svc.props?.sup_units?.[0] ?? 'V'; const unit = svc.props?.sup_units?.[0] ?? 'V';
@ -18,6 +21,6 @@ export function sensor_voltage__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_watflow__components( export function sensor_watflow__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'volume_flow_rate'; const device_class = 'volume_flow_rate';
const unit = svc.props?.sup_units?.[0] ?? 'l/h'; const unit = svc.props?.sup_units?.[0] ?? 'l/h';
@ -18,6 +21,6 @@ export function sensor_watflow__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_watpressure__components( export function sensor_watpressure__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'pressure'; const device_class = 'pressure';
const unit = svc.props?.sup_units?.[0] ?? 'kPa'; const unit = svc.props?.sup_units?.[0] ?? 'kPa';
@ -18,6 +21,6 @@ export function sensor_watpressure__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,13 +1,16 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_wattemp__components( export function sensor_wattemp__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'temperature'; const device_class = 'temperature';
let unit = svc.props?.sup_units?.[0] ?? "°C"; let unit = svc.props?.sup_units?.[0] ?? '°C';
if (unit === 'C') unit = '°C'; if (unit === 'C') unit = '°C';
if (unit === 'F') unit = '°F'; if (unit === 'F') unit = '°F';

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_weight__components( export function sensor_weight__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'weight'; const device_class = 'weight';
const unit = svc.props?.sup_units?.[0] ?? 'kg'; const unit = svc.props?.sup_units?.[0] ?? 'kg';
@ -18,6 +21,6 @@ export function sensor_weight__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -1,10 +1,13 @@
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ServiceComponentsCreationResult } from "../ha/publish_device"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ServiceComponentsCreationResult } from '../ha/publish_device';
export function sensor_wind__components( export function sensor_wind__components(
topicPrefix: string, topicPrefix: string,
device: VinculumPd7Device, device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const device_class = 'wind_speed'; const device_class = 'wind_speed';
const unit = svc.props?.sup_units?.[0] ?? 'km/h'; const unit = svc.props?.sup_units?.[0] ?? 'km/h';
@ -18,6 +21,6 @@ export function sensor_wind__components(
unit_of_measurement: unit, unit_of_measurement: unit,
value_template: `{{ value_json['${svc.addr}'].sensor }}`, value_template: `{{ value_json['${svc.addr}'].sensor }}`,
}, },
} },
}; };
} }

View File

@ -9,26 +9,29 @@
// temperature_command_topic → cmd.setpoint.set // temperature_command_topic → cmd.setpoint.set
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
import { sendFimpMsg } from "../fimp/fimp"; import { sendFimpMsg } from '../fimp/fimp';
import { VinculumPd7Device, VinculumPd7Service } from "../fimp/vinculum_pd7_device"; import {
import { ClimateComponent } from "../ha/mqtt_components/climate"; VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { ClimateComponent } from '../ha/mqtt_components/climate';
import { import {
CommandHandlers, CommandHandlers,
ServiceComponentsCreationResult, ServiceComponentsCreationResult,
} from "../ha/publish_device"; } from '../ha/publish_device';
import { haGetCachedState } from "../ha/update_state"; import { haGetCachedState } from '../ha/update_state';
export function thermostat__components( export function thermostat__components(
topicPrefix: string, topicPrefix: string,
_device: VinculumPd7Device, _device: VinculumPd7Device,
svc: VinculumPd7Service svc: VinculumPd7Service,
): ServiceComponentsCreationResult | undefined { ): ServiceComponentsCreationResult | undefined {
const supModes: string[] = svc.props?.sup_modes ?? []; const supModes: string[] = svc.props?.sup_modes ?? [];
const supSetpoints: string[] = svc.props?.sup_setpoints ?? []; const supSetpoints: string[] = svc.props?.sup_setpoints ?? [];
if (!supModes.length) return undefined; // nothing useful to expose if (!supModes.length) return undefined; // nothing useful to expose
const defaultSpType = supSetpoints[0] ?? "heat"; const defaultSpType = supSetpoints[0] ?? 'heat';
const ranges: Record<string, { min?: number; max?: number }> = const ranges: Record<string, { min?: number; max?: number }> =
svc.props?.sup_temperatures ?? {}; svc.props?.sup_temperatures ?? {};
@ -82,9 +85,9 @@ export function thermostat__components(
if (!supModes.includes(payload)) return; if (!supModes.includes(payload)) return;
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "thermostat", service: 'thermostat',
cmd: "cmd.mode.set", cmd: 'cmd.mode.set',
val_t: "string", val_t: 'string',
val: payload, val: payload,
}); });
}, },
@ -95,13 +98,15 @@ export function thermostat__components(
await sendFimpMsg({ await sendFimpMsg({
address: svc.addr!, address: svc.addr!,
service: "thermostat", service: 'thermostat',
cmd: "cmd.setpoint.set", cmd: 'cmd.setpoint.set',
val_t: "str_map", val_t: 'str_map',
val: { val: {
type: haGetCachedState({ topic: `${topicPrefix}/state` })?.[svc.addr]?.mode ?? defaultSpType, type:
haGetCachedState({ topic: `${topicPrefix}/state` })?.[svc.addr]
?.mode ?? defaultSpType,
temp: payload, temp: payload,
unit: "C", unit: 'C',
}, },
}); });
}, },

View File

@ -1,3 +1,3 @@
export function delay(ms: number) { export function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }