@pmouli/isy-matter-server
Version:
Service to expose an ISY device as a Matter Border router
135 lines (106 loc) • 5.33 kB
text/typescript
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 = '';
}
}