zigbee-herdsman 4.4.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.github/dependabot.yml +1 -0
  2. package/.github/workflows/ci.yml +20 -1
  3. package/.release-please-manifest.json +1 -1
  4. package/CHANGELOG.md +26 -0
  5. package/dist/controller/controller.d.ts.map +1 -1
  6. package/dist/controller/controller.js +5 -6
  7. package/dist/controller/controller.js.map +1 -1
  8. package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
  9. package/dist/controller/helpers/zclFrameConverter.js +2 -14
  10. package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
  11. package/dist/controller/model/device.d.ts.map +1 -1
  12. package/dist/controller/model/device.js +3 -13
  13. package/dist/controller/model/device.js.map +1 -1
  14. package/dist/controller/model/endpoint.d.ts.map +1 -1
  15. package/dist/controller/model/endpoint.js +42 -22
  16. package/dist/controller/model/endpoint.js.map +1 -1
  17. package/dist/controller/model/group.d.ts +4 -0
  18. package/dist/controller/model/group.d.ts.map +1 -1
  19. package/dist/controller/model/group.js +69 -10
  20. package/dist/controller/model/group.js.map +1 -1
  21. package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -1
  22. package/dist/zspec/zcl/buffaloZcl.js +5 -5
  23. package/dist/zspec/zcl/buffaloZcl.js.map +1 -1
  24. package/dist/zspec/zcl/definition/tstype.d.ts +1 -2
  25. package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
  26. package/dist/zspec/zcl/utils.d.ts.map +1 -1
  27. package/dist/zspec/zcl/utils.js +19 -34
  28. package/dist/zspec/zcl/utils.js.map +1 -1
  29. package/package.json +5 -3
  30. package/src/controller/controller.ts +5 -7
  31. package/src/controller/helpers/zclFrameConverter.ts +14 -14
  32. package/src/controller/model/device.ts +5 -15
  33. package/src/controller/model/endpoint.ts +51 -23
  34. package/src/controller/model/group.ts +83 -11
  35. package/src/zspec/zcl/buffaloZcl.ts +6 -4
  36. package/src/zspec/zcl/definition/tstype.ts +1 -2
  37. package/src/zspec/zcl/utils.ts +23 -43
  38. package/test/controller.bench.ts +193 -0
  39. package/test/controller.test.ts +554 -1294
  40. package/test/mockDevices.ts +127 -1
  41. package/test/requests.bench.ts +206 -0
  42. package/test/vitest.config.mts +2 -0
  43. package/test/zcl.test.ts +11 -15
  44. package/test/zspec/zcl/frame.test.ts +25 -25
  45. package/test/zspec/zcl/utils.test.ts +6 -14
@@ -1,3 +1,4 @@
1
+ import * as Zcl from "../src/zspec/zcl";
1
2
  import * as Zdo from "../src/zspec/zdo";
2
3
  import type * as ZdoTypes from "../src/zspec/zdo/definition/tstypes";
3
4
 
@@ -438,7 +439,10 @@ export const MOCK_DEVICES: {
438
439
  },
439
440
  },
