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