zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
296 lines • 13.2 kB
JavaScript
"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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZclFrame = void 0;
require("../../utils/patchBigIntSerialization");
const buffaloZcl_1 = require("./buffaloZcl");
const enums_1 = require("./definition/enums");
const Utils = __importStar(require("./utils"));
const zclHeader_1 = require("./zclHeader");
const ListTypes = [
enums_1.BuffaloZclDataType.LIST_UINT8,
enums_1.BuffaloZclDataType.LIST_UINT16,
enums_1.BuffaloZclDataType.LIST_UINT24,
enums_1.BuffaloZclDataType.LIST_UINT32,
enums_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;
}
toString() {
return JSON.stringify({ header: this.header, payload: this.payload, command: this.command });
}
/**
* Creating
*/
static create(frameType, direction, disableDefaultResponse, manufacturerCode, transactionSequenceNumber, commandKey, clusterKey, payload, customClusters, reservedBits = 0) {
const cluster = Utils.getCluster(clusterKey, manufacturerCode, customClusters);
const command = frameType === enums_1.FrameType.GLOBAL
? Utils.getGlobalCommand(commandKey)
: direction === enums_1.Direction.CLIENT_TO_SERVER
? cluster.getCommand(commandKey)
: cluster.getCommandResponse(commandKey);
const header = new zclHeader_1.ZclHeader({ reservedBits, frameType, direction, disableDefaultResponse, manufacturerSpecific: manufacturerCode != null }, manufacturerCode, transactionSequenceNumber, command.ID);
return new ZclFrame(header, payload, cluster, command);
}
toBuffer() {
const buffalo = new buffaloZcl_1.BuffaloZcl(Buffer.alloc(250));
this.header.write(buffalo);
if (this.header.isGlobal) {
this.writePayloadGlobal(buffalo);
}
else if (this.header.isSpecific) {
this.writePayloadCluster(buffalo);
}
else {
throw new Error(`Frametype '${this.header.frameControl.frameType}' not valid`);
}
return buffalo.getWritten();
}
writePayloadGlobal(buffalo) {
const command = Utils.getFoundationCommand(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 === enums_1.BuffaloZclDataType.USE_DATA_TYPE && typeof entry.dataType === 'number') {
// We need to grab the dataType to parse useDataType
options.dataType = entry.dataType;
}
buffalo.write(parameter.type, entry[parameter.name], options);
}
}
}
else if (command.parseStrategy === 'flat') {
for (const parameter of command.parameters) {
buffalo.write(parameter.type, this.payload[parameter.name], {});
}
}
else {
/* istanbul ignore else */
if (command.parseStrategy === 'oneof') {
/* istanbul ignore else */
if (Utils.isFoundationDiscoverRsp(command.ID)) {
buffalo.writeUInt8(this.payload.discComplete);
for (const entry of this.payload.attrInfos) {
for (const parameter of command.parameters) {
buffalo.write(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[parameter.name] == undefined) {
throw new Error(`Parameter '${parameter.name}' is missing`);
}
buffalo.write(parameter.type, this.payload[parameter.name], {});
}
}
/**
* Parsing
*/
static fromBuffer(clusterID, header, buffer, customClusters) {
if (!header) {
throw new Error('Invalid ZclHeader.');
}
const buffalo = new buffaloZcl_1.BuffaloZcl(buffer, header.length);
const cluster = Utils.getCluster(clusterID, header.manufacturerCode, customClusters);
const command = header.isGlobal
? Utils.getGlobalCommand(header.commandIdentifier)
: header.frameControl.direction === enums_1.Direction.CLIENT_TO_SERVER
? cluster.getCommand(header.commandIdentifier)
: cluster.getCommandResponse(header.commandIdentifier);
const payload = this.parsePayload(header, cluster, buffalo);
return new ZclFrame(header, payload, cluster, command);
}
static parsePayload(header, cluster, buffalo) {
if (header.isGlobal) {
return this.parsePayloadGlobal(header, buffalo);
}
else if (header.isSpecific) {
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 === enums_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;
}
}
payload[parameter.name] = buffalo.read(parameter.type, options);
}
return payload;
}
static parsePayloadGlobal(header, buffalo) {
const command = Utils.getFoundationCommand(header.commandIdentifier);
if (command.parseStrategy === 'repetitive') {
const payload = [];
while (buffalo.isMore()) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const entry = {};
for (const parameter of command.parameters) {
const options = {};
if (!this.conditionsValid(parameter, entry, buffalo.getBuffer().length - buffalo.getPosition())) {
continue;
}
if (parameter.type === enums_1.BuffaloZclDataType.USE_DATA_TYPE && typeof entry.dataType === 'number') {
// We need to grab the dataType to parse useDataType
options.dataType = entry.dataType;
if (entry.dataType === enums_1.DataType.CHAR_STR && entry.attrId === 65281) {
// [workaround] parse char str as Xiaomi struct
options.dataType = enums_1.BuffaloZclDataType.MI_STRUCT;
}
}
entry[parameter.name] = buffalo.read(parameter.type, options);
// TODO: not needed, but temp workaroudn to make payload equal to that of zcl-packet
// XXX: is this still needed?
if (parameter.type === enums_1.BuffaloZclDataType.USE_DATA_TYPE && entry.dataType === enums_1.DataType.STRUCT) {
entry['structElms'] = entry.attrData;
entry['numElms'] = entry.attrData.length;
}
}
payload.push(entry);
}
return payload;
}
else if (command.parseStrategy === 'flat') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payload = {};
for (const parameter of command.parameters) {
payload[parameter.name] = buffalo.read(parameter.type, {});
}
return payload;
}
else {
/* istanbul ignore else */
if (command.parseStrategy === 'oneof') {
/* istanbul ignore else */
if (Utils.isFoundationDiscoverRsp(command.ID)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payload = {
discComplete: buffalo.readUInt8(),
attrInfos: [],
};
while (buffalo.isMore()) {
const entry = {};
for (const parameter of command.parameters) {
entry[parameter.name] = buffalo.read(parameter.type, {});
}
payload.attrInfos.push(entry);
}
return payload;
}
}
}
}
/**
* Utils
*/
static conditionsValid(parameter, entry, remainingBufferBytes) {
if (parameter.conditions) {
for (const condition of parameter.conditions) {
switch (condition.type) {
case enums_1.ParameterCondition.STATUS_EQUAL: {
if (entry.status !== condition.value)
return false;
break;
}
case enums_1.ParameterCondition.STATUS_NOT_EQUAL: {
if (entry.status === condition.value)
return false;
break;
}
case enums_1.ParameterCondition.DIRECTION_EQUAL: {
if (entry.direction !== condition.value)
return false;
break;
}
case enums_1.ParameterCondition.BITMASK_SET: {
if ((entry[condition.param] & condition.mask) !== condition.mask)
return false;
break;
}
case enums_1.ParameterCondition.BITFIELD_ENUM: {
if (((entry[condition.param] >> condition.offset) & ((1 << condition.size) - 1)) !== condition.value)
return false;
break;
}
case enums_1.ParameterCondition.MINIMUM_REMAINING_BUFFER_BYTES: {
if (remainingBufferBytes != null && remainingBufferBytes < condition.value)
return false;
break;
}
case enums_1.ParameterCondition.DATA_TYPE_CLASS_EQUAL: {
if (Utils.getDataTypeClass(entry.dataType) !== condition.value)
return false;
break;
}
}
}
}
return true;
}
isCluster(clusterName) {
return this.cluster.name === clusterName;
}
// List of commands is not completed, feel free to add more.
isCommand(commandName) {
return this.command.name === commandName;
}
}
exports.ZclFrame = ZclFrame;
//# sourceMappingURL=zclFrame.js.map