UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

228 lines (197 loc) 8.27 kB
/* v8 ignore start */ import type {KeyValue} from "../../controller/tstype"; import type {DataType} from "../../zspec/zcl"; import {BuffaloZcl} from "../../zspec/zcl/buffaloZcl"; import type {BuffaloZclDataType} from "../../zspec/zcl/definition/enums"; import type {BuffaloZclOptions} from "../../zspec/zcl/definition/tstype"; import {ClusterId as ZdoClusterId} from "../../zspec/zdo"; import {BuffaloZdo} from "../../zspec/zdo/buffaloZdo"; import type {GenericZdoResponse} from "../../zspec/zdo/definition/tstypes"; import {FRAMES, type ParamsDesc, ZBOSS_COMMAND_ID_TO_ZDO_RSP_CLUSTER_ID} from "./commands"; import {BuffaloZBOSSDataType, type CommandId} from "./enums"; export class ZBOSSBuffaloZcl extends BuffaloZcl { // biome-ignore lint/suspicious/noExplicitAny: API public override write(type: DataType | BuffaloZclDataType | BuffaloZBOSSDataType, value: any, options: BuffaloZclOptions): void { switch (type) { case BuffaloZBOSSDataType.EXTENDED_PAN_ID: { this.writeBuffer(value, 8); break; } default: { super.write(type as DataType | BuffaloZclDataType, value, options); break; } } } // biome-ignore lint/suspicious/noExplicitAny: API public override read(type: DataType | BuffaloZclDataType | BuffaloZBOSSDataType, options: BuffaloZclOptions): any { switch (type) { case BuffaloZBOSSDataType.EXTENDED_PAN_ID: { return this.readBuffer(8); } default: { return super.read(type as DataType | BuffaloZclDataType, options); } } } public writeByDesc(payload: KeyValue, params: ParamsDesc[]): number { const start = this.getPosition(); for (const parameter of params) { const options: BuffaloZclOptions = {}; if (parameter.condition && !parameter.condition(payload, this)) { continue; } if (parameter.options) parameter.options(payload, options); if (parameter.type === BuffaloZBOSSDataType.LIST_TYPED && parameter.typed) { const internalPaload = payload[parameter.name]; for (const value of internalPaload) { this.writeByDesc(value, parameter.typed); } } else { this.write(parameter.type as DataType, payload[parameter.name], options); } } return this.getPosition() - start; } public readByDesc(params: ParamsDesc[]): KeyValue { const payload: KeyValue = {}; for (const parameter of params) { const options: BuffaloZclOptions = {payload}; if (parameter.condition && !parameter.condition(payload, this)) { continue; } if (parameter.options) parameter.options(payload, options); if (parameter.type === BuffaloZBOSSDataType.LIST_TYPED && parameter.typed) { payload[parameter.name] = []; if (!this.isMore()) break; for (let i = 0; i < (options.length || 0); i++) { const internalPaload = this.readByDesc(parameter.typed); payload[parameter.name].push(internalPaload); } } else { if (!this.isMore()) break; payload[parameter.name] = this.read(parameter.type as DataType, options); } } return payload; } } function getFrameDesc(type: FrameType, key: CommandId): ParamsDesc[] { const frameDesc = FRAMES[key]; if (!frameDesc) throw new Error(`Unrecognized frame type from FrameID ${key}`); switch (type) { case FrameType.REQUEST: return frameDesc.request || []; case FrameType.RESPONSE: return frameDesc.response || []; case FrameType.INDICATION: return frameDesc.indication || []; } } function fixNonStandardZdoRspPayload(clusterId: ZdoClusterId, buffer: Buffer): Buffer { switch (clusterId) { case ZdoClusterId.NODE_DESCRIPTOR_RESPONSE: case ZdoClusterId.POWER_DESCRIPTOR_RESPONSE: case ZdoClusterId.ACTIVE_ENDPOINTS_RESPONSE: case ZdoClusterId.MATCH_DESCRIPTORS_RESPONSE: { // flip nwkAddress from end to start return Buffer.concat([buffer.subarray(0, 1), buffer.subarray(-2), buffer.subarray(1, -2)]); } case ZdoClusterId.SIMPLE_DESCRIPTOR_RESPONSE: { // flip nwkAddress from end to start // add length after nwkAddress // move outClusterCount before inClusterList const inClusterListSize = buffer[7] * 2; // uint16 return Buffer.concat([ buffer.subarray(0, 1), // status buffer.subarray(-2), // nwkAddress Buffer.from([buffer.length - 3 /* status + nwkAddress */]), buffer.subarray(1, 8), // endpoint>inClusterCount buffer.subarray(9, 9 + inClusterListSize), // inClusterList buffer.subarray(8, 9), // outClusterCount buffer.subarray(9 + inClusterListSize, -2), // outClusterList ]); } } return buffer; } export function readZBOSSFrame(buffer: Buffer): ZBOSSFrame { const buf = new ZBOSSBuffaloZcl(buffer); const version = buf.readUInt8(); const type: FrameType = buf.readUInt8(); const commandId: CommandId = buf.readUInt16(); const tsn = type === FrameType.REQUEST || type === FrameType.RESPONSE ? buf.readUInt8() : 0; const zdoResponseClusterId = type === FrameType.RESPONSE || type === FrameType.INDICATION ? ZBOSS_COMMAND_ID_TO_ZDO_RSP_CLUSTER_ID[commandId] : undefined; if (zdoResponseClusterId !== undefined) { // FrameType.INDICATION has no tsn (above), no category const category = type === FrameType.RESPONSE ? buf.readUInt8() : undefined; const zdoPayload = fixNonStandardZdoRspPayload(zdoResponseClusterId, buffer.subarray(type === FrameType.RESPONSE ? 6 : 4)); const zdo = BuffaloZdo.readResponse(false, zdoResponseClusterId, zdoPayload); return { version, type, commandId, tsn, payload: { category, zdoClusterId: zdoResponseClusterId, zdo, }, }; } return { version, type, commandId, tsn, payload: readPayload(type, commandId, buf), }; } export function writeZBOSSFrame(frame: ZBOSSFrame): Buffer { const buf = new ZBOSSBuffaloZcl(Buffer.alloc(247)); buf.writeInt8(frame.version); buf.writeInt8(frame.type); buf.writeUInt16(frame.commandId); buf.writeUInt8(frame.tsn); writePayload(frame.type, frame.commandId, frame.payload, buf); return buf.getWritten(); } export enum FrameType { REQUEST = 0, RESPONSE = 1, INDICATION = 2, } export interface ZBOSSFrame { version: number; type: FrameType; commandId: CommandId; tsn: number; payload: KeyValue & {zdoCluster?: ZdoClusterId; zdo?: GenericZdoResponse}; } export function makeFrame(type: FrameType, commandId: CommandId, params: KeyValue): ZBOSSFrame { const frameDesc = getFrameDesc(type, commandId); const payload: KeyValue = {}; for (const parameter of frameDesc) { // const options: BuffaloZclOptions = {payload}; if (parameter.condition && !parameter.condition(params, undefined)) { continue; } payload[parameter.name] = params[parameter.name]; } return { version: 0, type: type, commandId: commandId, tsn: 0, payload: payload, }; } function readPayload(type: FrameType, commandId: CommandId, buffalo: ZBOSSBuffaloZcl): KeyValue { const frameDesc = getFrameDesc(type, commandId); return buffalo.readByDesc(frameDesc); } function writePayload(type: FrameType, commandId: CommandId, payload: KeyValue, buffalo: ZBOSSBuffaloZcl): number { const frameDesc = getFrameDesc(type, commandId); return buffalo.writeByDesc(payload, frameDesc); }