UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

354 lines 16.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const definition_1 = require("./definition"); const Utils = __importStar(require("./utils")); const buffaloZcl_1 = __importDefault(require("./buffaloZcl")); const definition_2 = require("./definition"); const MINIMAL_FRAME_LENGTH = 3; const ListTypes = [ definition_1.BuffaloZclDataType.LIST_UINT8, definition_1.BuffaloZclDataType.LIST_UINT16, definition_1.BuffaloZclDataType.LIST_UINT24, definition_1.BuffaloZclDataType.LIST_UINT32, definition_1.BuffaloZclDataType.LIST_ZONEINFO, ]; class ZclFrame { Header; Payload; Cluster; Command; constructor(header, payload, cluster, command) { this.Header = header; this.Payload = payload; this.Cluster = cluster; this.Command = command; } /** * Creating */ static create(frameType, direction, disableDefaultResponse, manufacturerCode, transactionSequenceNumber, commandKey, clusterKey, payload, reservedBits = 0) { const cluster = Utils.getCluster(clusterKey, manufacturerCode != null ? manufacturerCode : null); let command = null; if (frameType === definition_2.FrameType.GLOBAL) { command = Utils.getGlobalCommand(commandKey); } else { command = direction === definition_1.Direction.CLIENT_TO_SERVER ? cluster.getCommand(commandKey) : cluster.getCommandResponse(commandKey); } const header = { frameControl: { reservedBits, frameType, direction, disableDefaultResponse, manufacturerSpecific: manufacturerCode != null, }, transactionSequenceNumber, manufacturerCode, commandIdentifier: command.ID, }; return new ZclFrame(header, payload, cluster, command); } toBuffer() { const buffalo = new buffaloZcl_1.default(Buffer.alloc(250)); this.writeHeader(buffalo); if (this.Header.frameControl.frameType === definition_2.FrameType.GLOBAL) { this.writePayloadGlobal(buffalo); } else if (this.Header.frameControl.frameType === definition_2.FrameType.SPECIFIC) { this.writePayloadCluster(buffalo); } else { throw new Error(`Frametype '${this.Header.frameControl.frameType}' not valid`); } return buffalo.getWritten(); } writeHeader(buffalo) { const frameControl = ((this.Header.frameControl.frameType & 0x03) | (((this.Header.frameControl.manufacturerSpecific ? 1 : 0) << 2) & 0x04) | ((this.Header.frameControl.direction << 3) & 0x08) | (((this.Header.frameControl.disableDefaultResponse ? 1 : 0) << 4) & 0x10) | ((this.Header.frameControl.reservedBits << 5) & 0xE0)); buffalo.writeUInt8(frameControl); if (this.Header.frameControl.manufacturerSpecific) { buffalo.writeUInt16(this.Header.manufacturerCode); } buffalo.writeUInt8(this.Header.transactionSequenceNumber); buffalo.writeUInt8(this.Header.commandIdentifier); } writePayloadGlobal(buffalo) { const command = Object.values(definition_1.Foundation).find((c) => c.ID === this.Command.ID); if (command.parseStrategy === 'repetitive') { for (const entry of this.Payload) { for (const parameter of command.parameters) { const options = {}; if (!ZclFrame.conditionsValid(parameter, entry, null)) { continue; } if (parameter.type === definition_1.BuffaloZclDataType.USE_DATA_TYPE && typeof entry.dataType === 'number') { // We need to grab the dataType to parse useDataType options.dataType = definition_1.DataType[entry.dataType]; } const typeStr = ZclFrame.getDataTypeString(parameter.type); buffalo.write(typeStr, entry[parameter.name], options); } } } else if (command.parseStrategy === 'flat') { for (const parameter of command.parameters) { buffalo.write(definition_1.DataType[parameter.type], this.Payload[parameter.name], {}); } } else { /* istanbul ignore else */ if (command.parseStrategy === 'oneof') { /* istanbul ignore else */ if ([definition_1.Foundation.discoverRsp, definition_1.Foundation.discoverCommandsRsp, definition_1.Foundation.discoverCommandsGenRsp, definition_1.Foundation.discoverExtRsp].includes(command)) { buffalo.writeUInt8(this.Payload.discComplete); for (const entry of this.Payload.attrInfos) { for (const parameter of command.parameters) { buffalo.write(definition_1.DataType[parameter.type], entry[parameter.name], {}); } } } } } } writePayloadCluster(buffalo) { for (const parameter of this.Command.parameters) { if (!ZclFrame.conditionsValid(parameter, this.Payload, null)) { continue; } if (!this.Payload.hasOwnProperty(parameter.name)) { throw new Error(`Parameter '${parameter.name}' is missing`); } const typeStr = ZclFrame.getDataTypeString(parameter.type); buffalo.write(typeStr, this.Payload[parameter.name], {}); } } /** * Parsing */ static fromBuffer(clusterID, buffer) { if (buffer.length < MINIMAL_FRAME_LENGTH) { throw new Error("ZclFrame length is lower than minimal length"); } const buffalo = new buffaloZcl_1.default(buffer); const header = this.parseHeader(buffalo); let command = null; if (header.frameControl.frameType === definition_2.FrameType.GLOBAL) { command = Utils.getGlobalCommand(header.commandIdentifier); } else { const cluster = Utils.getCluster(clusterID, header.manufacturerCode); command = header.frameControl.direction === definition_1.Direction.CLIENT_TO_SERVER ? cluster.getCommand(header.commandIdentifier) : cluster.getCommandResponse(header.commandIdentifier); } const cluster = Utils.getCluster(clusterID, header.frameControl.manufacturerSpecific ? header.manufacturerCode : null); const payload = this.parsePayload(header, cluster, buffalo); return new ZclFrame(header, payload, cluster, command); } static parseHeader(buffalo) { const frameControlValue = buffalo.readUInt8(); const frameControl = { frameType: frameControlValue & 0x03, manufacturerSpecific: ((frameControlValue >> 2) & 0x01) === 1, direction: (frameControlValue >> 3) & 0x01, disableDefaultResponse: ((frameControlValue >> 4) & 0x01) === 1, reservedBits: frameControlValue >> 5, }; let manufacturerCode = null; if (frameControl.manufacturerSpecific) { manufacturerCode = buffalo.readUInt16(); } const transactionSequenceNumber = buffalo.readUInt8(); const commandIdentifier = buffalo.readUInt8(); return { frameControl, transactionSequenceNumber, manufacturerCode, commandIdentifier }; } static parsePayload(header, cluster, buffalo) { if (header.frameControl.frameType === definition_2.FrameType.GLOBAL) { return this.parsePayloadGlobal(header, buffalo); } else if (header.frameControl.frameType === definition_2.FrameType.SPECIFIC) { return this.parsePayloadCluster(header, cluster, buffalo); } else { throw new Error(`Unsupported frameType '${header.frameControl.frameType}'`); } } static parsePayloadCluster(header, cluster, buffalo) { const command = header.frameControl.direction === definition_1.Direction.CLIENT_TO_SERVER ? cluster.getCommand(header.commandIdentifier) : cluster.getCommandResponse(header.commandIdentifier); const payload = {}; for (const parameter of command.parameters) { const options = { payload }; if (!this.conditionsValid(parameter, payload, buffalo.getBuffer().length - buffalo.getPosition())) { continue; } if (ListTypes.includes(parameter.type)) { const lengthParameter = command.parameters[command.parameters.indexOf(parameter) - 1]; const length = payload[lengthParameter.name]; /* istanbul ignore else */ if (typeof length === 'number') { options.length = length; } } const typeStr = ZclFrame.getDataTypeString(parameter.type); payload[parameter.name] = buffalo.read(typeStr, options); } return payload; } static parsePayloadGlobal(header, buffalo) { const command = Object.values(definition_1.Foundation).find((c) => c.ID === header.commandIdentifier); if (command.parseStrategy === 'repetitive') { const payload = []; while (buffalo.isMore()) { const entry = {}; for (const parameter of command.parameters) { const options = {}; if (!this.conditionsValid(parameter, entry, buffalo.getBuffer().length - buffalo.getPosition())) { continue; } if (parameter.type === definition_1.BuffaloZclDataType.USE_DATA_TYPE && typeof entry.dataType === 'number') { // We need to grab the dataType to parse useDataType options.dataType = definition_1.DataType[entry.dataType]; if (entry.dataType === definition_1.DataType.charStr && entry.hasOwnProperty('attrId')) { // For Xiaomi struct parsing we need to pass the attributeID. options.attrId = entry.attrId; } } const typeStr = definition_1.DataType[parameter.type] != null ? definition_1.DataType[parameter.type] : definition_1.BuffaloZclDataType[parameter.type]; entry[parameter.name] = buffalo.read(typeStr, options); // TODO: not needed, but temp workaroudn to make payload equal to that of zcl-packet if (parameter.type === definition_1.BuffaloZclDataType.USE_DATA_TYPE && entry.dataType === definition_1.DataType.struct) { entry['structElms'] = entry.attrData; entry['numElms'] = entry.attrData.length; } } payload.push(entry); } return payload; } else if (command.parseStrategy === 'flat') { const payload = {}; for (const parameter of command.parameters) { payload[parameter.name] = buffalo.read(definition_1.DataType[parameter.type], {}); } return payload; } else { /* istanbul ignore else */ if (command.parseStrategy === 'oneof') { /* istanbul ignore else */ if ([definition_1.Foundation.discoverRsp, definition_1.Foundation.discoverCommandsRsp, definition_1.Foundation.discoverCommandsGenRsp, definition_1.Foundation.discoverExtRsp].includes(command)) { const payload = {}; payload.discComplete = buffalo.readUInt8(); payload.attrInfos = []; while (buffalo.isMore()) { const entry = {}; for (const parameter of command.parameters) { entry[parameter.name] = buffalo.read(definition_1.DataType[parameter.type], {}); } payload.attrInfos.push(entry); } return payload; } } } } /** * Utils */ static getDataTypeString(dataType) { return definition_1.DataType[dataType] != null ? definition_1.DataType[dataType] : definition_1.BuffaloZclDataType[dataType]; } static conditionsValid(parameter, entry, remainingBufferBytes) { if (parameter.conditions) { const failedCondition = parameter.conditions.find((condition) => { if (condition.type === 'statusEquals') { return entry.status !== condition.value; } else if (condition.type == 'statusNotEquals') { return entry.status === condition.value; } else if (condition.type == 'directionEquals') { return entry.direction !== condition.value; } else if (condition.type == 'bitMaskSet') { return (entry[condition.param] & condition.mask) !== condition.mask; } else if (condition.type == 'bitFieldEnum') { return ((entry[condition.param] >> condition.offset) & ((1 << condition.size) - 1)) !== condition.value; } else if (remainingBufferBytes != null && condition.type == 'minimumRemainingBufferBytes') { return remainingBufferBytes < condition.value; } else { /* istanbul ignore else */ if (condition.type == 'dataTypeValueTypeEquals') { return Utils.IsDataTypeAnalogOrDiscrete(entry.dataType) !== condition.value; } } }); if (failedCondition) { return false; } } return true; } isSpecific() { return this.Header.frameControl.frameType === definition_2.FrameType.SPECIFIC; } isGlobal() { return this.Header.frameControl.frameType === definition_2.FrameType.GLOBAL; } // List of clusters is not completed, feel free to add more. isCluster(clusterName) { return this.Cluster.name === clusterName; } // List of commands is not completed, feel free to add more. isCommand(commandName) { return this.getCommand().name === commandName; } getCommand() { let command = null; if (this.Header.frameControl.frameType === definition_2.FrameType.GLOBAL) { command = Utils.getGlobalCommand(this.Header.commandIdentifier); } else { command = this.Header.frameControl.direction === definition_1.Direction.CLIENT_TO_SERVER ? this.Cluster.getCommand(this.Header.commandIdentifier) : this.Cluster.getCommandResponse(this.Header.commandIdentifier); } return command; } } exports.default = ZclFrame; //# sourceMappingURL=zclFrame.js.map