UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

149 lines (123 loc) 5.98 kB
import assert from "node:assert"; import {ClusterId as ZdoClusterId} from "../../../zspec/zdo"; import {BuffaloZdo} from "../../../zspec/zdo/buffaloZdo"; import {Frame as UnpiFrame} from "../unpi"; import {MaxDataSize, Subsystem, Type} from "../unpi/constants"; import BuffaloZnp from "./buffaloZnp"; import Definition from "./definition"; import ParameterType from "./parameterType"; import type {BuffaloZnpOptions, MtCmd, MtParameter, MtType, ZpiObjectPayload} from "./tstype"; import {isMtCmdAreqZdo, isMtCmdSreqZdo} from "./utils"; const BufferAndListTypes = [ ParameterType.BUFFER, ParameterType.BUFFER8, ParameterType.BUFFER16, ParameterType.BUFFER18, ParameterType.BUFFER32, ParameterType.BUFFER42, ParameterType.BUFFER100, ParameterType.LIST_UINT16, ParameterType.LIST_NETWORK, ParameterType.LIST_UINT8, ]; type ZpiObjectType = "Request" | "Response"; export class ZpiObject<T extends ZpiObjectType = "Response"> { public readonly type: Type; public readonly subsystem: Subsystem; public readonly command: MtCmd; public readonly payload: ZpiObjectPayload; public readonly unpiFrame: T extends "Request" ? UnpiFrame : undefined; private constructor( type: Type, subsystem: Subsystem, command: MtCmd, payload: ZpiObjectPayload, unpiFrame: T extends "Request" ? UnpiFrame : undefined, ) { this.type = type; this.subsystem = subsystem; this.command = command; this.payload = payload; this.unpiFrame = unpiFrame; } public static createRequest(subsystem: Subsystem, command: string, payload: ZpiObjectPayload): ZpiObject<"Request"> { if (!Definition[subsystem]) { throw new Error(`Subsystem '${subsystem}' does not exist`); } const cmd = Definition[subsystem].find((c) => c.name === command); if (cmd === undefined || isMtCmdAreqZdo(cmd) || isMtCmdSreqZdo(cmd) || cmd.request === undefined) { throw new Error(`Command request '${command}' from subsystem '${subsystem}' not found`); } // Create the UnpiFrame const buffalo = new BuffaloZnp(Buffer.alloc(MaxDataSize)); for (const parameter of cmd.request) { const value = payload[parameter.name]; buffalo.write(parameter.parameterType, value, {}); } const buffer = buffalo.getWritten(); const unpiFrame = new UnpiFrame(cmd.type, subsystem, cmd.ID, buffer); return new ZpiObject(cmd.type, subsystem, cmd, payload, unpiFrame); } public static fromUnpiFrame(frame: UnpiFrame): ZpiObject<"Response"> { const cmd = Definition[frame.subsystem].find((c) => c.ID === frame.commandID); if (!cmd) { throw new Error(`CommandID '${frame.commandID}' from subsystem '${frame.subsystem}' not found`); } let payload: ZpiObjectPayload = {}; // hotpath AREQ & SREQ ZDO since payload is identical (no need to instantiate BuffaloZnp or parse generically) if (isMtCmdAreqZdo(cmd)) { if (cmd.zdoClusterId === ZdoClusterId.NETWORK_ADDRESS_RESPONSE || cmd.zdoClusterId === ZdoClusterId.IEEE_ADDRESS_RESPONSE) { // ZStack swaps the `startindex` and `numassocdev` compared to ZDO swap them back before handing to ZDO const startIndex = frame.data[11]; const assocDevCount = frame.data[12]; frame.data[11] = assocDevCount; frame.data[12] = startIndex; payload.zdo = BuffaloZdo.readResponse(false, cmd.zdoClusterId, frame.data); } else { payload.srcaddr = frame.data.readUInt16LE(0); payload.zdo = BuffaloZdo.readResponse(false, cmd.zdoClusterId, frame.data.subarray(2)); } } else if (isMtCmdSreqZdo(cmd)) { payload.status = frame.data.readUInt8(0); } else { const parameters = frame.type === Type.SRSP && cmd.type !== Type.AREQ ? cmd.response : cmd.request; assert( parameters, `CommandID '${frame.commandID}' from subsystem '${frame.subsystem}' cannot be a ` + `${frame.type === Type.SRSP ? "response" : "request"}`, ); payload = ZpiObject.readParameters(frame.data, parameters); } // GC UnpiFrame as early as possible, no longer needed return new ZpiObject(frame.type, frame.subsystem, cmd, payload, undefined); } private static readParameters(buffer: Buffer, parameters: MtParameter[]): ZpiObjectPayload { const buffalo = new BuffaloZnp(buffer); const result: ZpiObjectPayload = {}; for (const parameter of parameters) { const options: BuffaloZnpOptions = {}; if (BufferAndListTypes.includes(parameter.parameterType)) { // When reading a buffer, assume that the previous parsed parameter contains // the length of the buffer const lengthParameter = parameters[parameters.indexOf(parameter) - 1]; const length: MtType = result[lengthParameter.name]; if (typeof length === "number") { options.length = length; } } result[parameter.name] = buffalo.read(parameter.parameterType, options); } return result; } public isResetCommand(): boolean { return ( (this.command.name === "resetReq" && this.subsystem === Subsystem.SYS) || /* v8 ignore next */ (this.command.name === "systemReset" && this.subsystem === Subsystem.SAPI) ); } public toString(includePayload = true): string { const baseStr = `${Type[this.type]}: ${Subsystem[this.subsystem]} - ${this.command.name}`; return includePayload ? `${baseStr} - ${JSON.stringify(this.payload)}` : baseStr; } }