UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

135 lines (106 loc) 5.33 kB
import { camelize, EventEmitter, Observable } from '@matter/main'; import { Behavior, ClusterBehavior, type MutableEndpoint } from '@matter/node'; import type { ISYDeviceNode } from 'isy-nodejs/Devices/ISYDeviceNode'; import { DeviceNode, ISY, type CompositeDevice, type ObjectToUnion } from 'isy-nodejs/ISY'; import { ISYDevice } from 'isy-nodejs/ISYDevice'; import { ISYNode } from 'isy-nodejs/ISYNode'; import type { StringKeyOf } from 'type-fest'; import winston from 'winston'; import { MappingRegistry, type ClusterMapping, type DeviceToClusterMap } from '../Mappings/MappingRegistry.js'; export class ISYBridgedDeviceBehavior<N extends ISYDevice.Any, E extends MutableEndpoint = MutableEndpoint> extends Behavior { static override readonly id = 'isyNode'; static override readonly early = true; listeners = [] as ((...any) => void)[]; declare internal: ISYBridgedDeviceBehavior.Internal; declare state: ISYBridgedDeviceBehavior.State; declare events: ISYBridgedDeviceBehavior.Events & ISYBridgedDeviceBehavior.EventsFor<N>; override async initialize(_options?: {}) { await super.initialize(_options); var address = this.state.address; const cd = ISY.instance.getDevice(this.state.address); this.internal.device = cd; //@ts-ignore this.internal.map = MappingRegistry.getMapping(this.internal.device); this.logger.debug(`Initializing ${this.constructor.name} for ${this.internal.device.constructor.name} ${this.internal.device.name} with address ${address}`); if (ISYDevice.isComposite(cd)) { for (const m in cd.nodes) { const d = cd.nodes[m] as ISYNode<N['family']>; for (const f in d.drivers) { let evt = `${d.drivers[f].name}Changed`; const obs = Observable<[{ node: string; driver: string; newValue: any; oldValue: any; formattedValue: string }]>(); const listener = (driver: string, newValue: any, oldValue: any, formattedValue: string) => obs.emit({ node: m, driver: driver, newValue, oldValue, formattedValue }); d.events.on(evt, listener); this.events[`${m}.${evt}`] = obs; //@ts-ignore //d.events.on(evt, (driver: string, newValue: any, oldValue: any, formattedValue: string) => this.events.emit(evt, { driver, newValue, oldValue, formattedValue } as unknown as any)); } } } else if (ISYDevice.isNode(cd)) { const d = cd; for (const f in d.drivers) { let evt = `${d.drivers[f].name}Changed`; const obs = Observable<[{ driver: string; newValue: any; oldValue: any; formattedValue: string }]>(); d.events.on(evt, (driver: string, newValue: any, oldValue: any, formattedValue: string) => obs.emit({ driver, newValue, oldValue, formattedValue })); this.events[evt] = obs; //@ts-ignore //d.events.on(evt, (driver: string, newValue: any, oldValue: any, formattedValue: string) => this.events.emit(evt, { driver, newValue, oldValue, formattedValue } as unknown as any)); } } this.logger.debug(`Initialized ${this.constructor.name} for ${this.internal.device.constructor.name} ${this.internal.device.name} with address ${address}`); } get logger() { if (!this.internal.logger) { let l = winston.loggers.get('matter').child({ address: this.device.address, name: this.device.label }); const label = `${this.device.label} (${this.device.address})`; l.format = winston.format.label({ label: label }); this.internal.logger = l; } return this.internal.logger; } get device(): N { return this.internal.device as N; } get map(): DeviceToClusterMap<N, E> { return (this.internal.map ??= MappingRegistry.getMapping<N, E>(this.device)); } mapForBehavior<B extends ClusterBehavior, D extends ISYDevice.Any>(behavior: B): ClusterMapping<B['cluster'], D> { // @ts-ignore return this.map.mapping[camelize(behavior.cluster.name, false) as any] as any; } handlePropertyChange(driver: string, newValue: any, oldValue: any, formattedValue: string) { this.events.propertyChanged.emit({ driver, newValue, oldValue, formattedValue }); } override [Symbol.asyncDispose]() { this.internal.device = null; //(this.internal.device.events as EventEmitter).removeAllListeners return super[Symbol.asyncDispose](); } } export namespace ISYBridgedDeviceBehavior { export class Internal { device?: ISYDevice.Any; map?: DeviceToClusterMap<typeof this.device, any>; logger?: winston.Logger; } type InternalEventsFor<N extends ISYNode<any, any, any, any>> = N extends ISYDeviceNode<any, infer D, any> ? { [s in keyof D as `${D[s]['name']}Changed`]: Observable<[{ driver: s; newValue: any; oldValue: any; formattedValue: string }]>; } : never; export type InternalEventsForComposite<I extends CompositeDevice.Any> = ObjectToUnion<{ [s in StringKeyOf<I['drivers']>]: { [q in keyof I['drivers'][s] as `${s}.${I['drivers'][s][q]['name']}Changed`]: Observable<[{ node: s; driver: q; newValue: any; oldValue: any; formattedValue: string }]>; }; }>; export type EventsFor<N extends ISYDevice.Any> = N extends DeviceNode.Any ? InternalEventsFor<N> : N extends CompositeDevice.Any ? InternalEventsForComposite<N> : never; export class Events extends EventEmitter { propertyChanged = Observable<[{ node?: string; driver: string; newValue: any; oldValue: any; formattedValue: string }]>(); } export class State { address = ''; } }