import events from "node:events"; import type * as Models from "../models"; import type {BroadcastAddress} from "../zspec/enums"; import * as Zcl from "../zspec/zcl"; import type * as Zdo from "../zspec/zdo"; import type * as ZdoTypes from "../zspec/zdo/definition/tstypes"; import {discoverAdapter} from "./adapterDiscovery"; import type * as AdapterEvents from "./events"; import type * as TsType from "./tstype"; import {KonnextConfig} from "../controller/model/konnextConfig"; export interface ZclWaitressPayload extends AdapterEvents.ZclPayload { header: NonNullable; } export interface ClusterWaitressMatcher { address: number | string | undefined; clusterId: number; endpoint?: number; commandId?: number; defaultRspCommandId?: number; transactionSequenceNumber?: number; } interface AdapterEventMap { deviceJoined: [payload: AdapterEvents.DeviceJoinedPayload]; zclPayload: [payload: AdapterEvents.ZclPayload]; zdoResponse: [clusterId: Zdo.ClusterId, response: ZdoTypes.GenericZdoResponse]; disconnected: []; deviceLeave: [payload: AdapterEvents.DeviceLeavePayload]; } export abstract class Adapter extends events.EventEmitter { public hasZdoMessageOverhead: boolean; public manufacturerID: Zcl.ManufacturerCode; protected networkOptions: TsType.NetworkOptions; protected adapterOptions: TsType.AdapterOptions; protected serialPortOptions: TsType.SerialPortOptions; protected backupPath: string; protected constructor( networkOptions: TsType.NetworkOptions, serialPortOptions: TsType.SerialPortOptions, backupPath: string, adapterOptions: TsType.AdapterOptions, ) { super(); this.hasZdoMessageOverhead = true; this.manufacturerID = Zcl.ManufacturerCode.RESERVED_10; this.networkOptions = networkOptions; this.adapterOptions = adapterOptions; this.serialPortOptions = serialPortOptions; this.backupPath = backupPath; } /** * Utility */ public static async create( networkOptions: TsType.NetworkOptions, serialPortOptions: TsType.SerialPortOptions, backupPath: string, adapterOptions: TsType.AdapterOptions, konnextConfig: KonnextConfig, ): Promise { const discovered = await discoverAdapter(serialPortOptions.adapter, serialPortOptions.path); serialPortOptions.adapter = discovered.adapter; serialPortOptions.path = discovered.path; if (serialPortOptions.baudRate === undefined && discovered.baudRate !== undefined) { serialPortOptions.baudRate = discovered.baudRate; } if (serialPortOptions.rtscts === undefined && discovered.rtscts !== undefined) { serialPortOptions.rtscts = discovered.rtscts; } switch (discovered.adapter) { case "zstack": { const {ZStackAdapter} = await import("./z-stack/adapter/zStackAdapter.js"); return new ZStackAdapter(networkOptions, serialPortOptions, backupPath, adapterOptions, konnextConfig); } case "ember": { const {EmberAdapter} = await import("./ember/adapter/emberAdapter.js"); return new EmberAdapter(networkOptions, serialPortOptions, backupPath, adapterOptions); } case "deconz": { const {DeconzAdapter} = await import("./deconz/adapter/deconzAdapter.js"); return new DeconzAdapter(networkOptions, serialPortOptions, backupPath, adapterOptions); } case "zigate": { const {ZiGateAdapter} = await import("./zigate/adapter/zigateAdapter.js"); return new ZiGateAdapter(networkOptions, serialPortOptions, backupPath, adapterOptions); } case "zboss": { const {ZBOSSAdapter} = await import("./zboss/adapter/zbossAdapter.js"); return new ZBOSSAdapter(networkOptions, serialPortOptions, backupPath, adapterOptions); } case "zoh": { const {ZoHAdapter} = await import("./zoh/adapter/zohAdapter.js"); return new ZoHAdapter(networkOptions, serialPortOptions, backupPath, adapterOptions); } // @deprecated case "ezsp": { const {EZSPAdapter} = await import("./ezsp/adapter/ezspAdapter.js"); return new EZSPAdapter(networkOptions, serialPortOptions, backupPath, adapterOptions); } default: { throw new Error(`Adapter '${discovered.adapter}' does not exists, possible options: zstack, ember, deconz, zigate, zboss, zoh, ezsp`); } } } static clusterWaitressTimeoutFormatter(matcher: ClusterWaitressMatcher, timeout: number): string { return `Timeout after ${timeout}ms [address=${matcher.address} endpoint=${matcher.endpoint} clusterId=${matcher.clusterId} cmdId=${matcher.commandId} tsn=${matcher.transactionSequenceNumber}]`; } static zclWaitressValidator(payload: ZclWaitressPayload, matcher: ClusterWaitressMatcher): boolean { const {header, address, clusterID, endpoint} = payload; return ( // no sender in Touchlink (matcher.address === undefined || address === matcher.address) && clusterID === matcher.clusterId && endpoint === matcher.endpoint && (matcher.transactionSequenceNumber === undefined || header.transactionSequenceNumber === matcher.transactionSequenceNumber) && (header.commandIdentifier === matcher.commandId || // defaultRsp (header.frameControl.frameType === Zcl.FrameType.GLOBAL && header.commandIdentifier === 0x0b && (matcher.defaultRspCommandId === undefined || payload.data[payload.data.byteLength - 2] === matcher.defaultRspCommandId))) ); } public abstract start(): Promise; public abstract stop(): Promise; public abstract getCoordinatorIEEE(): Promise; public abstract getCoordinatorVersion(): Promise; public abstract reset(type: "soft" | "hard"): Promise; public abstract supportsBackup(): Promise; public abstract backup(ieeeAddressesInDatabase: string[]): Promise; public abstract getNetworkParameters(): Promise; public abstract addInstallCode(ieeeAddress: string, key: Buffer, hashed: boolean): Promise; public abstract waitFor( networkAddress: number, endpoint: number, frameType: Zcl.FrameType, direction: Zcl.Direction, transactionSequenceNumber: number | undefined, clusterId: number, commandId: number, defaultRspCommandId: number | undefined, timeout: number, ): {promise: Promise; cancel: () => void}; /** * ZDO */ public abstract sendZdo( ieeeAddress: string, networkAddress: number, clusterId: Zdo.ClusterId, payload: Buffer, disableResponse: true, ): Promise; public abstract sendZdo( ieeeAddress: string, networkAddress: number, clusterId: K, payload: Buffer, disableResponse: false, ): Promise; public abstract sendZdo( ieeeAddress: string, networkAddress: number, clusterId: K, payload: Buffer, disableResponse: boolean, ): Promise; public abstract permitJoin(seconds: number, networkAddress?: number): Promise; /** * ZCL */ public abstract sendZclFrameToEndpoint( ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: Zcl.Frame, timeout: number, disableResponse: boolean, disableRecovery: boolean, sourceEndpoint?: number, profileId?: number, ): Promise; public abstract sendZclFrameToGroup(groupID: number, zclFrame: Zcl.Frame, sourceEndpoint?: number, profileId?: number): Promise; public abstract sendZclFrameToAll( endpoint: number, zclFrame: Zcl.Frame, sourceEndpoint: number, destination: BroadcastAddress, profileId?: number, ): Promise; /** * InterPAN */ public abstract setChannelInterPAN(channel: number): Promise; public abstract sendZclFrameInterPANToIeeeAddr(zclFrame: Zcl.Frame, ieeeAddress: string): Promise; public abstract sendZclFrameInterPANBroadcast(zclFrame: Zcl.Frame, timeout: number, disableResponse: false): Promise; public abstract sendZclFrameInterPANBroadcast(zclFrame: Zcl.Frame, timeout: number, disableResponse: true): Promise; public abstract sendZclFrameInterPANBroadcast( zclFrame: Zcl.Frame, timeout: number, disableResponse: boolean, ): Promise; public abstract restoreChannelInterPAN(): Promise; } export default Adapter;