UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

341 lines (290 loc) 14.4 kB
import type { Constructor } from 'type-fest'; import type { Feature } from '../Definitions/Global/Features.js'; import { Category, Family } from '../Definitions/index.js'; import { isDynamic, ISY } from '../ISY.js'; import { ISYDevice } from '../ISYDevice.js'; import { ISYNode } from '../ISYNode.js'; import type { NodeInfo } from '../Model/NodeInfo.js'; import type { ISYScene } from '../Scenes/ISYScene.js'; import type { Factory as BaseFactory, InstanceOf, ObjectToUnion, StringKeys } from '../Utils.js'; import type { ISYDeviceNode } from './ISYDeviceNode.js'; export type CompositeDevice<F extends Family, N extends { [x: string]: ISYNode.Factory<F, any> }, R = N[keyof N]> = { [x in keyof N]: InstanceOf<N[x]> } & { root: R; events: { [x in keyof N]: InstanceType<N[x]['Class']>['events'] }; drivers: { [x in keyof N]: InstanceType<N[x]['Class']>['drivers'] }; commands: { [x in keyof N]: InstanceType<N[x]['Class']>['commands'] }; nodes: { [x in keyof N]: InstanceType<N[x]['Class']> }; applyNodeDefs(): Promise<void>; refreshNotes(): Promise<void>; addNode: (node: NodeInfo | ISYNode, isy?: ISY) => void | Promise<void>; } & Omit<ISYDevice<F, unknown, unknown, unknown>, 'drivers' | 'commands' | 'events'>; export namespace CompositeDevice { export type DriversOf<N extends CompositeDevice<any, any>> = N['drivers']; export type CommandsOf<N extends CompositeDevice<any, any>> = N['commands']; export type EventsOf<N extends CompositeDevice<any, any>> = N['events']; export type DriverNamesOf<N extends CompositeDevice<any, any>> = ObjectToUnion<{ [x in StringKeys<DriversOf<N>>]: `${x}.${ISYNode.DriverNamesOf<N[x]> & string}` }>; export type CommandNamesOf<N> = N extends BaseFactory<CompositeDevice<any, infer X>> ? CommandNamesOf<X[keyof X]> : never; export type EventNamesOf<N extends CompositeDevice<any, any>> = ObjectToUnion<{ [x in StringKeys<DriversOf<N>>]: `${x}.${ISYNode.EventNamesOf<N[x]> & string}` }>; export type DriverKeysOf<N extends CompositeDevice<any, any>> = ObjectToUnion<{ [x in StringKeys<DriversOf<N>>]: `${x}.${ISYNode.DriverKeysOf<N[x]> & string}` }>; export type CommandKeysOf<N extends CompositeDevice<any, any>> = ObjectToUnion<{ [x in StringKeys<CommandsOf<N>>]: `${x}.${ISYNode.CommandKeysOf<N[x]> & string}` }>; export type Any = CompositeDevice<Family, { [x: string]: ISYNode.Factory<Family, any> }>; //@ts-ignore type test = DriverNamesOf<FanDevice>; export function of<F extends Family, N extends { [x: string]: ISYNode.Factory<F, any> }>(nodes: N, keyFunction: (node: NodeInfo) => [keyof N, boolean]): Constructor<CompositeDevice<F, N>>; export function of<F extends Family, N extends { [x: string]: ISYNode.Factory<F, any> }>(nodes: N, keyMap: { [x in keyof N]: number | string }): Constructor<CompositeDevice<F, N>>; export function of<F extends Family, N extends { [x: string]: ISYNode.Factory<F, any> }>(nodes: N, keyFunction: { [x in keyof N]: number | string } | ((node: NodeInfo) => [keyof N, boolean])): Constructor<CompositeDevice<F, N> & ISYDevice<F, any, any, any>> { if (keyFunction === undefined) { keyFunction = (node: NodeInfo) => [node.name, true]; } if (typeof keyFunction === 'function') { return CompositeOf(nodes, keyFunction as any); } else if (typeof keyFunction === 'object') { const keyIndex = keyFunction as { [x in keyof N]: number | string }; return CompositeOf(nodes, (node: NodeInfo | ISYNode) => { for (const key in keyIndex) { if (node instanceof ISYNode) { } else if (isDynamic(node) && node.nodeTypeId == `${keyIndex[key]}`) { return [key, node.pnode == node.address] as any; } if (node.address.endsWith(keyIndex[key].toString())) { return [key, keyIndex[key] == 1 || keyIndex[key] == '1']; } } }); } } export interface Factory<F extends Family, N extends { [x: string]: ISYNode.Factory<F, any> }, C extends CompositeDevice<F, N>> extends BaseFactory<C> { //Drivers: { [x in keyof N]: N[x]['Drivers'] }; //Commands: { [x in keyof N]: [N[x]['Commands']] }; Nodes: N; } export function isComposite(device: ISYDevice<any, any, any, any>): device is CompositeDevice<any, any> { return 'addNode' in device; } //@ts-ignore } export function CompositeOf<F extends Family, N extends { [x: string]: ISYNode.Factory<F, any> }>(nodes: N, keyFunction: (node: NodeInfo | ISYNode) => [keyof N, boolean]): Constructor<CompositeDevice<F, N>> { return class implements ISYDevice<F, any, any, any> { readonly isy: ISY; /*public static async create(...args: any[]): Promise<CompositeDevice<F, N>> { const d = new class(args) as any as CompositeDevice<F, N>; let isy = d.isy; for (const n in d.nodes) { const node = d.nodes[n] as ISYNode; if ('getNodeDef' in node && typeof node.getNodeDef == 'function' && 'applyNodeDef' in node && typeof node.applyNodeDef == 'function') { } } return d; }*/ constructor(...args: any[]) { if (args[0] instanceof ISY) { this.isy = args.shift(); for (const nodeInfo of args as NodeInfo[]) { this.addNode(nodeInfo, this.isy); } } } category: F extends Family.Insteon ? Category.Insteon : Category.Home.Category; deviceClass: any; enabled: boolean; family: F; hidden: boolean; isDimmable: boolean; label: string; model: string; modelNumber: string; name: any; parentAddress: any; productName: string; scenes: ISYScene[]; subCategory: number; type: any; typeCode: string; version: string; manufacturer: string; productId: string | number; modelName: string; location: string; features: Feature; async query(): Promise<void> { for (const n in this.nodes) { let node = this.nodes[n]; if (ISYDevice.isQueryable(node)) { await node.query(); } } } async refreshState(): Promise<void> { for (const n in this.nodes) { let node = this.nodes[n]; await node.refreshState(); } } public async applyNodeDefs(): Promise<void> { for (const n in this.nodes) { let node = this.nodes[n] as ISYDeviceNode<F, any, any, any>; if (ISYDevice.isDynamic(node)) { await node.applyNodeDef(); } } } _initialized = false; public get initialized(): boolean { if (!this._initialized) for (const key in this.nodes) { if (!this.nodes[key]?.initialized) { return false; } } this._initialized = true; return true; } public address: string; public events: { [x in keyof N]: InstanceOf<N[x]>['events'] } = {} as any; public drivers: { [x in keyof N]: InstanceType<N[x]['Class']>['drivers'] } = {} as any; public commands: { [x in keyof N]: InstanceType<N[x]['Class']>['commands'] } = {} as any; public nodes: { [x in keyof N]: InstanceType<N[x]['Class']> } = {} as any; public async refreshNotes(): Promise<void> { for (const key in this.nodes) { await this.nodes[key].refreshNotes(); } return Promise.resolve(); } public root = null; public async addNode(node: ISYNode): Promise<void>; public async addNode(node: NodeInfo, isy: ISY): Promise<void>; public async addNode(node: NodeInfo | ISYNode, isy = this.isy) { let n: ISYDeviceNode<F, any, any, any> = null; if (node instanceof ISYNode) { n = node as ISYDeviceNode<any, any, any, any>; } else { n = await nodes[keyFunction(node)[0]].create(isy, node); } const keyL = keyFunction(node); const key = keyL[0]; const isRoot = keyL[1]; this.nodes[key] = n as InstanceType<N[typeof key]['Class']>; //@ts-ignore this[key] = n; Object.defineProperty(this.events, key, { get(): () => any { return this[key].events; } }); Object.defineProperty(this.drivers, key, { get(): () => any { return this[key].drivers; } }); Object.defineProperty(this.commands, key, { get(): () => any { return this[key].commands; } }); if (isRoot) { this.address = node.address; this.family = n.family; this.category = n.category; this.deviceClass = n.deviceClass; this.enabled = n.enabled; this.hidden = n.hidden; this.isDimmable = n.isDimmable; this.label = n.label; this.model = n.model; this.modelNumber = n.modelNumber; this.name = n.name; this.parentAddress = n.parentAddress; this.productName = n.productName; this.scenes = n.scenes; this.subCategory = n.subCategory; this.type = n.type; this.typeCode = n.typeCode; this.version = n.version; this.manufacturer = n.manufacturer; this.productId = n.productId; this.modelName = n.modelName; this.manufacturer = n.manufacturer; this.location = n.location; this.root = n; } } } as unknown as Constructor<CompositeDevice<F, N>>; } /* export class ISYMultiNodeDevice<T extends Family, N extends NodeList> implements ISYDevice<T, ISYNode.DriverMap<N>, ISYNode.CommandMap<N>, string> { commands: UnionToIntersection<{ [x in keyof N]: ISYNode.CommandsOf<N[x]>; } extends Record<string, unknown> ? keyof N extends string ? { [x in keyof N]: ISYNode.CommandsOf<N[x]>; }[string & keyof N] extends Record<string, unknown> ? keyof { [x in keyof N]: ISYNode.CommandsOf<N[x]>; }[string & keyof N] extends string ? { [x in `${string & keyof N}.${string & keyof { [x in keyof N]: ISYNode.CommandsOf<N[x]>; }[string & keyof N]}`]: { [x in keyof N]: ISYNode.CommandsOf<N[x]>; }[string & keyof N][x extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? any : B : B : B : B : B : B : B : B : B : B : x]; } : never : never : never : never>; readProperty(propertyName: keyof UnionToIntersection<{ [x in keyof N]: ISYNode.DriversOf<N[x]>; } extends Record<string, unknown> ? keyof N extends string ? { [x in keyof N]: ISYNode.DriversOf<N[x]>; }[string & keyof N] extends Record<string, unknown> ? keyof { [x in keyof N]: ISYNode.DriversOf<N[x]>; }[string & keyof N] extends string ? { [x in `${string & keyof N}.${string & keyof { [x in keyof N]: ISYNode.DriversOf<N[x]>; }[string & keyof N]}`]: { [x in keyof N]: ISYNode.DriversOf<N[x]>; }[string & keyof N][x extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? any : B : B : B : B : B : B : B : B : B : B : x]; } : never : never : never : never>): Promise<DriverState> { throw new Error('Method not implemented.'); } sendCommand(command: Extract<keyof UnionToIntersection<{ [x in keyof N]: ISYNode.CommandsOf<N[x]>; } extends Record<string, unknown> ? keyof N extends string ? { [x in keyof N]: ISYNode.CommandsOf<N[x]>; }[string & keyof N] extends Record<string, unknown> ? keyof { [x in keyof N]: ISYNode.CommandsOf<N[x]>; }[string & keyof N] extends string ? { [x in `${string & keyof N}.${string & keyof { [x in keyof N]: ISYNode.CommandsOf<N[x]>; }[string & keyof N]}`]: { [x in keyof N]: ISYNode.CommandsOf<N[x]>; }[string & keyof N][x extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? B extends `${infer A}.${infer B}` ? any : B : B : B : B : B : B : B : B : B : B : x]; } : never : never : never : never>, string>, parameters?: Record<string | symbol, string | number> | string | number): Promise<any> { throw new Error('Method not implemented.'); } updateProperty(propertyName: keyof ISYNode.DriverMap<N>, value: string): Promise<any> { throw new Error('Method not implemented.'); } handleControlTrigger(controlName: string): boolean { throw new Error('Method not implemented.'); } _parentDevice: ISYDevice<T, any, any, any>; children: ISYNode<any, any, any, any>[]; convertTo(value: any, uom: number); convertTo(value: any, uom: number, propertyName: keyof DriverMap<N>); convertTo(value: unknown, uom: unknown, propertyName?: unknown): any { throw new Error('Method not implemented.'); } convertFrom(value: any, uom: number); convertFrom(value: any, uom: number, propertyName: keyof DriverMap<N>); convertFrom(value: unknown, uom: unknown, propertyName?: unknown): any { throw new Error('Method not implemented.'); } addLink(isyScene: ISYScene): void { throw new Error('Method not implemented.'); } addChild(childDevice: ISYNode<any, any, any, any>): void { throw new Error('Method not implemented.'); } readProperties(): Promise<DriverState[]> { throw new Error('Method not implemented.'); } refresh(): Promise<any> { throw new Error('Method not implemented.'); } refreshNotes(): Promise<void> { throw new Error('Method not implemented.'); } parseResult(node: { property: DriverState | DriverState[]; }): void { throw new Error('Method not implemented.'); } handlePropertyChange(propertyName: keyof ISYNode.DriverMap<N> & string, value: any, uom: UnitOfMeasure, prec: number, formattedValue: string): boolean { throw new Error('Method not implemented.'); } logger(arg0: string): unknown { throw new Error('Method not implemented.'); } handleEvent(evt: any): unknown { throw new Error('Method not implemented.'); } on(arg0: string, arg1: any): unknown { throw new Error('Method not implemented.'); } name: any; drivers: Driver.ForAll<ISYNode.DriverMap<N>>; address: string; family: T; typeCode: string; deviceClass: any; parentAddress: any; category: number; subCategory: number; type: any; scenes: ISYScene[]; hidden: boolean; enabled: boolean; productName: string; model: string; modelNumber: string; version: string; isDimmable: boolean; label: string; } */