UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

119 lines (102 loc) 5.62 kB
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]> };