440
441
  177: {
441
- nodeDescriptor: [Zdo.Status.SUCCESS, {...NODE_DESC_DEFAULTS, nwkAddress: 177, logicalType: 0b001, manufacturerCode: 4129}],
442
+ nodeDescriptor: [
443
+ Zdo.Status.SUCCESS,
444
+ {...NODE_DESC_DEFAULTS, nwkAddress: 177, logicalType: 0b001, manufacturerCode: Zcl.ManufacturerCode.LEGRAND_GROUP},
445
+ ],
442
446
  activeEndpoints: [Zdo.Status.SUCCESS, {nwkAddress: 177, endpointList: [1]}],
443
447
  simpleDescriptor: {
444
448
  1: [
@@ -469,4 +473,126 @@ export const MOCK_DEVICES: {
469
473
  },
470
474
  },
471
475
  },
476
+ 178: {
477
+ nodeDescriptor: [
478
+ Zdo.Status.SUCCESS,
479
+ {...NODE_DESC_DEFAULTS, nwkAddress: 178, logicalType: 0b001, manufacturerCode: Zcl.ManufacturerCode.LEGRAND_GROUP},
480
+ ],
481
+ activeEndpoints: [Zdo.Status.SUCCESS, {nwkAddress: 178, endpointList: [1, 2]}],
482
+ simpleDescriptor: {
483
+ 1: [
484
+ Zdo.Status.SUCCESS,
485
+ {
486
+ nwkAddress: 178,
487
+ length: 32,
488
+ endpoint: 1,
489
+ profileId: 260,
490
+ deviceId: 514,
491
+ deviceVersion: 1,
492
+ inClusterList: [0, 3, 258, 4, 5, 15, 64513],
493
+ outClusterList: [258, 0, 64513, 5, 25],
494
+ },
495
+ ],
496
+ 2: [
497
+ Zdo.Status.SUCCESS,
498
+ {
499
+ nwkAddress: 178,
500
+ length: 32,
501
+ endpoint: 2,
502
+ profileId: 260,
503
+ deviceId: 514,
504
+ deviceVersion: 1,
505
+ inClusterList: [0, 3, 258, 4, 5, 15],
506
+ outClusterList: [258, 0, 64513, 5, 25],
507
+ },
508
+ ],
509
+ },
510
+ attributes: {
511
+ 1: {
512
+ modelId: " some other multi-endpoint device",
513
+ manufacturerName: "Legrand",
514
+ zclVersion: 2,
515
+ appVersion: 0,
516
+ hwVersion: 8,
517
+ dateCode: "241030",
518
+ swBuildId: "0039",
519
+ powerSource: 1,
520
+ stackVersion: 67,
521
+ },
522
+ },
523
+ },
524
+ 179: {
525
+ nodeDescriptor: [
526
+ Zdo.Status.SUCCESS,
527
+ {...NODE_DESC_DEFAULTS, nwkAddress: 179, logicalType: 0b001, manufacturerCode: Zcl.ManufacturerCode.V_MARK_ENTERPRISES_INC},
528
+ ],
529
+ activeEndpoints: [Zdo.Status.SUCCESS, {nwkAddress: 179, endpointList: [1, 2, 3, 4]}],
530
+ simpleDescriptor: {
531
+ 1: [
532
+ Zdo.Status.SUCCESS,
533
+ {
534
+ nwkAddress: 179,
535
+ length: 32,
536
+ endpoint: 1,
537
+ profileId: 260,
538
+ deviceId: 514,
539
+ deviceVersion: 1,
540
+ inClusterList: [0, 64561],
541
+ outClusterList: [64561],
542
+ },
543
+ ],
544
+ 2: [
545
+ Zdo.Status.SUCCESS,
546
+ {
547
+ nwkAddress: 179,
548
+ length: 32,
549
+ endpoint: 1,
550
+ profileId: 260,
551
+ deviceId: 514,
552
+ deviceVersion: 1,
553
+ inClusterList: [0, 64561],
554
+ outClusterList: [64561],
555
+ },
556
+ ],
557
+ 3: [
558
+ Zdo.Status.SUCCESS,
559
+ {
560
+ nwkAddress: 179,
561
+ length: 32,
562
+ endpoint: 1,
563
+ profileId: 260,
564
+ deviceId: 514,
565
+ deviceVersion: 1,
566
+ inClusterList: [0, 64561],
567
+ outClusterList: [64561],
568
+ },
569
+ ],
570
+ 4: [
571
+ Zdo.Status.SUCCESS,
572
+ {
573
+ nwkAddress: 179,
574
+ length: 32,
575
+ endpoint: 1,
576
+ profileId: 260,
577
+ deviceId: 514,
578
+ deviceVersion: 1,
579
+ inClusterList: [0, 64561],
580
+ outClusterList: [64561],
581
+ },
582
+ ],
583
+ },
584
+ attributes: {
585
+ 1: {
586
+ modelId: "VZM30-SN",
587
+ manufacturerName: "Inovelli",
588
+ zclVersion: 3,
589
+ appVersion: 0,
590
+ hwVersion: 8,
591
+ dateCode: "2025",
592
+ swBuildId: "0001",
593
+ powerSource: 1,
594
+ stackVersion: 67,
595
+ },
596
+ },
597
+ },
472
598
  };
