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 }, R = N[keyof N]> = { [x in keyof N]: InstanceOf; } & { root: R | null; events: { [x in keyof N]: InstanceType['events'] }; drivers: { [x in keyof N]: InstanceType['drivers'] }; commands: { [x in keyof N]: InstanceType['commands'] }; nodes: { [x in keyof N]: InstanceType }; applyNodeDefs(): Promise; refreshNotes(): Promise; addNode: (node: NodeInfo | ISYNode, isy?: ISY) => void | Promise; } & Omit, 'drivers' | 'commands' | 'events'>; export namespace CompositeDevice { export type DriversOf> = N['drivers']; export type CommandsOf> = N['commands']; export type EventsOf> = N['events']; export type DriverNamesOf> = ObjectToUnion<{ [x in StringKeys>]: `${x}.${ISYNode.DriverNamesOf & string}`; }>; export type CommandNamesOf = N extends BaseFactory> ? CommandNamesOf : never; export type EventNamesOf> = ObjectToUnion<{ [x in StringKeys>]: `${x}.${ISYNode.EventNamesOf & string}`; }>; export type DriverKeysOf> = ObjectToUnion<{ [x in StringKeys>]: `${x}.${ISYNode.DriverKeysOf & string}`; }>; export type CommandKeysOf> = ObjectToUnion<{ [x in StringKeys>]: `${x}.${ISYNode.CommandKeysOf & string}`; }>; export type Any = CompositeDevice }>; //@ts-ignore type test = DriverNamesOf; export function of }>( nodes: N, keyFunction: (node: NodeInfo) => [keyof N, boolean] ): Constructor>; export function of }>( nodes: N, keyMap: { [x in keyof N]: number | string } ): Constructor>; export function of }>( nodes: N, keyFunction: { [x in keyof N]: number | string } | ((node: NodeInfo) => [keyof N, boolean]) ): Constructor & ISYDevice> { 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) { // Handle ISYNode case if needed } 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']; } } // Return a default value if no match is found return [Object.keys(keyIndex)[0] as keyof N, false] as any; }); } // This should never be reached, but TypeScript needs a return throw new Error('Invalid keyFunction parameter'); } export interface Factory< F extends Family, N extends { [x: string]: ISYNode.Factory }, C extends CompositeDevice, > extends BaseFactory { //Drivers: { [x in keyof N]: N[x]['Drivers'] }; //Commands: { [x in keyof N]: [N[x]['Commands']] }; Nodes: N; } export function isComposite(device: ISYDevice): device is CompositeDevice { return 'addNode' in device; } //@ts-ignore } export function CompositeOf }>( nodes: N, keyFunction: (node: NodeInfo | ISYNode) => [keyof N, boolean] ): Constructor> { return class implements ISYDevice { readonly isy: ISY; /*public static async create(...args: any[]): Promise> { const d = new class(args) as any as CompositeDevice; 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 { for (const n in this.nodes) { let node = this.nodes[n]; if (ISYDevice.isQueryable(node)) { await node.query(); } } } async refreshState(): Promise { for (const n in this.nodes) { let node = this.nodes[n]; await node.refreshState(); } } public async applyNodeDefs(): Promise { for (const n in this.nodes) { let node = this.nodes[n] as ISYDeviceNode; 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['events'] } = {} as any; public drivers: { [x in keyof N]: InstanceType['drivers'] } = {} as any; public commands: { [x in keyof N]: InstanceType['commands'] } = {} as any; public nodes: { [x in keyof N]: InstanceType } = {} as any; public async refreshNotes(): Promise { for (const key in this.nodes) { await this.nodes[key].refreshNotes(); } return Promise.resolve(); } public root: ISYDeviceNode | null = null; public async addNode(node: ISYNode): Promise; public async addNode(node: NodeInfo, isy: ISY): Promise; public async addNode(node: NodeInfo | ISYNode, isy = this.isy) { let n: ISYDeviceNode | null = null; if (node instanceof ISYNode) { n = node as unknown as ISYDeviceNode; } else { const key = keyFunction(node)[0]; const nodeFactory = nodes[key]; if (nodeFactory?.create) { n = await nodeFactory.create(isy, node); } else { throw new Error(`No factory found for node key: ${String(key)}`); } } if (!n) { throw new Error('Failed to create node'); } const keyL = keyFunction(node); const key = keyL[0]; const isRoot = keyL[1]; this.nodes[key] = n as InstanceType; //@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>; } /* export class ISYMultiNodeDevice implements ISYDevice, ISYNode.CommandMap, string> { commands: UnionToIntersection<{ [x in keyof N]: ISYNode.CommandsOf; } extends Record ? keyof N extends string ? { [x in keyof N]: ISYNode.CommandsOf; }[string & keyof N] extends Record ? keyof { [x in keyof N]: ISYNode.CommandsOf; }[string & keyof N] extends string ? { [x in `${string & keyof N}.${string & keyof { [x in keyof N]: ISYNode.CommandsOf; }[string & keyof N]}`]: { [x in keyof N]: ISYNode.CommandsOf; }[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; } extends Record ? keyof N extends string ? { [x in keyof N]: ISYNode.DriversOf; }[string & keyof N] extends Record ? keyof { [x in keyof N]: ISYNode.DriversOf; }[string & keyof N] extends string ? { [x in `${string & keyof N}.${string & keyof { [x in keyof N]: ISYNode.DriversOf; }[string & keyof N]}`]: { [x in keyof N]: ISYNode.DriversOf; }[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 { throw new Error('Method not implemented.'); } sendCommand(command: Extract; } extends Record ? keyof N extends string ? { [x in keyof N]: ISYNode.CommandsOf; }[string & keyof N] extends Record ? keyof { [x in keyof N]: ISYNode.CommandsOf; }[string & keyof N] extends string ? { [x in `${string & keyof N}.${string & keyof { [x in keyof N]: ISYNode.CommandsOf; }[string & keyof N]}`]: { [x in keyof N]: ISYNode.CommandsOf; }[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 | number): Promise { throw new Error('Method not implemented.'); } updateProperty(propertyName: keyof ISYNode.DriverMap, value: string): Promise { throw new Error('Method not implemented.'); } handleControlTrigger(controlName: string): boolean { throw new Error('Method not implemented.'); } _parentDevice: ISYDevice; children: ISYNode[]; convertTo(value: any, uom: number); convertTo(value: any, uom: number, propertyName: keyof DriverMap); 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); 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): void { throw new Error('Method not implemented.'); } readProperties(): Promise { throw new Error('Method not implemented.'); } refresh(): Promise { throw new Error('Method not implemented.'); } refreshNotes(): Promise { throw new Error('Method not implemented.'); } parseResult(node: { property: DriverState | DriverState[]; }): void { throw new Error('Method not implemented.'); } handlePropertyChange(propertyName: keyof ISYNode.DriverMap & 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>; 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; } */