@pmouli/isy-matter-server
Version:
Service to expose an ISY device as a Matter Border router
119 lines (102 loc) • 5.62 kB
text/typescript
import { isObject } from '@matter/general';
import { writeFile, writeFileSync } from 'fs';
import { type EnumLiteral, Family } from '../Definitions/index.js';
import { ISY, logStringify, writeDebugFile } from '../ISY.js';
import { ISYNode } from '../ISYNode.js';
import type { NodeDef } from '../Model/NodeDef.js';
import { type NodeInfo, isDynamic, isStatic, isZWave } from '../Model/NodeInfo.js';
import type { Factory, IdentityOf, StringKeys } from '../Utils.js';
import type { Constructor } from './Constructor.js';
import type { DynamicNode } from './DynamicNode.js';
import type { ISYDeviceNode } from './ISYDeviceNode.js';
export namespace NodeFactory {
export type NodeClassFactory<T extends ISYDeviceNode<any, any, any, any>> = Factory<T> & { implements: string[] } & { Commands; Drivers };
export const registry: NodeClassRegistry = {};
export type NodeClass<T extends Family | keyof typeof Family> =
T extends Family ? typeof ISYNode<T, any, any, any>
: T extends keyof typeof Family ? typeof ISYNode<(typeof Family)[T], any, any, any>
: never;
export const implementsRegistry: { [x in StringKeys<typeof Family>]?: { [x: string]: string[] } } = {};
export function register<F extends keyof typeof Family>(nodeClass: NodeClass<F>, id: string = nodeClass.nodeDefId) {
//let s; //FamilyNodeClassRegistry<(typeof Family)[F]>;
let f = Family[nodeClass.family];
let s = (registry[f] ?? (registry[f] = {})) as FamilyNodeClassRegistry<any>;
s[id] = nodeClass;
if (!implementsRegistry[f]) {
implementsRegistry[f] = {};
}
implementsRegistry[f][id] = nodeClass.implements;
}
function compare(a: typeof ISYNode<any, any, any, any>, b: typeof ISYNode<any, any, any, any>) {
if (a.nodeDefId === b.nodeDefId) {
return 0;
}
if (a.implements.includes(b.nodeDefId)) return 1;
if (b.implements.includes(a.nodeDefId)) return -1;
return b.nodeDefId.localeCompare(a.nodeDefId);
}
export function sortImplementsRegistry() {
for (let f in implementsRegistry) {
let reg = implementsRegistry[f];
for (let e in reg) {
reg[e] = reg[e].sort((a, b) => compare(getForNodeDefId(f as keyof typeof Family, a), getForNodeDefId(f as keyof typeof Family, b)));
}
}
}
export function getImplements<F extends keyof typeof Family, T extends ISYNode<(typeof Family)[F], any, any, any>>(node: T | typeof ISYNode | Factory<ISYNode>): string[] {
if (node instanceof ISYNode) return implementsRegistry[Family[node.family] as F][node.nodeDefId] ?? [];
else if (typeof node === 'function') return implementsRegistry[Family[node.family] as F][node.nodeDefId] ?? [];
else return implementsRegistry[Family[(node.Class as typeof ISYNode).family] as F][(node.Class as typeof ISYNode).nodeDefId] ?? [];
}
export function getForNode<F extends keyof typeof Family>(family: F, node: NodeInfo<(typeof Family)[F]>): NodeClass<F> {
if (isZWave(node)) {
return getForNodeDefId(family, node.sgid ?? node.nodeDefId);
} else if (isDynamic(node)) {
return getForNodeDefId(family, node.nodeTypeId);
}
return getForNodeDefId(family, node.nodeDefId);
}
export function getForNodeDefId<F extends keyof typeof Family | Family>(family: F, nodeDefId: string): NodeClass<F> {
if (typeof family === 'string') {
return registry[family as keyof typeof Family]?.[nodeDefId] as NodeClass<F>;
} else if (typeof family === 'number') {
return registry[Family[family] as keyof typeof Family]?.[nodeDefId] as NodeClass<F>;
}
}
export async function get<F extends keyof typeof Family>(node: NodeInfo<(typeof Family)[F]>, isy: ISY = ISY.instance): Promise<typeof ISYNode<(typeof Family)[F], any, any, any>> {
let nodeN = null;
let family = Family.Insteon;
if (typeof node.family === 'object') family = node.family._;
else if (node.family !== undefined && node.family !== null) family = node.family;
nodeN = getForNodeDefId(Family[family] as F, node.nodeDefId);
if (nodeN) return Promise.resolve(nodeN);
let cls = (await import('../Devices/GenericNode.js')).GenericNode;
NodeFactory.register(cls, isDynamic(node) || isZWave(node) ? node.nodeTypeId : node.nodeDefId);
return cls;
}
export async function create<F extends Family>(nodeInfo: NodeInfo<F>, isy: ISY = ISY.instance): Promise<ISYNode<F, any, any, any>> {
const nodeClass = (await get(nodeInfo)) as unknown as Constructor<ISYNode<F, any, any, any>>;
let nodeInstance = null as ISYNode<F, any, any, any>;
if (nodeClass?.name !== 'ISYNode' && nodeClass !== undefined) {
nodeInstance = new nodeClass(isy, nodeInfo);
}
if (isDynamic(nodeInfo) || isZWave(nodeInfo)) {
let n = (await isy.sendRequest(`zmatter/${nodeInfo.family == Family.ZWave ? 'zwave' : 'zigbee'}/node/${nodeInfo.address}/def/get?full=true`, { trailingSlash: false })) as NodeDef;
if (n) {
isy.logger.info(`NodeDef for ${nodeInfo.name} with nodeDef ${nodeInfo.nodeDefId} is dynamic. Saving nodeDef for debug purposes.`);
if (isy.isDebugEnabled) await writeDebugFile(JSON.stringify(n), `${nodeInfo.address}_NodeDef.json`, isy.logger, isy.storagePath);
if (nodeInstance) {
try {
(nodeInstance as DynamicNode<any, any, any, any>).applyNodeDef(n);
} catch {}
}
} else {
/*ISY.instance.logger.warn(`No built-in class found for ${Family[nodeInfo.family ?? 1]}.${nodeInfo.nodeDefId}`);
return new GenericNode(isy, nodeInfo);*/
}
}
return nodeInstance;
}
}
export type FamilyNodeClassRegistry<T extends Family> = { [x: string]: NodeFactory.NodeClass<T> };
type NodeClassRegistry = { [x in Extract<keyof typeof Family, string>]?: FamilyNodeClassRegistry<(typeof Family)[x]> };