@@ -0,0 +1,206 @@
1
+ import {bench, describe} from "vitest";
2
+ import type {Adapter} from "../src/adapter";
3
+ import type {ZclPayload} from "../src/adapter/events";
4
+ import Database from "../src/controller/database";
5
+ import {Device, Entity, Group} from "../src/controller/model";
6
+ import {InterviewState} from "../src/controller/model/device";
7
+ import {setLogger} from "../src/utils/logger";
8
+ import * as Zcl from "../src/zspec/zcl";
9
+ import * as Zdo from "../src/zspec/zdo";
10
+ import {uint16To8Array} from "./utils/math";
11
+
12
+ let sendZclFrameToEndpointResponse: ZclPayload | undefined;
13
+ let sendZdoResponse: unknown | undefined;
14
+
15
+ // no-op, makes up for too much of the perf loss (with console logging by default)
16
+ setLogger({
17
+ debug: () => {},
18
+ info: () => {},
19
+ warning: () => {},
20
+ error: () => {},
21
+ });
22
+
23
+ const database = Database.open("dummy");
24
+ // no-op
25
+ database.write = () => {};
26
+
27
+ const adapter = {
28
+ sendZclFrameToEndpoint: async () => Promise.resolve(sendZclFrameToEndpointResponse),
29
+ sendZclFrameToGroup: async () => Promise.resolve(),
30
+ sendZdo: async () => Promise.resolve(sendZdoResponse),
31
+ };
32
+
33
+ Entity.injectDatabase(database);
34
+ Entity.injectAdapter(adapter as unknown as Adapter);
35
+
36
+ const device = Device.create(
37
+ "Router",
38
+ "0xfe34ac2385ff8311",
39
+ 0x0001,
40
+ 0x0102,
41
+ "Herdsman",
42
+ "Mains (single phase)",
43
+ "Herd-01",
44
+ InterviewState.Successful,
45
+ undefined,
46
+ );
47
+ const endpoint = device.createEndpoint(1);
48
+ const group = Group.create(1);
49
+
50
+ group.addMember(endpoint);
51
+
52
+ const IEEE_ADDRESS1 = "0xfe34ac2385ff8311";
53
+ const IEEE_ADDRESS1_BYTES = [0x11, 0x83, 0xff, 0x85, 0x23, 0xac, 0x34, 0xfe];
54
+ const IEEE_ADDRESS2 = "0x28373fecd834ba37";
55
+ const IEEE_ADDRESS2_BYTES = [0x37, 0xba, 0x34, 0xd8, 0xec, 0x3f, 0x37, 0x28];
56
+ const NODE_ID1 = 0xfe32;
57
+ const NODE_ID1_BYTES = uint16To8Array(NODE_ID1);
58
+ const NODE_ID2 = 0xab39;
59
+ const NODE_ID2_BYTES = uint16To8Array(NODE_ID2);
60
+ const EXT_PAN_ID1 = [3, 43, 56, 23, 65, 23, 67, 23];
61
+ const EXT_PAN_ID2 = [253, 231, 21, 3, 0, 44, 24, 46];
62
+ const LQI_TABLE_RESPONSE = Buffer.from([
63
+ 1,
64
+ Zdo.Status.SUCCESS,
65
+ 2,
66
+ 3,
67
+ 2,
68
+ ...EXT_PAN_ID2,
69
+ ...IEEE_ADDRESS1_BYTES,
70
+ ...NODE_ID2_BYTES,
71
+ 0b00100101,
72
+ 0b00000001,
73
+ 1,
74
+ 235,
75
+ ...EXT_PAN_ID1,
76
+ ...IEEE_ADDRESS2_BYTES,
77
+ ...NODE_ID1_BYTES,
78
+ 0b01000010,
79
+ 0b00000000,
80
+ 1,
81
+ 179,
82
+ ]);
83
+
84
+ const BASIC_RESP = Zcl.Frame.create(
85
+ 0,
86
+ 1,
87
+ true,
88
+ undefined,
89
+ 10,
90
+ "readRsp",
91
+ 0,
92
+ [
93
+ {
94
+ attrId: 5,
95
+ dataType: Zcl.DataType.CHAR_STR,
96
+ attrData: device.modelID,
97
+ status: 0,
98
+ },
99
+ {
100
+ attrId: 4,
101
+ dataType: Zcl.DataType.CHAR_STR,
102
+ attrData: device.manufacturerName,
103
+ status: 0,
104
+ },
105
+ ],
106
+ {},
107
+ ).toBuffer();
108
+
109
+ describe("Requests", () => {
110
+ beforeEach(() => {
111
+ sendZclFrameToEndpointResponse = undefined;
112
+ sendZdoResponse = undefined;
113
+ });
114
+
115
+ bench(
116
+ "device lqi",
117
+ async () => {
118
+ sendZdoResponse = Zdo.Buffalo.readResponse(true, Zdo.ClusterId.LQI_TABLE_RESPONSE, LQI_TABLE_RESPONSE);
119
+ const resp = await device.lqi();
120
+
121
+ if (resp.neighbors[0].ieeeAddr !== IEEE_ADDRESS1 || resp.neighbors[1].ieeeAddr !== IEEE_ADDRESS2) {
122
+ throw new Error("Invalid response");
123
+ }
124
+ },
125
+ {throws: true},
126
+ );
127
+
128
+ bench(
129
+ "device.endpoint write basic",
130
+ async () => {
131
+ await endpoint.write("genBasic", {modelId: "Herd-02", manufacturerName: "HerdsmanNew"}, {sendPolicy: "immediate"});
132
+ },
133
+ {throws: true},
134
+ );
135
+
136
+ bench(
137
+ "device.endpoint read basic",
138
+ async () => {
139
+ sendZclFrameToEndpointResponse = {
140
+ clusterID: Zcl.Clusters.genBasic.ID,
141
+ header: Zcl.Header.fromBuffer(BASIC_RESP),
142
+ address: 0x0001,
143
+ data: BASIC_RESP,
144
+ endpoint: 1,
145
+ linkquality: 200,
146
+ groupID: 0,
147
+ wasBroadcast: false,
148
+ destinationEndpoint: 1,
149
+ };
150
+ const resp = await endpoint.read("genBasic", ["modelId", "manufacturerName"], {sendPolicy: "immediate"});
151
+
152
+ if (resp.modelId !== device.modelID || resp.manufacturerName !== device.manufacturerName) {
153
+ throw new Error("Invalid response");
154
+ }
155
+ },
156
+ {throws: true},
157
+ );
158
+
159
+ bench(
160
+ "device.endpoint defaultRsp",
161
+ async () => {
162
+ await endpoint.defaultResponse(0, 0, 0, 1);
163
+ },
164
+ {throws: true},
165
+ );
166
+
167
+ bench(
168
+ "device.endpoint command",
169
+ async () => {
170
+ await endpoint.command("genOnOff", "offWithEffect", {effectid: 1, effectvariant: 2}, {sendPolicy: "immediate"});
171
+ },
172
+ {throws: true},
173
+ );
174
+
175
+ bench(
176
+ "device.endpoint commandResponse",
177
+ async () => {
178
+ await endpoint.commandResponse("genAlarms", "alarm", {alarmcode: 123, clusterid: 456}, {sendPolicy: "immediate"});
179
+ },
180
+ {throws: true},
181
+ );
182
+
183
+ bench(
184
+ "group write basic",
185
+ async () => {
186
+ await group.write("genBasic", {modelId: "Herd-02", manufacturerName: "HerdsmanNew"});
187
+ },
188
+ {throws: true},
189
+ );
190
+
191
+ bench(
192
+ "group read basic",
193
+ async () => {
194
+ await group.read("genBasic", ["modelId", "manufacturerName"]);
195
+ },
196
+ {throws: true},
197
+ );
198
+
199
+ bench(
200
+ "group command",
201
+ async () => {
202
+ await group.command("genRssiLocation", "getDevCfg", {targetaddr: IEEE_ADDRESS1}, {});
203
+ },
204
+ {throws: true},
205
+ );
206
+ });
@@ -1,6 +1,8 @@
1
+ import codspeedPlugin from "@codspeed/vitest-plugin";
1
2
  import {defineConfig} from "vitest/config";
