zigbee-clusters
Version:
Zigbee Cluster Library for Node.js
278 lines (232 loc) • 9.54 kB
JavaScript
'use strict';
const { ZCLDataType, ZCLDataTypes, ZCLStruct } = require('./zclTypes');
const ZCLFrameControlBitmap = ZCLDataTypes.map8('clusterSpecific', null, 'manufacturerSpecific', 'directionToClient', 'disableDefaultResponse');
const ZCLStandardHeader = ZCLStruct('ZCLStandardHeader', {
frameControl: ZCLFrameControlBitmap,
trxSequenceNumber: ZCLDataTypes.data8,
cmdId: ZCLDataTypes.data8,
data: ZCLDataTypes.buffer,
});
const ZCLMfgSpecificHeader = ZCLStruct('ZCLMfgSpecificHeader', {
frameControl: ZCLFrameControlBitmap,
manufacturerId: ZCLDataTypes.uint16,
trxSequenceNumber: ZCLDataTypes.data8,
cmdId: ZCLDataTypes.data8,
data: ZCLDataTypes.buffer,
});
const ZCLConfigureReportingRecordStruct = ZCLStruct('ConfigureReportingRecord', {
direction: ZCLDataTypes.enum8({
reported: 0,
received: 1,
}),
attributeId: ZCLDataTypes.uint16,
attributeDataType: ZCLDataTypes.uint8,
minInterval: ZCLDataTypes.uint16,
maxInterval: ZCLDataTypes.uint16,
minChange: ZCLDataTypes.buffer,
timeoutPeriod: ZCLDataTypes.uint16,
});
const ZCLAttributeDataRecord = (withStatus, attributes) => new ZCLDataType(NaN, 'attributeRecord', -3, ((buf, v, i) => {
const startByte = i;
i += ZCLDataTypes.uint16.toBuffer(buf, v.id, i);
if (withStatus) {
i += ZCLDataTypes.enum8Status.toBuffer(buf, v.status, i);
}
if (!withStatus || v.status === 'SUCCESS') {
i += ZCLDataTypes.uint8.toBuffer(buf, attributes[v.id].type.id, i);
i += attributes[v.id].type.toBuffer(buf, v.value, i);
}
return i - startByte;
}), function fromBuf(buf, i, returnLength) {
i = i || 0;
const startByte = i;
const res = {};
if (buf.length >= i + Math.abs(this.length)) {
res.id = ZCLDataTypes.uint16.fromBuffer(buf, i);
i += ZCLDataTypes.uint16.length;
if (withStatus) {
res.status = ZCLDataTypes.enum8Status.fromBuffer(buf, i);
i += ZCLDataTypes.enum8Status.length;
}
if (!withStatus || res.status === 'SUCCESS') {
const dataTypeId = ZCLDataTypes.uint8.fromBuffer(buf, i);
i += ZCLDataTypes.uint8.length;
// don't use the attribute's type if the type in the data is 0 (noData).
// This breaks when the attribute ID is 0
const DataType = attributes[res.id] && dataTypeId !== 0
? attributes[res.id].type
: Object.values(ZCLDataTypes).find(type => type && type.id === dataTypeId);
if (!DataType) throw new TypeError(`Invalid Type for Attribute: ${res.id}`);
if (attributes[res.id]) {
res.name = attributes[res.id].name || `unknown_attr_${res.id}`;
}
const entry = DataType.fromBuffer(buf, i, true);
if (DataType.length > 0) {
i += DataType.length;
res.value = entry;
} else {
res.value = entry.result;
i += entry.length;
}
}
}
if (returnLength) {
return { result: res, length: i - startByte };
}
return res;
});
const ZCLConfigureReportingRecords = ({ withStatus } = {}) => new ZCLDataType(
NaN,
'configureReportingRecords',
-0,
((buf, value, i) => {
// toBuffer
const startByte = i;
// Loop each configure reporting record
for (const configureReportingRecord of value) {
// Remove the minChange property (because its size will vary)
const { minChange, direction } = configureReportingRecord;
// Check if this record is expected to have a status field
if (withStatus) {
ZCLDataTypes.enum8Status.toBuffer(buf, configureReportingRecord.status, i);
i += ZCLDataTypes.enum8Status.length;
}
ZCLConfigureReportingRecordStruct.fields.direction
.toBuffer(buf, configureReportingRecord.direction, i);
i += ZCLConfigureReportingRecordStruct.fields.direction.length;
ZCLConfigureReportingRecordStruct.fields.attributeId
.toBuffer(buf, configureReportingRecord.attributeId, i);
i += ZCLConfigureReportingRecordStruct.fields.attributeId.length;
// Nothing further to parse if it was not a success
if (withStatus && configureReportingRecord.status !== 'SUCCESS') {
continue;
}
if (direction === 'reported') {
// Determine the type of the minChange property (equal to attributeDataType of this record)
const minChangeDataType = Object.values(ZCLDataTypes)
.find(DataType => DataType.id === configureReportingRecord.attributeDataType);
ZCLConfigureReportingRecordStruct.fields.attributeDataType
.toBuffer(buf, configureReportingRecord.attributeDataType, i);
i += ZCLConfigureReportingRecordStruct.fields.attributeDataType.length;
ZCLConfigureReportingRecordStruct.fields.minInterval
.toBuffer(buf, configureReportingRecord.minInterval, i);
i += ZCLConfigureReportingRecordStruct.fields.minInterval.length;
ZCLConfigureReportingRecordStruct.fields.maxInterval
.toBuffer(buf, configureReportingRecord.maxInterval, i);
i += ZCLConfigureReportingRecordStruct.fields.maxInterval.length;
if (minChangeDataType && typeof minChange === 'number') {
minChangeDataType.toBuffer(buf, minChange, i);
i += minChangeDataType.length;
}
} else {
ZCLConfigureReportingRecordStruct.fields.timeoutPeriod
.toBuffer(buf, configureReportingRecord.timeoutPeriod, i);
i += ZCLConfigureReportingRecordStruct.fields.timeoutPeriod.length;
}
}
return i - startByte;
}), ((buf, i, returnLength) => {
// fromBuffer
i = i || 0;
// Keep a reference to the current buffer index while reading it
const startByte = i;
const res = [];
// Read the entire buffer
while (buf.length > i) {
// Parse status only if part of record struct (the read configuration response has a
// status field whereas the configure reporting command does not)
let status;
// Check if this record is expected to have a status field
if (withStatus) {
status = ZCLDataTypes.enum8Status.fromBuffer(buf, i);
i += ZCLDataTypes.enum8Status.length;
}
// Parse direction
const direction = ZCLConfigureReportingRecordStruct.fields.direction.fromBuffer(buf, i);
i += 1;
// Parse attributeId
const attributeId = ZCLConfigureReportingRecordStruct.fields.attributeId.fromBuffer(buf, i);
i += 2;
// Check if the status is success, only then we need to continue parsing for other values
if (withStatus && status !== 'SUCCESS') {
// Buffer parsing is complete
res.push({ status, direction, attributeId });
// Continue reading the buffer in a next iteration
continue;
}
// eslint-disable-next-line default-case
switch (direction) {
// Only if the direction is 'reported' are the minInterval/maxInterval and minChange
// properties available
case 'reported': {
// Parse attributeDataType, we need this to find the respective ZCLDataType in the
// next step
const attributeDataType = ZCLConfigureReportingRecordStruct.fields.attributeDataType
.fromBuffer(buf, i);
i += 1;
// Find the respective ZCLDataType, we need it later on to determine if the data type
// is analog or discrete
const parsedAttributeDataType = Object.values(ZCLDataTypes)
.find(x => x.id === attributeDataType);
// Parse minInterval
const minInterval = ZCLDataTypes.uint16.fromBuffer(buf, i);
i += 2;
// Parse maxInterval
const maxInterval = ZCLDataTypes.uint16.fromBuffer(buf, i);
i += 2;
// If the ZCLDataType is not analog there will be no minChange field
if (!parsedAttributeDataType.isAnalog) {
const resultRecord = {
direction, attributeId, attributeDataType, minInterval, maxInterval,
};
if (withStatus) resultRecord.status = status;
res.push(resultRecord);
// Continue reading the buffer in a next iteration
continue;
}
// Apparently the ZCLDataType is analog so we should parse the minChange field
const minChange = ZCLDataTypes[parsedAttributeDataType.shortName]
.fromBuffer(buf, i);
i += parsedAttributeDataType.length;
const resultRecord = {
direction,
attributeId,
attributeDataType,
minInterval,
maxInterval,
minChange,
};
if (withStatus) resultRecord.status = status;
res.push(resultRecord);
// Buffer parsing is complete
break;
}
// If the direction is 'received' we only have to parse the
case 'received': {
// timeoutPeriod
// Parse timeout period
const timeoutPeriod = ZCLConfigureReportingRecordStruct.fields.timeoutPeriod
.fromBuffer(buf, i);
i += 2;
const resultRecord = {
direction, attributeId, timeoutPeriod,
};
if (withStatus) resultRecord.status = status;
res.push(resultRecord);
// Buffer parsing is complete
break;
}
}
}
if (returnLength) {
return { result: res, length: i - startByte };
}
return res;
}),
);
module.exports = {
ZCLStandardHeader,
ZCLMfgSpecificHeader,
ZCLAttributeDataRecord,
ZCLConfigureReportingRecords,
};