zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
926 lines (863 loc) • 42.3 kB
text/typescript
import * as Zcl from "../../../src/zspec/zcl";
import {BuffaloZcl} from "../../../src/zspec/zcl/buffaloZcl";
import {uint16To8Array, uint32To8Array, uint56To8Array} from "../../utils/math";
/** Header with Global frame type */
const GLOBAL_HEADER = new Zcl.Header(
{
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
123,
Zcl.Foundation.read.ID,
);
const GLOBAL_HEADER_BUFFER = Buffer.from([0, 123, Zcl.Foundation.read.ID]);
/** Header with Global frame type and server to client direction */
const GLOBAL_RSP_HEADER = new Zcl.Header(
{
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.SERVER_TO_CLIENT,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
78,
Zcl.Foundation.readRsp.ID,
);
const GLOBAL_RSP_HEADER_BUFFER = Buffer.from([8, 78, Zcl.Foundation.readRsp.ID]);
/** Header with Global frame type with command report */
const GLOBAL_HEADER_REPORT = new Zcl.Header(
{
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
123,
Zcl.Foundation.report.ID,
);
const GLOBAL_HEADER_REPORT_BUFFER = Buffer.from([0, 123, Zcl.Foundation.report.ID]);
/** Header with Global frame type and server to client direction including condition-based parameters */
const GLOBAL_CONDITION_HEADER = new Zcl.Header(
{
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.SERVER_TO_CLIENT,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
78,
Zcl.Foundation.configReport.ID,
);
const GLOBAL_CONDITION_HEADER_BUFFER = Buffer.from([8, 78, Zcl.Foundation.configReport.ID]);
/** Header with Specific frame type */
const SPECIFIC_HEADER = new Zcl.Header(
{
frameType: Zcl.FrameType.SPECIFIC,
manufacturerSpecific: false,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
44,
Zcl.Clusters.genAlarms.commands.getAlarm.ID,
);
const SPECIFIC_HEADER_BUFFER = Buffer.from([1, 44, Zcl.Clusters.genAlarms.commands.getAlarm.ID]);
/** Header with Specific frame type including condition-based parameters */
const SPECIFIC_CONDITION_HEADER = new Zcl.Header(
{
frameType: Zcl.FrameType.SPECIFIC,
manufacturerSpecific: false,
direction: Zcl.Direction.SERVER_TO_CLIENT,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
45,
Zcl.Clusters.genOta.commandsResponse.queryNextImageResponse.ID,
);
const SPECIFIC_CONDITION_HEADER_BUFFER = Buffer.from([9, 45, Zcl.Clusters.genOta.commandsResponse.queryNextImageResponse.ID]);
/** Header with Specific frame type and server to client direction */
const SPECIFIC_RSP_HEADER = new Zcl.Header(
{
frameType: Zcl.FrameType.SPECIFIC,
manufacturerSpecific: false,
direction: Zcl.Direction.SERVER_TO_CLIENT,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
53,
Zcl.Clusters.genAlarms.commandsResponse.alarm.ID,
);
const SPECIFIC_RSP_HEADER_BUFFER = Buffer.from([9, 53, Zcl.Clusters.genAlarms.commandsResponse.alarm.ID]);
/** Header with manufacturer-specific */
const MANUF_SPE_HEADER = new Zcl.Header(
{
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: true,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 0,
},
Zcl.ManufacturerCode.AAC_TECHNOLOGIES_HOLDING,
234,
Zcl.Foundation.read.ID,
);
const MANUF_SPE_HEADER_BUFFER = Buffer.from([4, ...uint16To8Array(Zcl.ManufacturerCode.AAC_TECHNOLOGIES_HOLDING), 234, Zcl.Foundation.read.ID]);
/** Frame of Global type */
const GLOBAL_FRAME = Zcl.Frame.create(
GLOBAL_HEADER.frameControl.frameType,
GLOBAL_HEADER.frameControl.direction,
GLOBAL_HEADER.frameControl.disableDefaultResponse,
GLOBAL_HEADER.manufacturerCode,
GLOBAL_HEADER.transactionSequenceNumber,
GLOBAL_HEADER.commandIdentifier,
Zcl.Clusters.genBasic.ID,
[{attrId: 256}] /*payload*/,
{} /*custom clusters*/,
GLOBAL_HEADER.frameControl.reservedBits,
);
const GLOBAL_FRAME_BUFFER = Buffer.concat([GLOBAL_HEADER_BUFFER, Buffer.from(uint16To8Array(256))]);
const GLOBAL_FRAME_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":0,"direction":0,"disableDefaultResponse":false,"manufacturerSpecific":false},"transactionSequenceNumber":123,"commandIdentifier":0},"payload":[{"attrId":256}],"command":{"ID":0,"name":"read","parameters":[{"name":"attrId","type":33}],"response":1}}`;
/** Frame of Global type with BigInt */
const GLOBAL_FRAME_BIG_INT = Zcl.Frame.create(
GLOBAL_HEADER_REPORT.frameControl.frameType,
GLOBAL_HEADER_REPORT.frameControl.direction,
GLOBAL_HEADER_REPORT.frameControl.disableDefaultResponse,
GLOBAL_HEADER_REPORT.manufacturerCode,
GLOBAL_HEADER_REPORT.transactionSequenceNumber,
GLOBAL_HEADER_REPORT.commandIdentifier,
Zcl.Clusters.haApplianceIdentification.ID,
[{attrId: 0, dataType: Zcl.DataType.UINT56, attrData: 200n}] /*payload*/,
{} /*custom clusters*/,
GLOBAL_HEADER_REPORT.frameControl.reservedBits,
);
const GLOBAL_FRAME_BIG_INT_BUFFER = Buffer.concat([
GLOBAL_HEADER_REPORT_BUFFER,
Buffer.from(uint16To8Array(0)),
Buffer.from([Zcl.DataType.UINT56]),
Buffer.from(uint56To8Array(200n)),
]);
const GLOBAL_FRAME_BIG_INT_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":0,"direction":0,"disableDefaultResponse":false,"manufacturerSpecific":false},"transactionSequenceNumber":123,"commandIdentifier":10},"payload":[{"attrId":0,"dataType":38,"attrData":"200"}],"command":{"ID":10,"name":"report","parameters":[{"name":"attrId","type":33},{"name":"dataType","type":32},{"name":"attrData","type":1000}]}}`;
/** Frame of Global type and response command */
const GLOBAL_RSP_FRAME = Zcl.Frame.create(
GLOBAL_RSP_HEADER.frameControl.frameType,
GLOBAL_RSP_HEADER.frameControl.direction,
GLOBAL_RSP_HEADER.frameControl.disableDefaultResponse,
GLOBAL_RSP_HEADER.manufacturerCode,
GLOBAL_RSP_HEADER.transactionSequenceNumber,
GLOBAL_RSP_HEADER.commandIdentifier,
Zcl.Clusters.genPowerCfg.ID,
[{attrId: 256, status: Zcl.Status.SUCCESS, dataType: Zcl.DataType.ENUM8, attrData: 127}] /*payload*/,
{} /*custom clusters*/,
GLOBAL_RSP_HEADER.frameControl.reservedBits,
);
const GLOBAL_RSP_FRAME_BUFFER = Buffer.concat([
GLOBAL_RSP_HEADER_BUFFER,
Buffer.from([...uint16To8Array(256), Zcl.Status.SUCCESS, Zcl.DataType.ENUM8, 127]),
]);
const GLOBAL_RSP_FRAME_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":0,"direction":1,"disableDefaultResponse":false,"manufacturerSpecific":false},"transactionSequenceNumber":78,"commandIdentifier":1},"payload":[{"attrId":256,"status":0,"dataType":48,"attrData":127}],"command":{"ID":1,"name":"readRsp","parameters":[{"name":"attrId","type":33},{"name":"status","type":32},{"name":"dataType","type":32,"conditions":[{"type":"statusEquals","value":0}]},{"name":"attrData","type":1000,"conditions":[{"type":"statusEquals","value":0}]}]}}`;
/** Frame of Global type with no payload */
const GLOBAL_FRAME_NO_PAYLOAD = Zcl.Frame.create(
GLOBAL_HEADER.frameControl.frameType,
GLOBAL_HEADER.frameControl.direction,
GLOBAL_HEADER.frameControl.disableDefaultResponse,
GLOBAL_HEADER.manufacturerCode,
GLOBAL_HEADER.transactionSequenceNumber,
GLOBAL_HEADER.commandIdentifier,
Zcl.Clusters.genBasic.ID,
[] /*payload*/,
{} /*custom clusters*/,
GLOBAL_HEADER.frameControl.reservedBits,
);
const GLOBAL_FRAME_NO_PAYLOAD_BUFFER = Buffer.concat([GLOBAL_HEADER_BUFFER]);
const GLOBAL_FRAME_NO_PAYLOAD_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":0,"direction":0,"disableDefaultResponse":false,"manufacturerSpecific":false},"transactionSequenceNumber":123,"commandIdentifier":0},"payload":[],"command":{"ID":0,"name":"read","parameters":[{"name":"attrId","type":33}],"response":1}}`;
/** Frame of Global type with condition-based parameters */
const GLOBAL_CONDITION_FRAME = Zcl.Frame.create(
GLOBAL_CONDITION_HEADER.frameControl.frameType,
GLOBAL_CONDITION_HEADER.frameControl.direction,
GLOBAL_CONDITION_HEADER.frameControl.disableDefaultResponse,
GLOBAL_CONDITION_HEADER.manufacturerCode,
GLOBAL_CONDITION_HEADER.transactionSequenceNumber,
GLOBAL_CONDITION_HEADER.commandIdentifier,
Zcl.Clusters.genOnOff.ID,
[{direction: Zcl.Direction.SERVER_TO_CLIENT, attrId: 256, timeout: 10000}] /*payload*/,
{} /*custom clusters*/,
GLOBAL_CONDITION_HEADER.frameControl.reservedBits,
);
const GLOBAL_CONDITION_FRAME_BUFFER = Buffer.concat([
GLOBAL_CONDITION_HEADER_BUFFER,
Buffer.from([Zcl.Direction.SERVER_TO_CLIENT, ...uint16To8Array(256), ...uint16To8Array(10000)]),
]);
const GLOBAL_CONDITION_FRAME_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":0,"direction":1,"disableDefaultResponse":false,"manufacturerSpecific":false},"transactionSequenceNumber":78,"commandIdentifier":6},"payload":[{"direction":1,"attrId":256,"timeout":10000}],"command":{"ID":6,"name":"configReport","parameters":[{"name":"direction","type":32},{"name":"attrId","type":33},{"name":"dataType","type":32,"conditions":[{"type":"directionEquals","value":0}]},{"name":"minRepIntval","type":33,"conditions":[{"type":"directionEquals","value":0}]},{"name":"maxRepIntval","type":33,"conditions":[{"type":"directionEquals","value":0}]},{"name":"repChange","type":1000,"conditions":[{"type":"directionEquals","value":0},{"type":"dataTypeValueTypeEquals","value":"ANALOG"}]},{"name":"timeout","type":33,"conditions":[{"type":"directionEquals","value":1}]}],"response":7}}`;
/** Frame of Specific type */
const SPECIFIC_FRAME = Zcl.Frame.create(
SPECIFIC_HEADER.frameControl.frameType,
SPECIFIC_HEADER.frameControl.direction,
SPECIFIC_HEADER.frameControl.disableDefaultResponse,
SPECIFIC_HEADER.manufacturerCode,
SPECIFIC_HEADER.transactionSequenceNumber,
SPECIFIC_HEADER.commandIdentifier,
Zcl.Clusters.genAlarms.ID,
{} /*payload*/,
{} /*custom clusters*/,
SPECIFIC_HEADER.frameControl.reservedBits,
);
const SPECIFIC_FRAME_BUFFER = Buffer.concat([SPECIFIC_HEADER_BUFFER]);
const SPECIFIC_FRAME_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":1,"direction":0,"disableDefaultResponse":false,"manufacturerSpecific":false},"transactionSequenceNumber":44,"commandIdentifier":2},"payload":{},"command":{"ID":2,"parameters":[],"name":"getAlarm"}}`;
/** Frame of Specific type and response command */
const SPECIFIC_RSP_FRAME = Zcl.Frame.create(
SPECIFIC_RSP_HEADER.frameControl.frameType,
SPECIFIC_RSP_HEADER.frameControl.direction,
SPECIFIC_RSP_HEADER.frameControl.disableDefaultResponse,
SPECIFIC_RSP_HEADER.manufacturerCode,
SPECIFIC_RSP_HEADER.transactionSequenceNumber,
SPECIFIC_RSP_HEADER.commandIdentifier,
Zcl.Clusters.genAlarms.ID,
{alarmcode: 246, clusterid: 3456} /*payload*/,
{} /*custom clusters*/,
SPECIFIC_RSP_HEADER.frameControl.reservedBits,
);
const SPECIFIC_RSP_FRAME_BUFFER = Buffer.concat([SPECIFIC_RSP_HEADER_BUFFER, Buffer.from([246, ...uint16To8Array(3456)])]);
const SPECIFIC_RSP_FRAME_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":1,"direction":1,"disableDefaultResponse":false,"manufacturerSpecific":false},"transactionSequenceNumber":53,"commandIdentifier":0},"payload":{"alarmcode":246,"clusterid":3456},"command":{"ID":0,"parameters":[{"name":"alarmcode","type":32},{"name":"clusterid","type":33}],"name":"alarm"}}`;
/** Frame of Specific type with condition-based parameters */
const SPECIFIC_CONDITION_FRAME = Zcl.Frame.create(
SPECIFIC_CONDITION_HEADER.frameControl.frameType,
SPECIFIC_CONDITION_HEADER.frameControl.direction,
SPECIFIC_CONDITION_HEADER.frameControl.disableDefaultResponse,
SPECIFIC_CONDITION_HEADER.manufacturerCode,
SPECIFIC_CONDITION_HEADER.transactionSequenceNumber,
SPECIFIC_CONDITION_HEADER.commandIdentifier,
Zcl.Clusters.genOta.ID,
{status: Zcl.Status.ABORT} /*payload*/,
{} /*custom clusters*/,
SPECIFIC_CONDITION_HEADER.frameControl.reservedBits,
);
const SPECIFIC_CONDITION_FRAME_BUFFER = Buffer.concat([SPECIFIC_CONDITION_HEADER_BUFFER, Buffer.from([149])]);
const SPECIFIC_CONDITION_FRAME_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":1,"direction":1,"disableDefaultResponse":false,"manufacturerSpecific":false},"transactionSequenceNumber":45,"commandIdentifier":2},"payload":{"status":149},"command":{"ID":2,"parameters":[{"name":"status","type":32},{"name":"manufacturerCode","type":33,"conditions":[{"type":"statusEquals","value":0}]},{"name":"imageType","type":33,"conditions":[{"type":"statusEquals","value":0}]},{"name":"fileVersion","type":35,"conditions":[{"type":"statusEquals","value":0}]},{"name":"imageSize","type":35,"conditions":[{"type":"statusEquals","value":0}]}],"name":"queryNextImageResponse"}}`;
/** Frame manufacturer-specific */
const MANUF_SPE_FRAME = Zcl.Frame.create(
MANUF_SPE_HEADER.frameControl.frameType,
MANUF_SPE_HEADER.frameControl.direction,
MANUF_SPE_HEADER.frameControl.disableDefaultResponse,
MANUF_SPE_HEADER.manufacturerCode,
MANUF_SPE_HEADER.transactionSequenceNumber,
MANUF_SPE_HEADER.commandIdentifier,
Zcl.Foundation.read.ID,
[{attrId: 256}] /*payload*/,
{} /*custom clusters*/,
MANUF_SPE_HEADER.frameControl.reservedBits,
);
const MANUF_SPE_FRAME_BUFFER = Buffer.concat([MANUF_SPE_HEADER_BUFFER, Buffer.from(uint16To8Array(256))]);
const MANUF_SPE_FRAME_STRING = `{"header":{"frameControl":{"reservedBits":0,"frameType":0,"direction":0,"disableDefaultResponse":false,"manufacturerSpecific":true},"manufacturerCode":4344,"transactionSequenceNumber":234,"commandIdentifier":0},"payload":[{"attrId":256}],"command":{"ID":0,"name":"read","parameters":[{"name":"attrId","type":33}],"response":1}}`;
describe("ZCL Frame", () => {
describe("Validates Parameter Condition", () => {
it("STATUS_EQUAL", () => {
expect(Zcl.Frame.conditionsValid(Zcl.Foundation.readRsp.parameters[2], {status: 0}, null)).toBeTruthy();
expect(Zcl.Frame.conditionsValid(Zcl.Foundation.readRsp.parameters[2], {status: 1}, null)).toBeFalsy();
});
it("STATUS_NOT_EQUAL", () => {
expect(Zcl.Frame.conditionsValid(Zcl.Foundation.writeRsp.parameters[1], {status: 1}, null)).toBeTruthy();
expect(Zcl.Frame.conditionsValid(Zcl.Foundation.writeRsp.parameters[1], {status: 0}, null)).toBeFalsy();
});
it("MINIMUM_REMAINING_BUFFER_BYTES", () => {
expect(Zcl.Frame.conditionsValid(Zcl.Foundation.configReportRsp.parameters[1], {status: 1}, 3)).toBeTruthy();
expect(Zcl.Frame.conditionsValid(Zcl.Foundation.configReportRsp.parameters[1], {status: 1}, 2)).toBeFalsy();
});
it("DIRECTION_EQUAL", () => {
expect(
Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[2], {direction: Zcl.Direction.CLIENT_TO_SERVER}, null),
).toBeTruthy();
expect(
Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[2], {direction: Zcl.Direction.SERVER_TO_CLIENT}, null),
).toBeFalsy();
expect(
Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[6], {direction: Zcl.Direction.SERVER_TO_CLIENT}, null),
).toBeTruthy();
expect(
Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[6], {direction: Zcl.Direction.CLIENT_TO_SERVER}, null),
).toBeFalsy();
});
it("BITMASK_SET", () => {
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x4000}, null)).toBeTruthy();
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x4150}, null)).toBeTruthy();
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x0400}, null)).toBeFalsy();
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x1400}, null)).toBeFalsy();
});
it("BITFIELD_ENUM", () => {
// {param:'options', offset: 0, size: 3, value: 0b000}
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b000}, null)).toBeTruthy();
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b1000}, null)).toBeTruthy();
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b001}, null)).toBeFalsy();
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b011}, null)).toBeFalsy();
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b100}, null)).toBeFalsy();
expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b1010}, null)).toBeFalsy();
});
it("multiple including DATA_TYPE_CLASS_EQUAL", () => {
expect(
Zcl.Frame.conditionsValid(
Zcl.Foundation.configReport.parameters[5],
{direction: Zcl.Direction.CLIENT_TO_SERVER, dataType: Zcl.DataType.UINT8},
null,
),
).toBeTruthy();
expect(
Zcl.Frame.conditionsValid(
Zcl.Foundation.configReport.parameters[5],
{direction: Zcl.Direction.CLIENT_TO_SERVER, dataType: Zcl.DataType.DATA8},
null,
),
).toBeFalsy();
expect(
Zcl.Frame.conditionsValid(
Zcl.Foundation.configReport.parameters[5],
{direction: Zcl.Direction.SERVER_TO_CLIENT, dataType: Zcl.DataType.UINT8},
null,
),
).toBeFalsy();
expect(
Zcl.Frame.conditionsValid(
Zcl.Foundation.configReport.parameters[5],
{direction: Zcl.Direction.SERVER_TO_CLIENT, dataType: Zcl.DataType.DATA8},
null,
),
).toBeFalsy();
});
it("FIELD_EQUAL", () => {
expect(
Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 1}, null),
).toBeTruthy();
expect(
Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 0}, null),
).toBeFalsy();
expect(
Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 3}, null),
).toBeFalsy();
});
});
describe("Header", () => {
it.each([
[
"global",
{
frameControl: GLOBAL_HEADER.frameControl,
manufacturerCode: GLOBAL_HEADER.manufacturerCode,
transactionSequenceNumber: GLOBAL_HEADER.transactionSequenceNumber,
commandId: GLOBAL_HEADER.commandIdentifier,
},
{written: GLOBAL_HEADER_BUFFER},
],
[
"global response",
{
frameControl: GLOBAL_RSP_HEADER.frameControl,
manufacturerCode: GLOBAL_RSP_HEADER.manufacturerCode,
transactionSequenceNumber: GLOBAL_RSP_HEADER.transactionSequenceNumber,
commandId: GLOBAL_RSP_HEADER.commandIdentifier,
},
{written: GLOBAL_RSP_HEADER_BUFFER},
],
[
"specific",
{
frameControl: SPECIFIC_HEADER.frameControl,
manufacturerCode: SPECIFIC_HEADER.manufacturerCode,
transactionSequenceNumber: SPECIFIC_HEADER.transactionSequenceNumber,
commandId: SPECIFIC_HEADER.commandIdentifier,
},
{written: SPECIFIC_HEADER_BUFFER},
],
[
"manufacturer-specific",
{
frameControl: MANUF_SPE_HEADER.frameControl,
manufacturerCode: MANUF_SPE_HEADER.manufacturerCode,
transactionSequenceNumber: MANUF_SPE_HEADER.transactionSequenceNumber,
commandId: MANUF_SPE_HEADER.commandIdentifier,
},
{written: MANUF_SPE_HEADER_BUFFER},
],
[
"disable default response",
{
frameControl: {
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: true,
reservedBits: 0,
},
manufacturerCode: undefined,
transactionSequenceNumber: 234,
commandId: 1,
},
{written: Buffer.from([16, 234, 1])},
],
[
"reserved bits - non-compliant use",
{
frameControl: {
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 7,
},
manufacturerCode: undefined,
transactionSequenceNumber: 234,
commandId: 1,
},
{written: Buffer.from([224, 234, 1])},
],
[
"specific manufacturer-specific",
{
frameControl: {
frameType: Zcl.FrameType.SPECIFIC,
manufacturerSpecific: true,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 0,
},
manufacturerCode: Zcl.ManufacturerCode.S3C,
transactionSequenceNumber: 234,
commandId: 1,
},
{written: Buffer.from([5, ...uint16To8Array(Zcl.ManufacturerCode.S3C), 234, 1])},
],
[
"all non-zero - non-compliant use of reservedBits",
{
frameControl: {
frameType: Zcl.FrameType.SPECIFIC,
manufacturerSpecific: true,
direction: Zcl.Direction.SERVER_TO_CLIENT,
disableDefaultResponse: true,
reservedBits: 3,
},
manufacturerCode: Zcl.ManufacturerCode.BOSCH_SECURITY_SYSTEMS_INC,
transactionSequenceNumber: 234,
commandId: 1,
},
{written: Buffer.from([125, ...uint16To8Array(Zcl.ManufacturerCode.BOSCH_SECURITY_SYSTEMS_INC), 234, 1])},
],
])("Reads & Writes %s", (_name, payload, expected) => {
// write
{
const header = new Zcl.Header(payload.frameControl, payload.manufacturerCode, payload.transactionSequenceNumber, payload.commandId);
const buffer = Buffer.alloc(10);
const buffalo = new BuffaloZcl(buffer);
header.write(buffalo);
expect(buffer.subarray(0, header.length)).toStrictEqual(expected.written);
expect(header.length).toStrictEqual(expected.written.length);
expect(header.isGlobal).toStrictEqual(payload.frameControl.frameType === Zcl.FrameType.GLOBAL);
expect(header.isSpecific).toStrictEqual(payload.frameControl.frameType === Zcl.FrameType.SPECIFIC);
}
// read
{
const header = Zcl.Header.fromBuffer(expected.written)!;
expect(header).toBeInstanceOf(Zcl.Header);
expect(header.length).toStrictEqual(expected.written.length);
expect(header.isGlobal).toStrictEqual(payload.frameControl.frameType === Zcl.FrameType.GLOBAL);
expect(header.isSpecific).toStrictEqual(payload.frameControl.frameType === Zcl.FrameType.SPECIFIC);
expect(header.frameControl).toStrictEqual(payload.frameControl);
expect(header.manufacturerCode).toStrictEqual(payload.manufacturerCode);
expect(header.transactionSequenceNumber).toStrictEqual(payload.transactionSequenceNumber);
expect(header.commandIdentifier).toStrictEqual(payload.commandId);
}
});
it.each([
["basic", {value: [0, 234]}],
["manufacturer specific", {value: [4, ...uint16To8Array(1234), 234]}],
])("Reads non-compliant header as undefined %s", (_name, payload) => {
expect(Zcl.Header.fromBuffer(Buffer.from(payload.value))).toStrictEqual(undefined);
});
});
it.each([
["global", GLOBAL_FRAME, {string: GLOBAL_FRAME_STRING, header: GLOBAL_HEADER, written: GLOBAL_FRAME_BUFFER}],
[
"global BigInt",
GLOBAL_FRAME_BIG_INT,
{string: GLOBAL_FRAME_BIG_INT_STRING, header: GLOBAL_HEADER_REPORT, written: GLOBAL_FRAME_BIG_INT_BUFFER},
],
["global response", GLOBAL_RSP_FRAME, {string: GLOBAL_RSP_FRAME_STRING, header: GLOBAL_RSP_HEADER, written: GLOBAL_RSP_FRAME_BUFFER}],
[
"global no payload",
GLOBAL_FRAME_NO_PAYLOAD,
{string: GLOBAL_FRAME_NO_PAYLOAD_STRING, header: GLOBAL_HEADER, written: GLOBAL_FRAME_NO_PAYLOAD_BUFFER},
],
[
"global with condition-based parameters",
GLOBAL_CONDITION_FRAME,
{string: GLOBAL_CONDITION_FRAME_STRING, header: GLOBAL_CONDITION_HEADER, written: GLOBAL_CONDITION_FRAME_BUFFER},
],
["specific", SPECIFIC_FRAME, {string: SPECIFIC_FRAME_STRING, header: SPECIFIC_HEADER, written: SPECIFIC_FRAME_BUFFER}],
[
"specific response",
SPECIFIC_RSP_FRAME,
{string: SPECIFIC_RSP_FRAME_STRING, header: SPECIFIC_RSP_HEADER, written: SPECIFIC_RSP_FRAME_BUFFER},
],
[
"specific with condition-based parameters",
SPECIFIC_CONDITION_FRAME,
{string: SPECIFIC_CONDITION_FRAME_STRING, header: SPECIFIC_CONDITION_HEADER, written: SPECIFIC_CONDITION_FRAME_BUFFER},
],
["manufacturer-specific", MANUF_SPE_FRAME, {string: MANUF_SPE_FRAME_STRING, header: MANUF_SPE_HEADER, written: MANUF_SPE_FRAME_BUFFER}],
])("Writes & Reads frame %s", (_name, frame, expected) => {
expect(frame).toBeDefined();
expect(frame.toString()).toStrictEqual(expected.string);
expect(frame.header).toStrictEqual(expected.header);
expect(frame.toBuffer()).toStrictEqual(expected.written);
expect(Zcl.Frame.fromBuffer(frame.cluster.ID, frame.header, expected.written, {}).toString()).toStrictEqual(expected.string);
});
it("Writes & Reads repetitive strategy", () => {
const expected = [{attrId: 256}, {attrId: 127}];
const header = new Zcl.Header(
{
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
123,
Zcl.Foundation.read.ID,
);
const frame = Zcl.Frame.create(
header.frameControl.frameType,
header.frameControl.direction,
header.frameControl.disableDefaultResponse,
header.manufacturerCode,
header.transactionSequenceNumber,
header.commandIdentifier,
Zcl.Foundation.read.ID,
expected /*payload*/,
{} /*custom clusters*/,
header.frameControl.reservedBits,
);
expect(frame.payload).toStrictEqual(expected);
const buffer = frame.toBuffer();
const readHeader = Zcl.Header.fromBuffer(buffer)!;
const readFrame = Zcl.Frame.fromBuffer(0 /*Foundation*/, readHeader, buffer, {} /*custom clusters*/);
expect(readFrame.payload).toStrictEqual(expected);
});
it("Writes & Reads flat strategy", () => {
const expected = {cmdId: 253, statusCode: 127};
const header = new Zcl.Header(
{
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
123,
Zcl.Foundation.defaultRsp.ID,
);
const frame = Zcl.Frame.create(
header.frameControl.frameType,
header.frameControl.direction,
header.frameControl.disableDefaultResponse,
header.manufacturerCode,
header.transactionSequenceNumber,
header.commandIdentifier,
Zcl.Foundation.defaultRsp.ID,
expected /*payload*/,
{} /*custom clusters*/,
header.frameControl.reservedBits,
);
expect(frame.payload).toStrictEqual(expected);
const buffer = frame.toBuffer();
const readHeader = Zcl.Header.fromBuffer(buffer)!;
const readFrame = Zcl.Frame.fromBuffer(0 /*Foundation*/, readHeader, buffer, {} /*custom clusters*/);
expect(readFrame.payload).toStrictEqual(expected);
});
it("Writes & Reads oneof strategy", () => {
const expected = {
discComplete: 123,
attrInfos: [
{attrId: 32, dataType: Zcl.DataType.UINT16},
{attrId: 67, dataType: Zcl.DataType.ARRAY},
],
};
const header = new Zcl.Header(
{
frameType: Zcl.FrameType.GLOBAL,
manufacturerSpecific: false,
direction: Zcl.Direction.CLIENT_TO_SERVER,
disableDefaultResponse: false,
reservedBits: 0,
},
undefined,
123,
Zcl.Foundation.discoverRsp.ID,
);
const frame = Zcl.Frame.create(
header.frameControl.frameType,
header.frameControl.direction,
header.frameControl.disableDefaultResponse,
header.manufacturerCode,
header.transactionSequenceNumber,
header.commandIdentifier,
Zcl.Foundation.discoverRsp.ID,
expected /*payload*/,
{} /*custom clusters*/,
header.frameControl.reservedBits,
);
expect(frame.payload).toStrictEqual(expected);
const buffer = frame.toBuffer();
const readHeader = Zcl.Header.fromBuffer(buffer)!;
const readFrame = Zcl.Frame.fromBuffer(0 /*Foundation*/, readHeader, buffer, {} /*custom clusters*/);
expect(readFrame.payload).toStrictEqual(expected);
});
it("Throws when writting invalid frame type", () => {
expect(() => {
const frame = Zcl.Frame.create(
// @ts-expect-error invalid on purpose
3,
Zcl.Direction.CLIENT_TO_SERVER,
false,
null,
123,
Zcl.Clusters.genAlarms.commands.reset.ID,
Zcl.Clusters.genAlarms.ID,
{} /*payload*/,
{} /*custom clusters*/,
0 /*reserved bits*/,
);
frame.toBuffer();
}).toThrow(`Frametype '3' not valid`);
});
it("Throws when reading invalid frame type", () => {
expect(() => {
Zcl.Frame.fromBuffer(
Zcl.Clusters.genBasic.ID,
Zcl.Header.fromBuffer(Buffer.from([2, 123, 0]))!,
Buffer.from([]) /*payload*/,
{} /*custom clusters*/,
);
}).toThrow(`Unsupported frameType '2'`);
});
it("Throws when reading frame without header", () => {
expect(() => {
Zcl.Frame.fromBuffer(
Zcl.Clusters.genBasic.ID,
// @ts-expect-error invalid of purpose
null,
Buffer.from([]) /*payload*/,
{} /*custom clusters*/,
);
}).toThrow("Invalid ZclHeader.");
});
it("Throws when writting missing payload", () => {
expect(() => {
const frame = Zcl.Frame.create(
SPECIFIC_RSP_HEADER.frameControl.frameType,
SPECIFIC_RSP_HEADER.frameControl.direction,
SPECIFIC_RSP_HEADER.frameControl.disableDefaultResponse,
SPECIFIC_RSP_HEADER.manufacturerCode,
SPECIFIC_RSP_HEADER.transactionSequenceNumber,
SPECIFIC_RSP_HEADER.commandIdentifier,
Zcl.Clusters.genAlarms.ID,
{alarmcode: 246 /*clusterid: 3456*/} /*payload*/,
{} /*custom clusters*/,
SPECIFIC_RSP_HEADER.frameControl.reservedBits,
);
frame.toBuffer();
}).toThrow(`Parameter 'clusterid' is missing`);
});
it("Checks cluster match by name", () => {
expect(GLOBAL_FRAME.isCluster("genBasic")).toBeTruthy();
expect(GLOBAL_FRAME.isCluster("discoverCommands")).toBeFalsy();
// @ts-expect-error invalid on purpose
expect(GLOBAL_FRAME.isCluster("notacluster")).toBeFalsy();
expect(SPECIFIC_FRAME.isCluster("genAlarms")).toBeTruthy();
expect(SPECIFIC_FRAME.isCluster("genAnalogInput")).toBeFalsy();
// @ts-expect-error invalid on purpose
expect(SPECIFIC_FRAME.isCluster("notacluster")).toBeFalsy();
});
it("Checks command match by name", () => {
expect(GLOBAL_FRAME.isCommand("read")).toBeTruthy();
expect(GLOBAL_FRAME.isCommand("discoverCommands")).toBeFalsy();
// @ts-expect-error invalid on purpose
expect(GLOBAL_FRAME.isCommand("notacommand")).toBeFalsy();
expect(SPECIFIC_FRAME.isCommand("getAlarm")).toBeTruthy();
expect(SPECIFIC_FRAME.isCommand("enrollReq")).toBeFalsy();
// @ts-expect-error invalid on purpose
expect(SPECIFIC_FRAME.isCommand("notacommand")).toBeFalsy();
});
it("[workaround] Reads Foundation char str as Mi struct for Xiaomi attridId=65281", () => {
const expected = [
{attrId: 5, dataType: Zcl.DataType.CHAR_STR, attrData: "lumi.sensor_wleak.aq1"},
{attrId: 65281, dataType: Zcl.DataType.CHAR_STR, attrData: {1: 3285, 3: 33, 4: 5032, 5: 43, 6: 327680, 8: 516, 10: 0, 100: 0}},
];
const buffer = Buffer.from([
28,
...uint16To8Array(Zcl.ManufacturerCode.LUMI_UNITED_TECHOLOGY_LTD_SHENZHEN),
3,
10 /*header*/,
// regular attr parsing
5,
0,
Zcl.DataType.CHAR_STR,
21,
108,
117,
109,
105,
46,
115,
101,
110,
115,
111,
114,
95,
119,
108,
101,
97,
107,
46,
97,
113,
49,
// workaround parsing
1,
255,
Zcl.DataType.CHAR_STR,
34,
1,
Zcl.DataType.UINT16,
...uint16To8Array(3285),
3,
Zcl.DataType.INT8,
33,
4,
Zcl.DataType.UINT16,
...uint16To8Array(5032),
5,
Zcl.DataType.UINT16,
...uint16To8Array(43),
6,
Zcl.DataType.UINT40,
...uint32To8Array(327680),
0,
8,
Zcl.DataType.UINT16,
...uint16To8Array(516),
10,
Zcl.DataType.UINT16,
...uint16To8Array(0),
100,
Zcl.DataType.BOOLEAN,
0,
]);
const header = Zcl.Header.fromBuffer(buffer)!;
const frame = Zcl.Frame.fromBuffer(0 /*Foundation*/, header, buffer, {});
expect(frame.payload).toStrictEqual(expected);
});
it("[workaround] Reads Foundation struct with extra payload to match zcl-packet", () => {
const expected = [
{
attrId: 65282,
dataType: Zcl.DataType.STRUCT,
numElms: 6,
structElms: [
{elmType: Zcl.DataType.BOOLEAN, elmVal: 1},
{elmType: Zcl.DataType.UINT16, elmVal: 3022},
{elmType: Zcl.DataType.UINT16, elmVal: 17320},
{elmType: Zcl.DataType.UINT40, elmVal: 1},
{elmType: Zcl.DataType.UINT16, elmVal: 560},
{elmType: Zcl.DataType.UINT8, elmVal: 86},
],
attrData: [
{elmType: Zcl.DataType.BOOLEAN, elmVal: 1},
{elmType: Zcl.DataType.UINT16, elmVal: 3022},
{elmType: Zcl.DataType.UINT16, elmVal: 17320},
{elmType: Zcl.DataType.UINT40, elmVal: 1},
{elmType: Zcl.DataType.UINT16, elmVal: 560},
{elmType: Zcl.DataType.UINT8, elmVal: 86},
],
},
];
const buffer = Buffer.from([
28,
...uint16To8Array(Zcl.ManufacturerCode.DSR_CORPORATION),
194,
10 /*header*/,
...uint16To8Array(65282),
Zcl.DataType.STRUCT,
6,
0,
Zcl.DataType.BOOLEAN,
1,
Zcl.DataType.UINT16,
...uint16To8Array(3022),
Zcl.DataType.UINT16,
...uint16To8Array(17320),
Zcl.DataType.UINT40,
...uint32To8Array(1),
0,
Zcl.DataType.UINT16,
...uint16To8Array(560),
Zcl.DataType.UINT8,
86,
]);
const header = Zcl.Header.fromBuffer(buffer)!;
const frame = Zcl.Frame.fromBuffer(0 /*Foundation*/, header, buffer, {});
expect(frame.payload).toStrictEqual(expected);
});
it("Reads/writes Touchlink Scan Response with different size payloads", () => {
const full = Buffer.from([
0x19, 0x0, 0x1, 0xe0, 0xde, 0x5e, 0x2f, 0xa, 0x5, 0x0, 0x12, 0x0, 0x3d, 0x30, 0x1d, 0x4a, 0x8f, 0xb7, 0xdc, 0x1c, 0x0, 0x4b, 0x12, 0x0,
0x0, 0xf, 0x62, 0x1a, 0xb3, 0xaa, 0x1, 0x0, 0xb, 0x5e, 0xc0, 0x10, 0x2, 0x2, 0x0,
]);
const short = Buffer.from([
0x19, 0x0, 0x1, 0xe0, 0xde, 0x5e, 0x2f, 0xa, 0x5, 0x0, 0x12, 0x0, 0x3d, 0x30, 0x1d, 0x4a, 0x8f, 0xb7, 0xdc, 0x1c, 0x0, 0x4b, 0x12, 0x0,
0x0, 0xf, 0x62, 0x1a, 0xb3, 0xaa, 0x0, 0x0,
]);
const short2 = Buffer.from([
0x19, 0x0, 0x1, 0xe0, 0xde, 0x5e, 0x2f, 0xa, 0x5, 0x0, 0x12, 0x0, 0x3d, 0x30, 0x1d, 0x4a, 0x8f, 0xb7, 0xdc, 0x1c, 0x0, 0x4b, 0x12, 0x0,
0x0, 0xf, 0x62, 0x1a, 0xb3, 0xaa, 0x3, 0x0,
]);
const fullZcl = Zcl.Frame.fromBuffer(0x1000, Zcl.Header.fromBuffer(full), full, {});
expect(fullZcl.payload.numberOfSubDevices).toStrictEqual(1);
expect(fullZcl.payload.endpointID).toStrictEqual(11);
expect(fullZcl.payload.profileID).toStrictEqual(0xc05e);
expect(fullZcl.payload.deviceID).toStrictEqual(0x0210);
expect(fullZcl.payload.version).toStrictEqual(0x02);
expect(fullZcl.payload.groupIDCount).toStrictEqual(0);
const shortZcl = Zcl.Frame.fromBuffer(0x1000, Zcl.Header.fromBuffer(short), short, {});
expect(shortZcl.payload.numberOfSubDevices).toStrictEqual(0);
expect(shortZcl.payload.endpointID).toStrictEqual(undefined);
expect(shortZcl.payload.profileID).toStrictEqual(undefined);
expect(shortZcl.payload.deviceID).toStrictEqual(undefined);
expect(shortZcl.payload.version).toStrictEqual(undefined);
expect(shortZcl.payload.groupIDCount).toStrictEqual(undefined);
const short2Zcl = Zcl.Frame.fromBuffer(0x1000, Zcl.Header.fromBuffer(short2), short2, {});
expect(short2Zcl.payload.numberOfSubDevices).toStrictEqual(3);
expect(short2Zcl.payload.endpointID).toStrictEqual(undefined);
expect(short2Zcl.payload.profileID).toStrictEqual(undefined);
expect(short2Zcl.payload.deviceID).toStrictEqual(undefined);
expect(short2Zcl.payload.version).toStrictEqual(undefined);
expect(short2Zcl.payload.groupIDCount).toStrictEqual(undefined);
const fullNew = fullZcl.toBuffer();
expect(fullNew).toStrictEqual(full);
const shortNew = shortZcl.toBuffer();
expect(shortNew).toStrictEqual(short);
const short2New = short2Zcl.toBuffer();
expect(short2New).toStrictEqual(short2);
});
});