2
3
 
3
4
  export default defineConfig({
5
+ plugins: [codspeedPlugin()],
4
6
  test: {
5
7
  globals: true,
6
8
  onConsoleLog(_log: string, _type: "stdout" | "stderr"): boolean | undefined {
package/test/zcl.test.ts CHANGED
@@ -20,8 +20,6 @@ describe("Zcl", () => {
20
20
  // @ts-expect-error testing
21
21
  delete cluster1.getCommand;
22
22
  // @ts-expect-error testing
23
- delete cluster1.hasAttribute;
24
- // @ts-expect-error testing
25
23
  delete cluster1.getCommandResponse;
26
24
  const cluster2 = Zcl.Utils.getCluster("genBasic", undefined, {});
27
25
  // @ts-expect-error testing
@@ -29,8 +27,6 @@ describe("Zcl", () => {
29
27
  // @ts-expect-error testing
30
28
  delete cluster2.getCommand;
31
29
  // @ts-expect-error testing
32
- delete cluster2.hasAttribute;
33
- // @ts-expect-error testing
34
30
  delete cluster2.getCommandResponse;
35
31
  expect(cluster1).toStrictEqual(cluster2);
36
32
  });
@@ -43,10 +39,10 @@ describe("Zcl", () => {
43
39
 
44
40
  it("Cluster has attribute", () => {
45
41
  const cluster = Zcl.Utils.getCluster(0, undefined, {});
46
- expect(cluster.hasAttribute("zclVersion")).toBeTruthy();
47
- expect(cluster.hasAttribute("NOTEXISTING")).toBeFalsy();
48
- expect(cluster.hasAttribute(0)).toBeTruthy();
49
- expect(cluster.hasAttribute(910293)).toBeFalsy();
42
+ expect(cluster.getAttribute("zclVersion")).not.toBeUndefined();
43
+ expect(cluster.getAttribute("NOTEXISTING")).toBeUndefined();
44
+ expect(cluster.getAttribute(0)).not.toBeUndefined();
45
+ expect(cluster.getAttribute(910293)).toBeUndefined();
50
46
  });
51
47
 
52
48
  it("Get specific command by name", () => {
@@ -1917,26 +1913,26 @@ describe("Zcl", () => {
1917
1913
 
1918
1914
  it("Zcl utils get cluster attributes manufacturerCode wrong", () => {
1919
1915
  const cluster = Zcl.Utils.getCluster("closuresWindowCovering", 123, {});
1920
- expect(() => cluster.getAttribute(0x1000)).toThrow("Cluster 'closuresWindowCovering' has no attribute '4096'");
1916
+ expect(cluster.getAttribute(0x1000)).toBeUndefined();
1921
1917
  });
1922
1918
 
1923
1919
  it("Zcl utils get command", () => {
1924
1920
  const cluster = Zcl.Utils.getCluster("genOnOff", undefined, {});
1925
1921
  const command = cluster.getCommand(0);
1926
- expect(command.name).toEqual("off");
1927
- expect(cluster.getCommand("off")).toEqual(command);
1922
+ expect(command.name).toStrictEqual("off");
1923
+ expect(cluster.getCommand("off")).toStrictEqual(command);
1928
1924
  });
1929
1925
 
1930
1926
  it("Zcl utils get attribute", () => {
1931
1927
  const cluster = Zcl.Utils.getCluster("genOnOff", undefined, {});
1932
- const command = cluster.getAttribute(16385);
1933
- expect(command.name).toEqual("onTime");
1934
- expect(cluster.getAttribute("onTime")).toEqual(command);
1928
+ const attribute = cluster.getAttribute(16385);
1929
+ expect(attribute?.name).toStrictEqual("onTime");
1930
+ expect(cluster.getAttribute("onTime")).toStrictEqual(attribute);
1935
1931
  });
1936
1932
 
1937
1933
  it("Zcl utils get attribute non-existing", () => {
1938
1934
  const cluster = Zcl.Utils.getCluster("genOnOff", undefined, {});
1939
- expect(() => cluster.getAttribute("notExisting")).toThrow("Cluster 'genOnOff' has no attribute 'notExisting'");
1935
+ expect(cluster.getAttribute("notExisting")).toBeUndefined();
1940
1936
  });
1941
1937
 
1942
1938
  it("Zcl utils get command non-existing", () => {
@@ -280,13 +280,13 @@ const MANUF_SPE_FRAME_STRING = `{"header":{"frameControl":{"reservedBits":0,"fra
280
280
  describe("ZCL Frame", () => {
281
281
  describe("Validates Parameter Condition", () => {
282
282
  it("STATUS_EQUAL", () => {
283
- expect(Zcl.Frame.conditionsValid(Zcl.Foundation.readRsp.parameters[2], {status: 0}, null)).toBeTruthy();
284
- expect(Zcl.Frame.conditionsValid(Zcl.Foundation.readRsp.parameters[2], {status: 1}, null)).toBeFalsy();
283
+ expect(Zcl.Frame.conditionsValid(Zcl.Foundation.readRsp.parameters[2], {status: 0}, undefined)).toBeTruthy();
284
+ expect(Zcl.Frame.conditionsValid(Zcl.Foundation.readRsp.parameters[2], {status: 1}, undefined)).toBeFalsy();
285
285
  });
286
286
 
287
287
  it("STATUS_NOT_EQUAL", () => {
288
- expect(Zcl.Frame.conditionsValid(Zcl.Foundation.writeRsp.parameters[1], {status: 1}, null)).toBeTruthy();
289
- expect(Zcl.Frame.conditionsValid(Zcl.Foundation.writeRsp.parameters[1], {status: 0}, null)).toBeFalsy();
288
+ expect(Zcl.Frame.conditionsValid(Zcl.Foundation.writeRsp.parameters[1], {status: 1}, undefined)).toBeTruthy();
289
+ expect(Zcl.Frame.conditionsValid(Zcl.Foundation.writeRsp.parameters[1], {status: 0}, undefined)).toBeFalsy();
290
290
  });
291
291
 
292
292
  it("MINIMUM_REMAINING_BUFFER_BYTES", () => {
@@ -296,34 +296,34 @@ describe("ZCL Frame", () => {
296
296
 
297
297
  it("DIRECTION_EQUAL", () => {
298
298
  expect(
299
- Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[2], {direction: Zcl.Direction.CLIENT_TO_SERVER}, null),
299
+ Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[2], {direction: Zcl.Direction.CLIENT_TO_SERVER}, undefined),
300
300
  ).toBeTruthy();
301
301
  expect(
302
- Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[2], {direction: Zcl.Direction.SERVER_TO_CLIENT}, null),
302
+ Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[2], {direction: Zcl.Direction.SERVER_TO_CLIENT}, undefined),
303
303
  ).toBeFalsy();
304
304
  expect(
305
- Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[6], {direction: Zcl.Direction.SERVER_TO_CLIENT}, null),
305
+ Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[6], {direction: Zcl.Direction.SERVER_TO_CLIENT}, undefined),
306
306
  ).toBeTruthy();
307
307
  expect(
308
- Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[6], {direction: Zcl.Direction.CLIENT_TO_SERVER}, null),
308
+ Zcl.Frame.conditionsValid(Zcl.Foundation.configReport.parameters[6], {direction: Zcl.Direction.CLIENT_TO_SERVER}, undefined),
309
309
  ).toBeFalsy();
310
310
  });
311
311
 
312
312
  it("BITMASK_SET", () => {
313
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x4000}, null)).toBeTruthy();
314
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x4150}, null)).toBeTruthy();
315
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x0400}, null)).toBeFalsy();
316
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x1400}, null)).toBeFalsy();
313
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x4000}, undefined)).toBeTruthy();
314
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x4150}, undefined)).toBeTruthy();
315
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x0400}, undefined)).toBeFalsy();
316
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[8], {options: 0x1400}, undefined)).toBeFalsy();
317
317
  });
318
318
 
319
319
  it("BITFIELD_ENUM", () => {
320
320
  // {param:'options', offset: 0, size: 3, value: 0b000}
321
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b000}, null)).toBeTruthy();
322
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b1000}, null)).toBeTruthy();
323
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b001}, null)).toBeFalsy();
324
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b011}, null)).toBeFalsy();
325
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b100}, null)).toBeFalsy();
326
- expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b1010}, null)).toBeFalsy();
321
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b000}, undefined)).toBeTruthy();
322
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b1000}, undefined)).toBeTruthy();
323
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b001}, undefined)).toBeFalsy();
324
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b011}, undefined)).toBeFalsy();
325
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b100}, undefined)).toBeFalsy();
326
+ expect(Zcl.Frame.conditionsValid(Zcl.Clusters.greenPower.commands.notification.parameters[1], {options: 0b1010}, undefined)).toBeFalsy();
327
327
  });
328
328
 
329
329
  it("multiple including DATA_TYPE_CLASS_EQUAL", () => {
@@ -331,41 +331,41 @@ describe("ZCL Frame", () => {
331
331
  Zcl.Frame.conditionsValid(
332
332
  Zcl.Foundation.configReport.parameters[5],
333
333
  {direction: Zcl.Direction.CLIENT_TO_SERVER, dataType: Zcl.DataType.UINT8},
334
- null,
334
+ undefined,
335
335
  ),
336
336
  ).toBeTruthy();
337
337
  expect(
338
338
  Zcl.Frame.conditionsValid(
339
339
  Zcl.Foundation.configReport.parameters[5],
340
340
  {direction: Zcl.Direction.CLIENT_TO_SERVER, dataType: Zcl.DataType.DATA8},
341
- null,
341
+ undefined,
342
342
  ),
343
343
  ).toBeFalsy();
344
344
  expect(
345
345
  Zcl.Frame.conditionsValid(
346
346
  Zcl.Foundation.configReport.parameters[5],
347
347
  {direction: Zcl.Direction.SERVER_TO_CLIENT, dataType: Zcl.DataType.UINT8},
348
- null,
348
+ undefined,
349
349
  ),
350
350
  ).toBeFalsy();
351
351
  expect(
352
352
  Zcl.Frame.conditionsValid(
353
353
  Zcl.Foundation.configReport.parameters[5],
354
354
  {direction: Zcl.Direction.SERVER_TO_CLIENT, dataType: Zcl.DataType.DATA8},
355
- null,
355
+ undefined,
356
356
  ),
357
357
  ).toBeFalsy();
358
358
  });
359
359
 
360
360
  it("FIELD_EQUAL", () => {
361
361
  expect(
362
- Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 1}, null),
362
+ Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 1}, undefined),
363
363
  ).toBeTruthy();
364
364
  expect(
365
- Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 0}, null),
365
+ Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 0}, undefined),
366
366
  ).toBeFalsy();
367
367
  expect(
368
- Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 3}, null),
368
+ Zcl.Frame.conditionsValid(Zcl.Clusters.touchlink.commandsResponse.scanResponse.parameters[13], {numberOfSubDevices: 3}, undefined),
369
369
  ).toBeFalsy();
370
370
  });
371
371
  });
@@ -116,7 +116,6 @@ describe("ZCL Utils", () => {
116
116
  expect(cluster.getAttribute).toBeInstanceOf(Function);
117
117
  expect(cluster.getCommand).toBeInstanceOf(Function);
118
118
  expect(cluster.getCommandResponse).toBeInstanceOf(Function);
119
- expect(cluster.hasAttribute).toBeInstanceOf(Function);
120
119
  });
121
120
 
122
121
  it("Creates empty cluster when getting by invalid ID", () => {
@@ -130,7 +129,6 @@ describe("ZCL Utils", () => {
130
129
  expect(cluster.getAttribute).toBeInstanceOf(Function);
131
130
  expect(cluster.getCommand).toBeInstanceOf(Function);
132
131
  expect(cluster.getCommandResponse).toBeInstanceOf(Function);
133
- expect(cluster.hasAttribute).toBeInstanceOf(Function);
134
132
  });
135
133
 
136
134
  it("Throws when getting invalid cluster name", () => {
@@ -173,25 +171,19 @@ describe("ZCL Utils", () => {
173
171
  ])("Gets and checks cluster attribute %s", (_name, payload, expected) => {
174
172
  const cluster = Zcl.Utils.getCluster(expected.cluster.ID, payload.manufacturerCode, payload.customClusters);
175
173
  const attribute = cluster.getAttribute(payload.key);
176
- expect(cluster.hasAttribute(payload.key)).toBeTruthy();
174
+ expect(attribute).not.toBeUndefined();
177
175
  expect(attribute).toStrictEqual(cluster.attributes[expected.name]);
178
176
  });
179
177
 
180
- it("Throws when getting invalid attribute", () => {
178
+ it("Returns undefined when getting invalid attribute", () => {
181
179
  const cluster = Zcl.Utils.getCluster(Zcl.Clusters.genAlarms.ID, undefined, {});
182
- expect(() => {
183
- cluster.getAttribute("abcd");
184
- }).toThrow();
185
- expect(() => {
186
- cluster.getAttribute(99999);
187
- }).toThrow();
180
+ expect(cluster.getAttribute("abcd")).toBeUndefined();
181
+ expect(cluster.getAttribute(99999)).toBeUndefined();
188
182
  });
189
183
 
190
- it("Throws when getting attribute with invalid manufacturer code", () => {
184
+ it("Returns undefined when getting attribute with invalid manufacturer code", () => {
191
185
  const cluster = Zcl.Utils.getCluster(Zcl.Clusters.haDiagnostic.ID, 123, {});
192
- expect(() => {
193
- cluster.getAttribute(Zcl.Clusters.haDiagnostic.attributes.danfossSystemStatusCode.ID);
194
- }).toThrow();
186
+ expect(cluster.getAttribute(Zcl.Clusters.haDiagnostic.attributes.danfossSystemStatusCode.ID)).toBeUndefined();
195
187
  });
196
188
 
197
189
  it.each([