UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

327 lines (266 loc) 13.1 kB
import { ClusterBehavior } from '@matter/main'; import { ISYDeviceNode } from 'isy-nodejs/Devices/ISYDeviceNode'; import { type DeviceNode, type Driver, type Factory, CompositeDevice, Converter } from 'isy-nodejs/ISY'; import { ISYDevice } from 'isy-nodejs/ISYDevice'; import type { DriversOf } from 'isy-nodejs/ISYNode'; import type { Constructor } from 'type-fest'; import winston from 'winston'; import { type ClusterMapping, hasPath } from '../Mappings/MappingRegistry.js'; import { ISYBridgedDeviceBehavior } from './ISYBridgedDeviceBehavior.js'; // #region Type aliases (6) export type ClusterForBehavior<B> = B extends ClusterBehavior.Type<infer C, infer D, infer E> ? C : never; export interface DeviceBehavior<P extends CompositeDevice.Any | DeviceNode.Any, T extends { cluster? }> { device: P; bridgedDeviceBehavior: ISYBridgedDeviceBehavior<P>; logger: winston.Logger; //ts-ignore map: ClusterMapping<T['cluster'], P>; handlePropertyChange(chg: PropertyChange<P>): void; } type NotUnknown<T extends ClusterBehavior> = T extends { cluster: { name: 'Unknown' } } ? never : T; export type PropertyChange<P extends CompositeDevice.Any | DeviceNode.Any> = P extends DeviceNode.Any ? { driver: keyof P['drivers']; newValue: any; oldValue: any; formattedValue: string; } : P extends CompositeDevice.Any ? { node: keyof P['nodes']; driver: keyof P['nodes'][keyof P['nodes']]['drivers']; newValue: any; oldValue: any; formattedValue: string } : never; // #endregion Type aliases (6) // #region Functions (1) function clusterBehaviorForNode<T extends Constructor<ClusterBehavior> & { cluster }, P extends DeviceNode.Any>(base: T, p: Factory<P>): typeof base & { new (...args: any[]): DeviceBehavior<P, T> } & { factory: typeof p } { //@ts-ignore let s = class ISYClusterBehavior extends base implements DeviceBehavior<P, T> { _device: P; static factory = p; handlers: { [x in DriversOf<P>]: (newValue, oldValue, formattedValue) => void } = {} as any; _bridgedDeviceBehavior: ISYBridgedDeviceBehavior<P>; get bridgedDeviceBehavior(): ISYBridgedDeviceBehavior<P> { return this._bridgedDeviceBehavior ?? this.agent.get(ISYBridgedDeviceBehavior<P>); } ///public map: ClusterMapping<ToClusterTypeByName<ClusterForBehavior<ConstructedType<typeof base>>["name"]>,ISYDeviceNode<any, any, any>>; map: ClusterMapping<T['cluster'], P>; setStateSafe(key: string, value: any) { if (this.state[key] != value) { try { this.state[key] = value; } catch (e) { this.logger.error(`Error setting state ${key} to ${value}: ${e}`); } } } override async initialize(_options?: {}) { await super.initialize(_options); var behavior = (await this.agent.load(ISYBridgedDeviceBehavior<P>)) as ISYBridgedDeviceBehavior<P>; this._bridgedDeviceBehavior = behavior; //var behavior = this.agent.get(ISYBridgedDeviceBehavior); this._device = behavior.device as P & ISYDeviceNode.Any; this.logger.debug(`Initializing cluster behavior: ${this.constructor.name}`); //@ts-ignore this.map = behavior.mapForBehavior<T, P>(this); for (const key2 in this.map?.attributes) { let val = this.map.attributes[key2]; let that = this; let driverObj = null as Driver<any, any>; if (typeof val === 'string' || typeof val === 'number') { driverObj = this._device.drivers[val] as Driver<any, any>; if (driverObj) { let evt = `${driverObj.name}Changed`; //this.state[key2 as string] = driverObj.value; //this.state[key2 as string] = driverObj.value; this.setStateSafe(key2, driverObj.value); this[`handle_${evt}`] = function ({ driver, newValue, oldValue, formattedValue }) { this.logger.debug(`${that.constructor.name}: handling property change for ${String(driver)} from ${oldValue} to ${newValue}`); this.setStateSafe(key2, newValue); }; this.reactTo(behavior.events[evt], this[`handle_${evt}`]); } } else if (hasPath(val)) { driverObj = this._device.drivers[val.driver as string] as Driver<any, any>; if (driverObj) { let evt = `${driverObj.name}Changed`; let { driver, converter } = val; const convFunc = Converter.get(converter as any)?.to; if (!convFunc) throw new Error(`Converter ${converter} not found`); /*this.state[key2 as string] = convFunc(this._device.drivers[driver as string].value);*/ this.setStateSafe(key2, convFunc(this._device.drivers[driver as string].value)); this[`handle_${evt}`] = function ({ driver, newValue, oldValue, formattedValue }) { let v = convFunc(newValue); let oldV = this.state[key2 as string]; this.logger.debug(`${that.constructor.name}: handling property change for ${String(driver)} from ${oldValue} (${oldV}) to ${newValue} (${v})`); this.setStateSafe(key2, v); }; this.reactTo(behavior.events[evt], this[`handle_${evt}`]); } } /*this.handlers[driverObj.id] = function (parent = that, newValue, oldValue, formattedValue) { //this.device.logger(`Handling property change for ${driver} (${key2}) with value ${newValue}`); //if (convFunc) this.state[key2 as string] = convFunc(newValue); parent.state[key2 as string] = convFunc(newValue); };*/ //(this as any).evt = this.handlers[driverObj.name]; } this.logger.debug(`Cluster behavior initialized: ${this.constructor.name}`); //this.reactTo(behavior.events.propertyChanged, this.handlePropertyChange, { lock: false }); //this._device.on("PropertyChanged", this.handlePropertyChange.bind(this)); } get device(): P & ISYDeviceNode.Any { return (this._device = this._device ?? (this.agent.get(ISYBridgedDeviceBehavior<P>).device as P & ISYDeviceNode.Any)) as P & ISYDeviceNode.Any; } get logger() { return this.bridgedDeviceBehavior.logger; } async handlePropertyChange({ driver, newValue, oldValue, formattedValue }: PropertyChange<P>) { // for (const key2 in this.map.attributes) { //await this.initialize(); this.device.logger(`${this.constructor.name}: handling property change for ${String(driver)} with value ${newValue}`); if (this.handlers[driver]) { this.handlers[driver](newValue, oldValue, formattedValue); } // if (typeof this.map.attributes[key2] === "string") { // if(this.map.attributes[key2] == driver) // { // this.state[key2 as string] = newValue; // return; // } }else if (this.map.attributes[key2].driver == driver) { // if (this.map.attributes[key2]?.driver == driver) { // this.state[key2 as string] = this.map.attributes[key2].converter(newValue); // } // } // } } }; s.factory = p; return s; } function clusterBehaviorForComposite<T extends Constructor<ClusterBehavior> & { cluster }, P extends CompositeDevice.Any>(base: T, p: Factory<P>): typeof base & { new (...args: any[]): DeviceBehavior<P, T> } & { factory: typeof p } { let s = class ISYClusterBehavior extends base implements DeviceBehavior<P, T> { _device: P; static factory = p; handlers: { [x in keyof P['drivers']]: { [y in keyof P['drivers'][x]]: (newValue, oldValue, formattedValue) => void; }; } = {} as any; setStateSafe(key: string, value: any) { if (this.state[key] != value) { try { this.state[key] = value; } catch (e) { this.logger.error(`Error setting state ${key} to ${value}: ${e}`); } } } _bridgedDeviceBehavior: ISYBridgedDeviceBehavior<P>; get bridgedDeviceBehavior(): ISYBridgedDeviceBehavior<P> { return this._bridgedDeviceBehavior ?? this.agent.get(ISYBridgedDeviceBehavior<P>); } ///public map: ClusterMapping<ToClusterTypeByName<ClusterForBehavior<ConstructedType<typeof base>>["name"]>,ISYDeviceNode<any, any, any>>; map: ClusterMapping<T['cluster'], P>; override async initialize(_options?: {}) { await super.initialize(_options); var behavior = (await this.agent.load(ISYBridgedDeviceBehavior<P>)) as ISYBridgedDeviceBehavior<P>; this._bridgedDeviceBehavior = behavior; //var behavior = this.agent.get(ISYBridgedDeviceBehavior); this._device = behavior.device as P & CompositeDevice.Any; this.logger.debug(`Initializing cluster behavior: ${this.constructor.name}`); //@ts-ignore this.map = behavior.mapForBehavior<T, P>(this); for (const key in this.map?.attributes) { let val = this.map.attributes[key]; let driverObj = null as Driver<any, any>; let nodeObj = null as DeviceNode.Any; if (typeof val === 'string') { let [node, id] = val.split('.'); nodeObj = this._device.nodes[node as string] as DeviceNode.Any; driverObj = nodeObj.drivers[id as string] as Driver<any, any>; this.setStateSafe(key, nodeObj.drivers[id].value); //this.state[key as string] = nodeObj.drivers[id].value; let evt = `${node}.${driverObj.name}Changed`; this[`handle_${evt}`] = function ({ node, driver, newValue, oldValue, formattedValue }) { this.logger.debug(`${this.constructor.name}: handling property change for ${node}.${driver} (${key}) with value ${newValue}`); this.setStateSafe(key, newValue); }; this.reactTo(behavior.events[evt], this[`handle_${evt}`]); } else if (hasPath(val)) { let driverObj = this._device?.nodes[val.node]?.drivers[val.driver] as Driver<any, any>; if (driverObj) { let evt = `${val.node}.${driverObj.name}Changed`; if (val.converter) { const convFunc = Converter.get(val.converter as any)?.to; if (!convFunc) throw new Error(`Converter ${val.converter} not found`); this.setStateSafe(key, convFunc(driverObj.value)); //this.state[key as string] = convFunc(driverObj.value); this[`handle_${evt}`] = function ({ node, driver, newValue, oldValue, formattedValue }) { let v = convFunc(newValue); let oldV = this.state[key as string]; this.logger.debug(`${this.constructor.name}: handling property change for ${node}.${String(driver)} from ${oldValue} (${oldV}) to ${newValue} (${v})`); this.setStateSafe(key, v); }; } else { this.setStateSafe(key, driverObj.value); //this.state[key as string] = driverObj.value; this[`handle_${evt}`] = function ({ node, driver, newValue, oldValue, formattedValue }) { this.logger.debug(`${this.constructor.name}: handling property change for ${node}.${driver} (${key}) from ${oldValue} to ${newValue}`); this.setStateSafe(key, newValue); }; } this.reactTo(behavior.events[evt], this[`handle_${evt}`]); } } /*if (driverObj && nodeObj) { let evt = `${nodeObj}.${driverObj.name}Changed`; (this as any).evt = this.handlers[driverObj.name]; //this.reactTo(behavior.events[evt], this.handlePropertyChange, { lock: false }); }*/ this.logger.debug(`Cluster behavior initialized: ${this.constructor.name}`); } //this.reactTo(behavior.events.propertyChanged, this.handlePropertyChange, { lock: false }); //this._device.on("PropertyChanged", this.handlePropertyChange.bind(this)); } get device(): P & ISYDeviceNode.Any { return (this._device = this._device ?? (this.agent.get(ISYBridgedDeviceBehavior<P>).device as P & ISYDeviceNode.Any)) as P & ISYDeviceNode.Any; } get logger() { return this.bridgedDeviceBehavior.logger; } async handlePropertyChange({ node, driver, newValue, oldValue, formattedValue }: PropertyChange<P>) { // for (const key2 in this.map.attributes) { //await this.initialize(); this.logger.debug(`${this.constructor.name}: handling property change for ${node}.${String(driver)} with value ${newValue}`); if (this.handlers[node][driver as string]) { this.handlers[node][driver as string](newValue, oldValue, formattedValue); } // if (typeof this.map.attributes[key2] === "string") { // if(this.map.attributes[key2] == driver) // { // this.state[key2 as string] = newValue; // return; // } }else if (this.map.attributes[key2].driver == driver) { // if (this.map.attributes[key2]?.driver == driver) { // this.state[key2 as string] = this.map.attributes[key2].converter(newValue); // } // } // } } }; s.factory = p; return s; } export function ISYClusterBehavior<T extends Constructor<ClusterBehavior> & { cluster } & Pick<ClusterBehavior.Type, 'alter'>, P extends CompositeDevice.Any | DeviceNode.Any>(base: T, p: Factory<P>): typeof base & { new (...args: any[]): DeviceBehavior<P, T> } & { factory: Factory<P> } { let alterations = { attributes: {} }; for (const l in base.cluster.attributes) { alterations.attributes[l] = { persistent: false }; } base = (base as unknown as ClusterBehavior.Type).alter(alterations) as unknown as T; if (ISYDevice.isNode(p)) { //@ts-ignore return clusterBehaviorForNode(base, p); } else if (ISYDevice.isComposite(p)) { //@ts-ignore return clusterBehaviorForComposite(base, p); } } // #endregion Functions (1)