@willieee802/zigbee-herdsman 0.49.3 → 0.50.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 (211) hide show
  1. package/.github/dependabot.yml +0 -3
  2. package/.github/workflows/ci.yml +1 -2
  3. package/.github/workflows/release-please.yml +1 -1
  4. package/.github/workflows/typedoc.yaml +3 -3
  5. package/.release-please-manifest.json +1 -1
  6. package/CHANGELOG.md +143 -0
  7. package/biome.json +1 -1
  8. package/dist/adapter/adapter.d.ts +14 -1
  9. package/dist/adapter/adapter.d.ts.map +1 -1
  10. package/dist/adapter/adapter.js +17 -0
  11. package/dist/adapter/adapter.js.map +1 -1
  12. package/dist/adapter/adapterDiscovery.d.ts.map +1 -1
  13. package/dist/adapter/adapterDiscovery.js.map +1 -1
  14. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +1 -3
  15. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
  16. package/dist/adapter/deconz/adapter/deconzAdapter.js +14 -29
  17. package/dist/adapter/deconz/adapter/deconzAdapter.js.map +1 -1
  18. package/dist/adapter/deconz/driver/constants.d.ts +1 -1
  19. package/dist/adapter/deconz/driver/constants.d.ts.map +1 -1
  20. package/dist/adapter/ember/adapter/emberAdapter.d.ts +1 -1
  21. package/dist/adapter/ember/adapter/emberAdapter.d.ts.map +1 -1
  22. package/dist/adapter/ember/adapter/emberAdapter.js +19 -10
  23. package/dist/adapter/ember/adapter/emberAdapter.js.map +1 -1
  24. package/dist/adapter/ember/adapter/oneWaitress.d.ts +2 -0
  25. package/dist/adapter/ember/adapter/oneWaitress.d.ts.map +1 -1
  26. package/dist/adapter/ember/adapter/oneWaitress.js +13 -5
  27. package/dist/adapter/ember/adapter/oneWaitress.js.map +1 -1
  28. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts +1 -3
  29. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts.map +1 -1
  30. package/dist/adapter/ezsp/adapter/ezspAdapter.js +17 -30
  31. package/dist/adapter/ezsp/adapter/ezspAdapter.js.map +1 -1
  32. package/dist/adapter/ezsp/driver/index.d.ts +1 -1
  33. package/dist/adapter/ezsp/driver/index.d.ts.map +1 -1
  34. package/dist/adapter/ezsp/driver/index.js +1 -1
  35. package/dist/adapter/ezsp/driver/index.js.map +1 -1
  36. package/dist/adapter/ezsp/driver/types/index.d.ts +1 -1
  37. package/dist/adapter/ezsp/driver/types/index.d.ts.map +1 -1
  38. package/dist/adapter/ezsp/driver/types/index.js +3 -3
  39. package/dist/adapter/ezsp/driver/types/index.js.map +1 -1
  40. package/dist/adapter/serialPort.d.ts.map +1 -1
  41. package/dist/adapter/serialPort.js +7 -0
  42. package/dist/adapter/serialPort.js.map +1 -1
  43. package/dist/adapter/z-stack/adapter/adapter-backup.js +1 -1
  44. package/dist/adapter/z-stack/adapter/adapter-backup.js.map +1 -1
  45. package/dist/adapter/z-stack/adapter/adapter-nv-memory.js +1 -1
  46. package/dist/adapter/z-stack/adapter/adapter-nv-memory.js.map +1 -1
  47. package/dist/adapter/z-stack/adapter/manager.d.ts.map +1 -1
  48. package/dist/adapter/z-stack/adapter/manager.js +12 -2
  49. package/dist/adapter/z-stack/adapter/manager.js.map +1 -1
  50. package/dist/adapter/z-stack/adapter/tstype.d.ts.map +1 -1
  51. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts +1 -3
  52. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts.map +1 -1
  53. package/dist/adapter/z-stack/adapter/zStackAdapter.js +20 -34
  54. package/dist/adapter/z-stack/adapter/zStackAdapter.js.map +1 -1
  55. package/dist/adapter/z-stack/constants/index.d.ts +1 -1
  56. package/dist/adapter/z-stack/constants/index.d.ts.map +1 -1
  57. package/dist/adapter/z-stack/constants/index.js +1 -1
  58. package/dist/adapter/z-stack/constants/index.js.map +1 -1
  59. package/dist/adapter/z-stack/unpi/constants.d.ts +1 -1
  60. package/dist/adapter/z-stack/unpi/constants.d.ts.map +1 -1
  61. package/dist/adapter/z-stack/unpi/constants.js +1 -1
  62. package/dist/adapter/z-stack/unpi/constants.js.map +1 -1
  63. package/dist/adapter/zboss/adapter/zbossAdapter.d.ts +7 -8
  64. package/dist/adapter/zboss/adapter/zbossAdapter.d.ts.map +1 -1
  65. package/dist/adapter/zboss/adapter/zbossAdapter.js +12 -30
  66. package/dist/adapter/zboss/adapter/zbossAdapter.js.map +1 -1
  67. package/dist/adapter/zboss/driver.d.ts.map +1 -1
  68. package/dist/adapter/zboss/driver.js +8 -1
  69. package/dist/adapter/zboss/driver.js.map +1 -1
  70. package/dist/adapter/zboss/uart.d.ts.map +1 -1
  71. package/dist/adapter/zboss/uart.js +14 -2
  72. package/dist/adapter/zboss/uart.js.map +1 -1
  73. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts +1 -3
  74. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts.map +1 -1
  75. package/dist/adapter/zigate/adapter/zigateAdapter.js +8 -29
  76. package/dist/adapter/zigate/adapter/zigateAdapter.js.map +1 -1
  77. package/dist/adapter/zoh/adapter/zohAdapter.d.ts +1 -3
  78. package/dist/adapter/zoh/adapter/zohAdapter.d.ts.map +1 -1
  79. package/dist/adapter/zoh/adapter/zohAdapter.js +18 -33
  80. package/dist/adapter/zoh/adapter/zohAdapter.js.map +1 -1
  81. package/dist/controller/controller.d.ts.map +1 -1
  82. package/dist/controller/controller.js +10 -2
  83. package/dist/controller/controller.js.map +1 -1
  84. package/dist/controller/greenPower.d.ts.map +1 -1
  85. package/dist/controller/greenPower.js +15 -9
  86. package/dist/controller/greenPower.js.map +1 -1
  87. package/dist/controller/helpers/ota.d.ts +4 -4
  88. package/dist/controller/helpers/ota.d.ts.map +1 -1
  89. package/dist/controller/helpers/ota.js +28 -9
  90. package/dist/controller/helpers/ota.js.map +1 -1
  91. package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
  92. package/dist/controller/helpers/zclFrameConverter.js +17 -16
  93. package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
  94. package/dist/controller/model/device.d.ts +14 -4
  95. package/dist/controller/model/device.d.ts.map +1 -1
  96. package/dist/controller/model/device.js +167 -85
  97. package/dist/controller/model/device.js.map +1 -1
  98. package/dist/controller/model/endpoint.d.ts +7 -3
  99. package/dist/controller/model/endpoint.d.ts.map +1 -1
  100. package/dist/controller/model/endpoint.js +34 -21
  101. package/dist/controller/model/endpoint.js.map +1 -1
  102. package/dist/controller/model/group.d.ts +0 -1
  103. package/dist/controller/model/group.d.ts.map +1 -1
  104. package/dist/controller/model/group.js +14 -19
  105. package/dist/controller/model/group.js.map +1 -1
  106. package/dist/controller/touchlink.js +3 -3
  107. package/dist/controller/touchlink.js.map +1 -1
  108. package/dist/utils/timeService.js +2 -2
  109. package/dist/utils/timeService.js.map +1 -1
  110. package/dist/zspec/zcl/buffaloZcl.d.ts +3 -3
  111. package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -1
  112. package/dist/zspec/zcl/buffaloZcl.js +198 -96
  113. package/dist/zspec/zcl/buffaloZcl.js.map +1 -1
  114. package/dist/zspec/zcl/definition/cluster.d.ts +2 -2
  115. package/dist/zspec/zcl/definition/cluster.d.ts.map +1 -1
  116. package/dist/zspec/zcl/definition/cluster.js +2699 -2808
  117. package/dist/zspec/zcl/definition/cluster.js.map +1 -1
  118. package/dist/zspec/zcl/definition/clusters-types.d.ts +63 -1109
  119. package/dist/zspec/zcl/definition/clusters-types.d.ts.map +1 -1
  120. package/dist/zspec/zcl/definition/enums.d.ts +0 -1
  121. package/dist/zspec/zcl/definition/enums.d.ts.map +1 -1
  122. package/dist/zspec/zcl/definition/enums.js +0 -1
  123. package/dist/zspec/zcl/definition/enums.js.map +1 -1
  124. package/dist/zspec/zcl/definition/foundation.d.ts +306 -7
  125. package/dist/zspec/zcl/definition/foundation.d.ts.map +1 -1
  126. package/dist/zspec/zcl/definition/foundation.js +552 -207
  127. package/dist/zspec/zcl/definition/foundation.js.map +1 -1
  128. package/dist/zspec/zcl/definition/status.d.ts +21 -10
  129. package/dist/zspec/zcl/definition/status.d.ts.map +1 -1
  130. package/dist/zspec/zcl/definition/status.js +11 -0
  131. package/dist/zspec/zcl/definition/status.js.map +1 -1
  132. package/dist/zspec/zcl/definition/tstype.d.ts +57 -48
  133. package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
  134. package/dist/zspec/zcl/utils.d.ts +7 -4
  135. package/dist/zspec/zcl/utils.d.ts.map +1 -1
  136. package/dist/zspec/zcl/utils.js +133 -240
  137. package/dist/zspec/zcl/utils.js.map +1 -1
  138. package/dist/zspec/zcl/zclFrame.d.ts +4 -4
  139. package/dist/zspec/zcl/zclFrame.d.ts.map +1 -1
  140. package/dist/zspec/zcl/zclFrame.js +19 -103
  141. package/dist/zspec/zcl/zclFrame.js.map +1 -1
  142. package/dist/zspec/zcl/zclStatusError.d.ts +1 -1
  143. package/dist/zspec/zcl/zclStatusError.d.ts.map +1 -1
  144. package/dist/zspec/zcl/zclStatusError.js +2 -2
  145. package/dist/zspec/zcl/zclStatusError.js.map +1 -1
  146. package/package.json +1 -1
  147. package/scripts/clusters-typegen.ts +44 -139
  148. package/src/adapter/adapter.ts +38 -3
  149. package/src/adapter/adapterDiscovery.ts +2 -1
  150. package/src/adapter/deconz/adapter/deconzAdapter.ts +24 -51
  151. package/src/adapter/deconz/driver/constants.ts +1 -1
  152. package/src/adapter/ember/adapter/emberAdapter.ts +23 -10
  153. package/src/adapter/ember/adapter/oneWaitress.ts +16 -6
  154. package/src/adapter/ezsp/adapter/ezspAdapter.ts +27 -48
  155. package/src/adapter/ezsp/driver/index.ts +1 -1
  156. package/src/adapter/ezsp/driver/types/index.ts +99 -99
  157. package/src/adapter/serialPort.ts +9 -0
  158. package/src/adapter/z-stack/adapter/adapter-backup.ts +1 -1
  159. package/src/adapter/z-stack/adapter/adapter-nv-memory.ts +1 -1
  160. package/src/adapter/z-stack/adapter/manager.ts +16 -2
  161. package/src/adapter/z-stack/adapter/tstype.ts +1 -0
  162. package/src/adapter/z-stack/adapter/zStackAdapter.ts +34 -81
  163. package/src/adapter/z-stack/constants/index.ts +1 -1
  164. package/src/adapter/z-stack/unpi/constants.ts +1 -1
  165. package/src/adapter/zboss/adapter/zbossAdapter.ts +23 -54
  166. package/src/adapter/zboss/driver.ts +8 -1
  167. package/src/adapter/zboss/uart.ts +14 -1
  168. package/src/adapter/zigate/adapter/zigateAdapter.ts +17 -48
  169. package/src/adapter/zoh/adapter/zohAdapter.ts +27 -50
  170. package/src/controller/controller.ts +12 -2
  171. package/src/controller/greenPower.ts +16 -9
  172. package/src/controller/helpers/ota.ts +37 -11
  173. package/src/controller/helpers/zclFrameConverter.ts +20 -17
  174. package/src/controller/model/device.ts +204 -97
  175. package/src/controller/model/endpoint.ts +36 -24
  176. package/src/controller/model/group.ts +14 -20
  177. package/src/controller/touchlink.ts +3 -3
  178. package/src/utils/timeService.ts +2 -2
  179. package/src/zspec/zcl/buffaloZcl.ts +226 -100
  180. package/src/zspec/zcl/definition/cluster.ts +2713 -2822
  181. package/src/zspec/zcl/definition/clusters-types.ts +80 -1135
  182. package/src/zspec/zcl/definition/enums.ts +0 -1
  183. package/src/zspec/zcl/definition/foundation.ts +703 -216
  184. package/src/zspec/zcl/definition/status.ts +22 -11
  185. package/src/zspec/zcl/definition/tstype.ts +59 -58
  186. package/src/zspec/zcl/utils.ts +137 -264
  187. package/src/zspec/zcl/zclFrame.ts +25 -130
  188. package/src/zspec/zcl/zclStatusError.ts +2 -2
  189. package/test/adapter/ember/emberAdapter.test.ts +191 -4
  190. package/test/adapter/ezsp/uart.test.ts +10 -10
  191. package/test/adapter/z-stack/adapter.test.ts +88 -32
  192. package/test/adapter/zoh/zohAdapter.test.ts +4 -4
  193. package/test/controller.test.ts +822 -248
  194. package/test/device-ota.test.ts +141 -16
  195. package/test/device.test.ts +731 -0
  196. package/test/requests.bench.ts +2 -0
  197. package/test/zcl.test.ts +70 -95
  198. package/test/zspec/zcl/buffalo.test.ts +251 -11
  199. package/test/zspec/zcl/foundation.test.ts +990 -0
  200. package/test/zspec/zcl/frame.test.ts +84 -69
  201. package/test/zspec/zcl/utils.test.ts +105 -81
  202. package/tsconfig.json +0 -1
  203. package/scripts/check-clusters-changes.ts +0 -328
  204. package/scripts/clusters-changes.log +0 -584
  205. package/scripts/utils.ts +0 -88
  206. package/scripts/zap-update-clusters-report.json +0 -303
  207. package/scripts/zap-update-clusters.ts +0 -1520
  208. package/scripts/zap-update-types.ts +0 -707
  209. package/scripts/zap-xml-clusters-overrides-data.ts +0 -52
  210. package/scripts/zap-xml-clusters-overrides.ts +0 -400
  211. package/scripts/zap-xml-types.ts +0 -146
@@ -10,6 +10,7 @@ import Request from "../src/controller/helpers/request";
10
10
  import zclTransactionSequenceNumber from "../src/controller/helpers/zclTransactionSequenceNumber";
11
11
  import {Device, Endpoint, Group} from "../src/controller/model";
12
12
  import {InterviewState} from "../src/controller/model/device";
13
+ import type {TCustomCluster} from "../src/controller/tstype";
13
14
  import type * as Models from "../src/models";
14
15
  import * as Utils from "../src/utils";
15
16
  import {setLogger} from "../src/utils/logger";
@@ -207,9 +208,11 @@ const mockAdapterSendZdo = vi
207
208
 
208
209
  let iasZoneReadState170Count = 0;
209
210
  let enroll170 = true;
211
+ let enrollRspThrow = false;
210
212
  let configureReportStatus = 0;
211
213
  let configureReportDefaultRsp = false;
212
214
  let lastSentZclFrameToEndpoint: Buffer | undefined;
215
+ let readManufacturerCode: number | undefined;
213
216
 
214
217
  const restoreMocksendZclFrameToEndpoint = () => {
215
218
  mocksendZclFrameToEndpoint.mockImplementation((_ieeeAddr, networkAddress, endpoint, frame: Zcl.Frame) => {
@@ -223,24 +226,31 @@ const restoreMocksendZclFrameToEndpoint = () => {
223
226
  const payload: {[key: string]: unknown}[] = [];
224
227
  const cluster = frame.cluster;
225
228
  for (const item of frame.payload) {
226
- if (item.attrId !== 65314) {
227
- const attribute = cluster.getAttribute(item.attrId);
229
+ if (item.attrId === 65314) {
230
+ payload.push({
231
+ attrId: item.attrId,
232
+ status: Zcl.Status.SUCCESS,
233
+ dataType: Zcl.DataType.UINT16,
234
+ attrData: 0x4312,
235
+ });
236
+ } else {
237
+ const attribute = Zcl.Utils.getClusterAttribute(cluster, item.attrId, readManufacturerCode);
228
238
 
229
239
  if (attribute) {
230
240
  if (frame.isCluster("ssIasZone") && item.attrId === 0) {
231
241
  iasZoneReadState170Count++;
232
242
  payload.push({
233
243
  attrId: item.attrId,
244
+ status: Zcl.Status.SUCCESS,
234
245
  dataType: attribute.type,
235
246
  attrData: iasZoneReadState170Count === 2 && enroll170 ? 1 : 0,
236
- status: 0,
237
247
  });
238
248
  } else {
239
249
  payload.push({
240
250
  attrId: item.attrId,
251
+ status: Zcl.Status.SUCCESS,
241
252
  dataType: attribute.type,
242
253
  attrData: MOCK_DEVICES[networkAddress]!.attributes![endpoint][attribute.name],
243
- status: 0,
244
254
  });
245
255
  }
246
256
  }
@@ -260,12 +270,16 @@ const restoreMocksendZclFrameToEndpoint = () => {
260
270
  10,
261
271
  `${frame.command.name}Rsp`,
262
272
  frame.cluster.ID,
263
- {status: 0, groupid: 1},
273
+ {status: Zcl.Status.SUCCESS, groupid: 1},
264
274
  {},
265
275
  );
266
276
  return {clusterID: frame.cluster.ID, header: responseFrame.header, data: responseFrame.toBuffer()};
267
277
  }
268
278
 
279
+ if (frame.header.isSpecific && frame.command.name === "enrollRsp" && frame.cluster.name === "ssIasZone" && enrollRspThrow) {
280
+ throw new Error("ias-zone-failure");
281
+ }
282
+
269
283
  if (
270
284
  networkAddress === 170 &&
271
285
  frame.header.isGlobal &&
@@ -301,7 +315,7 @@ const restoreMocksendZclFrameToEndpoint = () => {
301
315
  if (frame.header.isGlobal && frame.isCommand("write")) {
302
316
  const payload: {[key: string]: unknown}[] = [];
303
317
  for (const item of frame.payload) {
304
- payload.push({attrId: item.attrId, status: 0});
318
+ payload.push({attrId: item.attrId, status: Zcl.Status.SUCCESS});
305
319
  }
306
320
 
307
321
  const responseFrame = Zcl.Frame.create(0, 1, true, undefined, 10, "writeRsp", 0, payload, {});
@@ -460,6 +474,78 @@ const options = {
460
474
 
461
475
  const databaseContents = () => fs.readFileSync(options.databasePath).toString();
462
476
 
477
+ const CUSTOM_CLUSTERS = {
478
+ hvacThermostat: {
479
+ name: "hvacThermostat",
480
+ ID: 0x0201,
481
+ attributes: {
482
+ viessmannWindowOpenInternal: {
483
+ name: "viessmannWindowOpenInternal",
484
+ ID: 0x4000,
485
+ type: Zcl.DataType.ENUM8,
486
+ manufacturerCode: Zcl.ManufacturerCode.VIESSMANN_ELEKTRONIK_GMBH,
487
+ write: true,
488
+ max: 0xff,
489
+ },
490
+ },
491
+ commands: {},
492
+ commandsResponse: {},
493
+ },
494
+ closuresWindowCovering: {
495
+ name: "closuresWindowCovering",
496
+ ID: Zcl.Clusters.closuresWindowCovering.ID,
497
+ attributes: {
498
+ calibrationMode: {
499
+ name: "calibrationMode",
500
+ ID: 0xf002,
501
+ type: Zcl.DataType.ENUM8,
502
+ manufacturerCode: Zcl.ManufacturerCode.LEGRAND_GROUP,
503
+ write: true,
504
+ max: 0xff,
505
+ },
506
+ tuyaMotorReversal: {name: "tuyaMotorReversal", ID: 0xf002, type: Zcl.DataType.ENUM8, write: true, max: 0xff},
507
+ },
508
+ commands: {},
509
+ commandsResponse: {},
510
+ },
511
+ lightingColorCtrl: {
512
+ name: "lightingColorCtrl",
513
+ ID: Zcl.Clusters.lightingColorCtrl.ID,
514
+ attributes: {},
515
+ commands: {
516
+ tuyaMoveToHueAndSaturationBrightness: {
517
+ name: "tuyaMoveToHueAndSaturationBrightness",
518
+ ID: 0x06,
519
+ parameters: [
520
+ {name: "hue", type: Zcl.DataType.UINT8, max: 0xff},
521
+ {name: "saturation", type: Zcl.DataType.UINT8, max: 0xff},
522
+ {name: "transtime", type: Zcl.DataType.UINT16, max: 0xffff},
523
+ {name: "brightness", type: Zcl.DataType.UINT8, max: 0xff},
524
+ ],
525
+ },
526
+ },
527
+ commandsResponse: {},
528
+ },
529
+ } satisfies CustomClusters;
530
+
531
+ interface CustomClustersTypes extends Record<string, TCustomCluster> {
532
+ hvacThermostat: {
533
+ attributes: {
534
+ viessmannWindowOpenInternal: number;
535
+ };
536
+ commands: never;
537
+ commandResponses: never;
538
+ };
539
+ closuresWindowCovering: {
540
+ attributes: {
541
+ calibrationMode: number;
542
+ tuyaMotorReversal: number;
543
+ };
544
+ commands: never;
545
+ commandResponses: never;
546
+ };
547
+ }
548
+
463
549
  describe("Controller", () => {
464
550
  let controller: Controller;
465
551
  let mockedDate: Date;
@@ -480,6 +566,7 @@ describe("Controller", () => {
480
566
 
481
567
  beforeEach(() => {
482
568
  vi.setSystemTime(mockedDate);
569
+ readManufacturerCode = undefined;
483
570
  sendZdoResponseStatus = Zdo.Status.SUCCESS;
484
571
  for (const m of mocksRestore) m.mockRestore();
485
572
  for (const m of mocksClear) m.mockClear();
@@ -490,6 +577,7 @@ describe("Controller", () => {
490
577
  configureReportStatus = 0;
491
578
  configureReportDefaultRsp = false;
492
579
  enroll170 = true;
580
+ enrollRspThrow = false;
493
581
  options.network.channelList = [15];
494
582
 
495
583
  for (const event in events) {
@@ -2297,8 +2385,7 @@ describe("Controller", () => {
2297
2385
 
2298
2386
  it("Device joins and interview iAs enrollment succeeds", async () => {
2299
2387
  await controller.start();
2300
- const event = mockAdapterEvents.deviceJoined({networkAddress: 170, ieeeAddr: "0x170"});
2301
- await event;
2388
+ await mockAdapterEvents.deviceJoined({networkAddress: 170, ieeeAddr: "0x170"});
2302
2389
  expect(events.deviceInterview.length).toBe(2);
2303
2390
  expect(events.deviceInterview[0].status).toBe("started");
2304
2391
  // @ts-expect-error private but deep cloned
@@ -2325,7 +2412,6 @@ describe("Controller", () => {
2325
2412
  command: {
2326
2413
  ID: 2,
2327
2414
  name: "write",
2328
- parameters: expect.any(Array),
2329
2415
  response: 4,
2330
2416
  },
2331
2417
  });
@@ -2337,7 +2423,7 @@ describe("Controller", () => {
2337
2423
  expect(deepClone(enrollRsp[3])).toStrictEqual({
2338
2424
  header: {
2339
2425
  frameControl: {reservedBits: 0, frameType: 1, direction: 0, disableDefaultResponse: true, manufacturerSpecific: false},
2340
- transactionSequenceNumber: 11,
2426
+ transactionSequenceNumber: 1,
2341
2427
  commandIdentifier: 0,
2342
2428
  },
2343
2429
  payload: {enrollrspcode: 0, zoneid: 23},
@@ -2368,6 +2454,20 @@ describe("Controller", () => {
2368
2454
  expect(events.deviceInterview[1].device._ieeeAddr).toBe("0x170");
2369
2455
  });
2370
2456
 
2457
+ it("Device joins and interview iAs enrollment throws", async () => {
2458
+ await controller.start();
2459
+ enrollRspThrow = true;
2460
+ await mockAdapterEvents.deviceJoined({networkAddress: 170, ieeeAddr: "0x170"});
2461
+
2462
+ expect(events.deviceInterview.length).toBe(2);
2463
+ expect(events.deviceInterview[0].status).toBe("started");
2464
+ // @ts-expect-error private but deep cloned
2465
+ expect(events.deviceInterview[0].device._ieeeAddr).toBe("0x170");
2466
+ expect(events.deviceInterview[1].status).toBe("failed");
2467
+ // @ts-expect-error private but deep cloned
2468
+ expect(events.deviceInterview[1].device._ieeeAddr).toBe("0x170");
2469
+ });
2470
+
2371
2471
  it("Device joins, shouldnt enroll when already enrolled", async () => {
2372
2472
  await controller.start();
2373
2473
  iasZoneReadState170Count = 1;
@@ -2532,9 +2632,9 @@ describe("Controller", () => {
2532
2632
  const endpointReadSpy = vi.spyOn(endpoint, "read");
2533
2633
  const endpointDefaultResponseSpy = vi.spyOn(endpoint, "defaultResponse");
2534
2634
 
2535
- deviceOnZclDataSpy.mockImplementationOnce(async (a, b, c) => {
2635
+ deviceOnZclDataSpy.mockImplementationOnce(async (a, b, c, d) => {
2536
2636
  await mockAdapterEvents.deviceLeave({networkAddress: 129, ieeeAddr: undefined});
2537
- await device.onZclData(a, b, c);
2637
+ await device.onZclData(a, b, c, d);
2538
2638
  });
2539
2639
 
2540
2640
  await mockAdapterEvents.zclPayload({
@@ -2600,8 +2700,17 @@ describe("Controller", () => {
2600
2700
  });
2601
2701
 
2602
2702
  it("Receive cluster command", async () => {
2603
- const buffer = Buffer.from([0x05, 0x7c, 0x11, 0x1d, 0x07, 0x00, 0x01, 0x0d, 0x00]);
2604
- const frame = Zcl.Frame.fromBuffer(5, Zcl.Header.fromBuffer(buffer), buffer, {});
2703
+ const frame = Zcl.Frame.create(
2704
+ Zcl.FrameType.SPECIFIC,
2705
+ Zcl.Direction.SERVER_TO_CLIENT,
2706
+ false,
2707
+ undefined,
2708
+ 29,
2709
+ "addRsp",
2710
+ "genScenes",
2711
+ {status: 0, groupId: 10, sceneId: 2},
2712
+ {},
2713
+ );
2605
2714
  await controller.start();
2606
2715
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
2607
2716
  await mockAdapterEvents.zclPayload({
@@ -2618,24 +2727,25 @@ describe("Controller", () => {
2618
2727
  expect(events.message.length).toBe(1);
2619
2728
  const expected = {
2620
2729
  cluster: "genScenes",
2621
- type: "commandTradfriArrowSingle",
2730
+ type: "commandAddRsp",
2622
2731
  device: expect.any(Device),
2623
2732
  endpoint: expect.any(Endpoint),
2624
2733
  data: {
2625
- value: 256,
2626
- value2: 13,
2734
+ groupId: 10,
2735
+ sceneId: 2,
2736
+ status: 0,
2627
2737
  },
2628
2738
  linkquality: 19,
2629
2739
  groupID: 10,
2630
2740
  meta: {
2631
2741
  zclTransactionSequenceNumber: 29,
2632
- manufacturerCode: 4476,
2742
+ manufacturerCode: undefined,
2633
2743
  frameControl: {
2634
2744
  reservedBits: 0,
2635
- direction: 0,
2745
+ direction: 1,
2636
2746
  disableDefaultResponse: false,
2637
2747
  frameType: 1,
2638
- manufacturerSpecific: true,
2748
+ manufacturerSpecific: false,
2639
2749
  },
2640
2750
  rawData: expect.any(Buffer),
2641
2751
  },
@@ -2680,21 +2790,14 @@ describe("Controller", () => {
2680
2790
 
2681
2791
  it("Receive zclData send default response", async () => {
2682
2792
  const frame = Zcl.Frame.create(
2683
- 1,
2684
- 1,
2793
+ Zcl.FrameType.SPECIFIC,
2794
+ Zcl.Direction.SERVER_TO_CLIENT,
2685
2795
  false,
2686
- 4476,
2796
+ Zcl.ManufacturerCode.IKEA_OF_SWEDEN,
2687
2797
  29,
2688
- 1,
2689
- 5,
2690
- {
2691
- groupid: 1,
2692
- sceneid: 1,
2693
- status: 0,
2694
- transtime: 0,
2695
- scenename: "",
2696
- extensionfieldsets: [],
2697
- },
2798
+ "viewRsp",
2799
+ "genScenes",
2800
+ {groupid: 1, sceneid: 1, status: 0, transtime: 0, scenename: "", extensionfieldsets: []},
2698
2801
  {},
2699
2802
  );
2700
2803
  await controller.start();
@@ -2986,11 +3089,8 @@ describe("Controller", () => {
2986
3089
  it("Respond to read of attribute", async () => {
2987
3090
  await controller.start();
2988
3091
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
2989
- const device = controller.getDeviceByIeeeAddr("0x129")!;
2990
- const endpoint = device.getEndpoint(1)!;
2991
- endpoint.saveClusterAttributeKeyValue("hvacThermostat", {systemMode: 3});
2992
3092
  mocksendZclFrameToEndpoint.mockClear();
2993
- const frame = Zcl.Frame.create(0, 0, true, undefined, 40, 0, 513, [{attrId: 28}, {attrId: 290}], {});
3093
+ const frame = Zcl.Frame.create(0, 0, true, undefined, 40, "read", "genTime", [{attrId: 0x0000}, {attrId: 0xfffa}], {});
2994
3094
  await mockAdapterEvents.zclPayload({
2995
3095
  wasBroadcast: false,
2996
3096
  address: 129,
@@ -3013,12 +3113,38 @@ describe("Controller", () => {
3013
3113
  transactionSequenceNumber: 40,
3014
3114
  commandIdentifier: 1,
3015
3115
  },
3016
- payload: [{attrId: 28, attrData: 3, dataType: 48, status: 0}],
3116
+ payload: [
3117
+ {attrId: 0xfffa, status: Zcl.Status.UNSUPPORTED_ATTRIBUTE},
3118
+ {attrId: 0x0000, attrData: expect.any(Number), dataType: Zcl.DataType.UTC, status: Zcl.Status.SUCCESS},
3119
+ ],
3017
3120
  cluster: null,
3018
- command: expect.objectContaining({
3019
- ID: 1,
3020
- name: "readRsp",
3021
- }),
3121
+ command: expect.objectContaining({ID: 1, name: "readRsp"}),
3122
+ });
3123
+ });
3124
+
3125
+ it("Sends read response for unsupported attribute", async () => {
3126
+ await controller.start();
3127
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
3128
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
3129
+ const endpoint = device.getEndpoint(1)!;
3130
+ mocksendZclFrameToEndpoint.mockClear();
3131
+
3132
+ endpoint.readResponse("genGroups", 1, {nameSupport: undefined}, {srcEndpoint: 1});
3133
+
3134
+ expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(1);
3135
+ const call = mocksendZclFrameToEndpoint.mock.calls[0];
3136
+ expect(call[0]).toBe("0x129");
3137
+ expect(call[1]).toBe(129);
3138
+ expect(call[2]).toBe(1);
3139
+ expect(deepClone({...call[3], cluster: null})).toStrictEqual({
3140
+ header: {
3141
+ frameControl: {reservedBits: 0, frameType: 0, direction: 1, disableDefaultResponse: true, manufacturerSpecific: false},
3142
+ transactionSequenceNumber: 1,
3143
+ commandIdentifier: 1,
3144
+ },
3145
+ payload: [{attrId: 0x0000, status: Zcl.Status.UNSUPPORTED_ATTRIBUTE}],
3146
+ cluster: null,
3147
+ command: expect.objectContaining({ID: 1, name: "readRsp"}),
3022
3148
  });
3023
3149
  });
3024
3150
 
@@ -3330,12 +3456,13 @@ describe("Controller", () => {
3330
3456
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
3331
3457
  const device = controller.getDeviceByIeeeAddr("0x129")!;
3332
3458
  device.addCustomCluster("genBasic", {
3459
+ name: "genBasic",
3333
3460
  ID: 0,
3334
3461
  commands: {},
3335
3462
  commandsResponse: {},
3336
3463
  attributes: {
3337
- customAttr: {ID: 256, type: Zcl.DataType.UINT8},
3338
- aDifferentZclVersion: {ID: 0, type: Zcl.DataType.UINT8},
3464
+ customAttr: {name: "customAttr", ID: 256, type: Zcl.DataType.UINT8},
3465
+ aDifferentZclVersion: {name: "aDifferentZclVersion", ID: 0, type: Zcl.DataType.UINT8},
3339
3466
  },
3340
3467
  });
3341
3468
  const buffer = Buffer.from([24, 169, 10, 0, 1, 24, 3, 0, 0, 24, 1, 2, 0, 24, 1]);
@@ -3348,11 +3475,12 @@ describe("Controller", () => {
3348
3475
 
3349
3476
  // Should allow to extend an already extended cluster again.
3350
3477
  device.addCustomCluster("genBasic", {
3478
+ name: "genBasic",
3351
3479
  ID: 0,
3352
3480
  commands: {},
3353
3481
  commandsResponse: {},
3354
3482
  attributes: {
3355
- customAttrSecondOverride: {ID: 256, type: Zcl.DataType.UINT8},
3483
+ customAttrSecondOverride: {name: "customAttrSecondOverride", ID: 256, type: Zcl.DataType.UINT8},
3356
3484
  },
3357
3485
  });
3358
3486
  await mockAdapterEvents.zclPayload(payload);
@@ -3366,10 +3494,11 @@ describe("Controller", () => {
3366
3494
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
3367
3495
  const device = controller.getDeviceByIeeeAddr("0x129")!;
3368
3496
  device.addCustomCluster("myCustomCluster", {
3497
+ name: "myCustomCluster",
3369
3498
  ID: 9123,
3370
3499
  commands: {},
3371
3500
  commandsResponse: {},
3372
- attributes: {superAttribute: {ID: 0, type: Zcl.DataType.UINT8}},
3501
+ attributes: {superAttribute: {name: "superAttribute", ID: 0, type: Zcl.DataType.UINT8}},
3373
3502
  });
3374
3503
  const buffer = Buffer.from([24, 169, 10, 0, 1, 24, 3, 0, 0, 24, 1]);
3375
3504
  const header = Zcl.Header.fromBuffer(buffer);
@@ -3393,10 +3522,11 @@ describe("Controller", () => {
3393
3522
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
3394
3523
  const device = controller.getDeviceByIeeeAddr("0x129")!;
3395
3524
  device.addCustomCluster("myCustomCluster", {
3525
+ name: "myCustomCluster",
3396
3526
  ID: Zcl.Clusters.genBasic.ID,
3397
3527
  commands: {},
3398
3528
  commandsResponse: {},
3399
- attributes: {customAttr: {ID: 256, type: Zcl.DataType.UINT8}},
3529
+ attributes: {customAttr: {name: "customAttr", ID: 256, type: Zcl.DataType.UINT8}},
3400
3530
  });
3401
3531
  const buffer = Buffer.from([24, 169, 10, 0, 1, 24, 3, 0, 0, 24, 1]);
3402
3532
  const header = Zcl.Header.fromBuffer(buffer);
@@ -3462,17 +3592,28 @@ describe("Controller", () => {
3462
3592
  }
3463
3593
 
3464
3594
  device.addCustomCluster("hvacThermostat", {
3595
+ name: "hvacThermostat",
3465
3596
  ID: 0x0201,
3466
3597
  attributes: {
3467
- localTemperatureCalibration: {ID: 0x0010, type: Zcl.DataType.INT8, write: true, min: -50, max: 50, default: 0},
3598
+ localTemperatureCalibration: {
3599
+ name: "localTemperatureCalibration",
3600
+ ID: 0x0010,
3601
+ type: Zcl.DataType.INT8,
3602
+ write: true,
3603
+ min: -50,
3604
+ max: 50,
3605
+ default: 0,
3606
+ },
3468
3607
  },
3469
3608
  commands: {},
3470
3609
  commandsResponse: {},
3471
3610
  });
3472
3611
  device.addCustomCluster("hvacThermostat", {
3612
+ name: "hvacThermostat",
3473
3613
  ID: Zcl.Clusters.hvacThermostat.ID,
3474
3614
  attributes: {
3475
3615
  operatingMode: {
3616
+ name: "operatingMode",
3476
3617
  ID: 0x4007,
3477
3618
  type: Zcl.DataType.ENUM8,
3478
3619
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3480,6 +3621,7 @@ describe("Controller", () => {
3480
3621
  max: 0xff,
3481
3622
  },
3482
3623
  heatingDemand: {
3624
+ name: "heatingDemand",
3483
3625
  ID: 0x4020,
3484
3626
  type: Zcl.DataType.ENUM8,
3485
3627
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3487,6 +3629,7 @@ describe("Controller", () => {
3487
3629
  max: 0xff,
3488
3630
  },
3489
3631
  valveAdaptStatus: {
3632
+ name: "valveAdaptStatus",
3490
3633
  ID: 0x4022,
3491
3634
  type: Zcl.DataType.ENUM8,
3492
3635
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3494,6 +3637,7 @@ describe("Controller", () => {
3494
3637
  max: 0xff,
3495
3638
  },
3496
3639
  unknownAttribute0: {
3640
+ name: "unknownAttribute0",
3497
3641
  ID: 0x4025,
3498
3642
  type: Zcl.DataType.ENUM8,
3499
3643
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3501,6 +3645,7 @@ describe("Controller", () => {
3501
3645
  max: 0xff,
3502
3646
  },
3503
3647
  remoteTemperature: {
3648
+ name: "remoteTemperature",
3504
3649
  ID: 0x4040,
3505
3650
  type: Zcl.DataType.INT16,
3506
3651
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3508,6 +3653,7 @@ describe("Controller", () => {
3508
3653
  min: -32768,
3509
3654
  },
3510
3655
  unknownAttribute1: {
3656
+ name: "unknownAttribute1",
3511
3657
  ID: 0x4041,
3512
3658
  type: Zcl.DataType.ENUM8,
3513
3659
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3515,6 +3661,7 @@ describe("Controller", () => {
3515
3661
  max: 0xff,
3516
3662
  },
3517
3663
  windowOpenMode: {
3664
+ name: "windowOpenMode",
3518
3665
  ID: 0x4042,
3519
3666
  type: Zcl.DataType.ENUM8,
3520
3667
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3522,6 +3669,7 @@ describe("Controller", () => {
3522
3669
  max: 0xff,
3523
3670
  },
3524
3671
  boostHeating: {
3672
+ name: "boostHeating",
3525
3673
  ID: 0x4043,
3526
3674
  type: Zcl.DataType.ENUM8,
3527
3675
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3529,14 +3677,23 @@ describe("Controller", () => {
3529
3677
  max: 0xff,
3530
3678
  },
3531
3679
  cableSensorTemperature: {
3680
+ name: "cableSensorTemperature",
3532
3681
  ID: 0x4052,
3533
3682
  type: Zcl.DataType.INT16,
3534
3683
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
3535
3684
  write: true,
3536
3685
  min: -32768,
3537
3686
  },
3538
- valveType: {ID: 0x4060, type: Zcl.DataType.ENUM8, manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, write: true, max: 0xff},
3687
+ valveType: {
3688
+ name: "valveType",
3689
+ ID: 0x4060,
3690
+ type: Zcl.DataType.ENUM8,
3691
+ manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
3692
+ write: true,
3693
+ max: 0xff,
3694
+ },
3539
3695
  unknownAttribute2: {
3696
+ name: "unknownAttribute2",
3540
3697
  ID: 0x4061,
3541
3698
  type: Zcl.DataType.ENUM8,
3542
3699
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3544,15 +3701,30 @@ describe("Controller", () => {
3544
3701
  max: 0xff,
3545
3702
  },
3546
3703
  cableSensorMode: {
3704
+ name: "cableSensorMode",
3547
3705
  ID: 0x4062,
3548
3706
  type: Zcl.DataType.ENUM8,
3549
3707
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
3550
3708
  write: true,
3551
3709
  max: 0xff,
3552
3710
  },
3553
- heaterType: {ID: 0x4063, type: Zcl.DataType.ENUM8, manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, write: true, max: 0xff},
3554
- errorState: {ID: 0x5000, type: Zcl.DataType.BITMAP8, manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, write: true},
3711
+ heaterType: {
3712
+ name: "heaterType",
3713
+ ID: 0x4063,
3714
+ type: Zcl.DataType.ENUM8,
3715
+ manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
3716
+ write: true,
3717
+ max: 0xff,
3718
+ },
3719
+ errorState: {
3720
+ name: "errorState",
3721
+ ID: 0x5000,
3722
+ type: Zcl.DataType.BITMAP8,
3723
+ manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
3724
+ write: true,
3725
+ },
3555
3726
  automaticValveAdapt: {
3727
+ name: "automaticValveAdapt",
3556
3728
  ID: 0x5010,
3557
3729
  type: Zcl.DataType.ENUM8,
3558
3730
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
@@ -3561,7 +3733,7 @@ describe("Controller", () => {
3561
3733
  },
3562
3734
  },
3563
3735
  commands: {
3564
- calibrateValve: {ID: 0x41, parameters: []},
3736
+ calibrateValve: {name: "calibrateValve", ID: 0x41, parameters: []},
3565
3737
  },
3566
3738
  commandsResponse: {},
3567
3739
  });
@@ -3590,11 +3762,13 @@ describe("Controller", () => {
3590
3762
  ),
3591
3763
  ).rejects.toThrow("localTemperatureCalibration requires max of 50");
3592
3764
 
3765
+ readManufacturerCode = Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH;
3593
3766
  await expect(
3594
3767
  endpoint.read<"hvacThermostat", BoschThermostatCluster>("hvacThermostat", ["boostHeating", "operatingMode"], {
3595
3768
  manufacturerCode: Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
3596
3769
  }),
3597
3770
  ).resolves.toStrictEqual({16391: 0, 16451: 0});
3771
+ readManufacturerCode = undefined;
3598
3772
  });
3599
3773
 
3600
3774
  it("Send zcl command to all no options", async () => {
@@ -3625,8 +3799,11 @@ describe("Controller", () => {
3625
3799
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
3626
3800
  const device = controller.getDeviceByIeeeAddr("0x129")!;
3627
3801
  device.addCustomCluster("ssIasZone", {
3802
+ name: "ssIasZone",
3628
3803
  ID: Zcl.Clusters.ssIasZone.ID,
3629
- commands: {boschSmokeAlarmSiren: {ID: 0x80, parameters: [{name: "data", type: Zcl.DataType.UINT16, max: 0xffff}]}},
3804
+ commands: {
3805
+ boschSmokeAlarmSiren: {name: "boschSmokeAlarmSiren", ID: 0x80, parameters: [{name: "data", type: Zcl.DataType.UINT16, max: 0xffff}]},
3806
+ },
3630
3807
  commandsResponse: {},
3631
3808
  attributes: {},
3632
3809
  });
@@ -4032,13 +4209,24 @@ describe("Controller", () => {
4032
4209
  expect(device.checkinInterval).toBeUndefined();
4033
4210
  expect(device.pendingRequestTimeout).toStrictEqual(0);
4034
4211
  mocksendZclFrameToEndpoint.mockClear();
4035
- mocksendZclFrameToEndpoint.mockReturnValueOnce(null);
4212
+ mocksendZclFrameToEndpoint.mockReturnValueOnce(undefined);
4213
+ const newCheckinInterval = 204;
4036
4214
  mocksendZclFrameToEndpoint.mockImplementationOnce((_ieeeAddr, _networkAddress, _endpoint, frame: Zcl.Frame) => {
4037
- const payload = [{attrId: 0, status: 0, dataType: 35, attrData: 204}];
4215
+ const payload = [
4216
+ {
4217
+ attrId: Zcl.Clusters.genPollCtrl.attributes.checkinInterval.ID,
4218
+ status: Zcl.Status.SUCCESS,
4219
+ dataType: Zcl.DataType.UINT32,
4220
+ attrData: newCheckinInterval,
4221
+ },
4222
+ ];
4038
4223
  const responseFrame = Zcl.Frame.create(0, 1, true, undefined, 10, "readRsp", frame.cluster.ID, payload, {});
4039
4224
  return {header: responseFrame.header, data: responseFrame.toBuffer(), clusterID: frame.cluster.ID};
4040
4225
  });
4041
- mocksendZclFrameToEndpoint.mockImplementationOnce(() => vi.advanceTimersByTime(10));
4226
+ mocksendZclFrameToEndpoint.mockImplementationOnce(() => {
4227
+ vi.advanceTimersByTime(10);
4228
+ return undefined;
4229
+ });
4042
4230
  let frame = Zcl.Frame.create(
4043
4231
  Zcl.FrameType.SPECIFIC,
4044
4232
  Zcl.Direction.SERVER_TO_CLIENT,
@@ -4061,10 +4249,9 @@ describe("Controller", () => {
4061
4249
  groupID: undefined,
4062
4250
  });
4063
4251
  await flushPromises();
4064
- expect(device.checkinInterval).toStrictEqual(51);
4065
- expect(device.pendingRequestTimeout).toStrictEqual(51000);
4252
+ expect(device.checkinInterval).toStrictEqual(newCheckinInterval / 4);
4253
+ expect(device.pendingRequestTimeout).toStrictEqual((newCheckinInterval / 4) * 1000);
4066
4254
  expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(3);
4067
- device.checkinInterval = 50;
4068
4255
 
4069
4256
  mocksendZclFrameToEndpoint.mockClear();
4070
4257
  frame = Zcl.Frame.create(
@@ -4095,6 +4282,36 @@ describe("Controller", () => {
4095
4282
  expect(call[3].cluster.name).toBe("genPollCtrl");
4096
4283
  expect(call[3].command.name).toBe("checkinRsp");
4097
4284
  expect(call[3].payload).toStrictEqual({startFastPolling: 0, fastPollTimeout: 0});
4285
+
4286
+ await mockAdapterEvents.zdoResponse(Zdo.ClusterId.END_DEVICE_ANNOUNCE, [
4287
+ Zdo.Status.SUCCESS,
4288
+ {
4289
+ nwkAddress: 174,
4290
+ eui64: "0x174",
4291
+ capabilities: {
4292
+ allocateAddress: 0,
4293
+ alternatePANCoordinator: 0,
4294
+ deviceType: 0,
4295
+ powerSource: 0,
4296
+ reserved1: 0,
4297
+ reserved2: 0,
4298
+ rxOnWhenIdle: 0,
4299
+ securityCapability: 0,
4300
+ },
4301
+ },
4302
+ ]);
4303
+
4304
+ // doesn't reset on announce
4305
+ expect(device.checkinInterval).toStrictEqual(newCheckinInterval / 4);
4306
+ expect(device.pendingRequestTimeout).toStrictEqual((newCheckinInterval / 4) * 1000);
4307
+
4308
+ device.removeFromDatabase();
4309
+
4310
+ await mockAdapterEvents.deviceJoined({networkAddress: 174, ieeeAddr: "0x174"});
4311
+
4312
+ // resets on join
4313
+ expect(device.checkinInterval).toStrictEqual(DEFAULT_184_CHECKIN_INTERVAL / 4);
4314
+ expect(device.pendingRequestTimeout).toStrictEqual((DEFAULT_184_CHECKIN_INTERVAL / 4) * 1000);
4098
4315
  });
4099
4316
 
4100
4317
  it("Poll control unsupported", async () => {
@@ -4437,11 +4654,11 @@ describe("Controller", () => {
4437
4654
  mocksendZclFrameToEndpoint.mockClear();
4438
4655
 
4439
4656
  // @ts-expect-error private
4440
- endpoint._configuredReportings = [{cluster: 65281, attrId: 269, minRepIntval: 60, maxRepIntval: 900, repChange: 1}];
4657
+ endpoint._configuredReportings = [{cluster: 0xfc57, attrId: 0x0005, minRepIntval: 60, maxRepIntval: 900, repChange: 1}];
4441
4658
 
4442
- await endpoint.configureReporting("manuSpecificSinope", [
4659
+ await endpoint.configureReporting("manuSpecificAmazonWWAH", [
4443
4660
  {
4444
- attribute: "roomTemperature",
4661
+ attribute: "macRetryCount",
4445
4662
  minimumReportInterval: 1,
4446
4663
  maximumReportInterval: 10,
4447
4664
  reportableChange: 1,
@@ -4449,8 +4666,8 @@ describe("Controller", () => {
4449
4666
  ]);
4450
4667
 
4451
4668
  expect(endpoint.configuredReportings.length).toBe(1);
4452
- expect(endpoint.configuredReportings[0].attribute.name).toBe("roomTemperature");
4453
- expect(endpoint.configuredReportings[0].cluster.name).toBe("manuSpecificSinope");
4669
+ expect(endpoint.configuredReportings[0].attribute.name).toBe("macRetryCount");
4670
+ expect(endpoint.configuredReportings[0].cluster.name).toBe("manuSpecificAmazonWWAH");
4454
4671
  });
4455
4672
 
4456
4673
  it("Endpoint configure reporting for manufacturer specific attribute", async () => {
@@ -4459,9 +4676,10 @@ describe("Controller", () => {
4459
4676
  const device = controller.getDeviceByIeeeAddr("0x129")!;
4460
4677
  // @ts-expect-error private
4461
4678
  device._manufacturerID = 4641;
4679
+ device.addCustomCluster("hvacThermostat", CUSTOM_CLUSTERS.hvacThermostat);
4462
4680
  const endpoint = device.getEndpoint(1)!;
4463
4681
  mocksendZclFrameToEndpoint.mockClear();
4464
- await endpoint.configureReporting(
4682
+ await endpoint.configureReporting<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>(
4465
4683
  "hvacThermostat",
4466
4684
  [
4467
4685
  {
@@ -4478,7 +4696,7 @@ describe("Controller", () => {
4478
4696
  expect(call[0]).toBe("0x129");
4479
4697
  expect(call[1]).toBe(129);
4480
4698
  expect(call[2]).toBe(1);
4481
- expect(deepClone(call[3])).toStrictEqual(
4699
+ expect(deepClone(call[3])).toMatchObject(
4482
4700
  deepClone(
4483
4701
  Zcl.Frame.create(
4484
4702
  Zcl.FrameType.GLOBAL,
@@ -4489,12 +4707,13 @@ describe("Controller", () => {
4489
4707
  "configReport",
4490
4708
  513,
4491
4709
  [{attrId: 16384, dataType: 48, direction: 0, maxRepIntval: 10, minRepIntval: 1, repChange: 1}],
4492
- {},
4710
+ CUSTOM_CLUSTERS,
4493
4711
  ),
4494
4712
  ),
4495
4713
  );
4496
4714
 
4497
4715
  expect(endpoint.configuredReportings.length).toBe(1);
4716
+
4498
4717
  expect({...endpoint.configuredReportings[0], cluster: undefined}).toStrictEqual({
4499
4718
  attribute: expect.objectContaining({ID: 16384, type: 48, manufacturerCode: 4641, name: "viessmannWindowOpenInternal"}),
4500
4719
  minimumReportInterval: 1,
@@ -4510,9 +4729,10 @@ describe("Controller", () => {
4510
4729
  const device = controller.getDeviceByIeeeAddr("0x129")!;
4511
4730
  // @ts-expect-error private
4512
4731
  device._manufacturerID = Zcl.ManufacturerCode.VIESSMANN_ELEKTRONIK_GMBH;
4732
+ device.addCustomCluster("hvacThermostat", CUSTOM_CLUSTERS.hvacThermostat);
4513
4733
  const endpoint = device.getEndpoint(1)!;
4514
4734
  mocksendZclFrameToEndpoint.mockClear();
4515
- await endpoint.configureReporting("hvacThermostat", [
4735
+ await endpoint.configureReporting<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", [
4516
4736
  {
4517
4737
  attribute: "viessmannWindowOpenInternal",
4518
4738
  minimumReportInterval: 1,
@@ -4525,7 +4745,7 @@ describe("Controller", () => {
4525
4745
  expect(call[0]).toBe("0x129");
4526
4746
  expect(call[1]).toBe(129);
4527
4747
  expect(call[2]).toBe(1);
4528
- expect(deepClone(call[3])).toStrictEqual(
4748
+ expect(deepClone(call[3])).toMatchObject(
4529
4749
  deepClone(
4530
4750
  Zcl.Frame.create(
4531
4751
  Zcl.FrameType.GLOBAL,
@@ -4565,7 +4785,7 @@ describe("Controller", () => {
4565
4785
  expect(deepClone(endpoint.configuredReportings)).toStrictEqual([
4566
4786
  {
4567
4787
  cluster: deepClone(genPowerCfg),
4568
- attribute: genPowerCfg.getAttribute("mainsFrequency"),
4788
+ attribute: Zcl.Utils.getClusterAttribute(genPowerCfg, "mainsFrequency", undefined),
4569
4789
  minimumReportInterval: 1,
4570
4790
  maximumReportInterval: 10,
4571
4791
  reportableChange: 1,
@@ -4578,7 +4798,7 @@ describe("Controller", () => {
4578
4798
  expect(deepClone(endpoint.configuredReportings)).toStrictEqual([
4579
4799
  {
4580
4800
  cluster: deepClone(genPowerCfg),
4581
- attribute: genPowerCfg.getAttribute("mainsFrequency"),
4801
+ attribute: Zcl.Utils.getClusterAttribute(genPowerCfg, "mainsFrequency", undefined),
4582
4802
  minimumReportInterval: 3,
4583
4803
  maximumReportInterval: 100,
4584
4804
  reportableChange: 2,
@@ -4591,14 +4811,14 @@ describe("Controller", () => {
4591
4811
  expect(deepClone(endpoint.configuredReportings)).toStrictEqual([
4592
4812
  {
4593
4813
  cluster: deepClone(genPowerCfg),
4594
- attribute: genPowerCfg.getAttribute("mainsFrequency"),
4814
+ attribute: Zcl.Utils.getClusterAttribute(genPowerCfg, "mainsFrequency", undefined),
4595
4815
  minimumReportInterval: 3,
4596
4816
  maximumReportInterval: 100,
4597
4817
  reportableChange: 2,
4598
4818
  },
4599
4819
  {
4600
4820
  cluster: deepClone(msOccupancySensing),
4601
- attribute: msOccupancySensing.getAttribute("occupancy"),
4821
+ attribute: Zcl.Utils.getClusterAttribute(msOccupancySensing, "occupancy", undefined),
4602
4822
  minimumReportInterval: 3,
4603
4823
  maximumReportInterval: 100,
4604
4824
  reportableChange: 2,
@@ -4611,7 +4831,7 @@ describe("Controller", () => {
4611
4831
  expect(deepClone(endpoint.configuredReportings)).toStrictEqual([
4612
4832
  {
4613
4833
  cluster: deepClone(genPowerCfg),
4614
- attribute: genPowerCfg.getAttribute("mainsFrequency"),
4834
+ attribute: Zcl.Utils.getClusterAttribute(genPowerCfg, "mainsFrequency", undefined),
4615
4835
  minimumReportInterval: 3,
4616
4836
  maximumReportInterval: 100,
4617
4837
  reportableChange: 2,
@@ -4927,14 +5147,37 @@ describe("Controller", () => {
4927
5147
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
4928
5148
  const device = controller.getDeviceByIeeeAddr("0x129")!;
4929
5149
  const endpoint = device.getEndpoint(1)!;
4930
- const genBasic = Zcl.Utils.getCluster("genBasic", undefined, {});
4931
5150
  const saveClusterAttributeReportConfigSpy = vi.spyOn(endpoint, "saveClusterAttributeReportConfig");
4932
5151
 
4933
- endpoint.saveClusterAttributeReportConfig(genBasic.ID, Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC, [
5152
+ interface SchneiderGenBasic {
5153
+ attributes: {schneiderMeterRadioPower: number};
5154
+ commands: Record<string, never>;
5155
+ commandResponses: Record<string, never>;
5156
+ }
5157
+
5158
+ device.addCustomCluster("genBasic", {
5159
+ name: "genBasic",
5160
+ ID: 0x0000,
5161
+ attributes: {
5162
+ schneiderMeterRadioPower: {
5163
+ name: "schneiderMeterRadioPower",
5164
+ ID: 0xe200,
5165
+ type: Zcl.DataType.INT8,
5166
+ manufacturerCode: Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC,
5167
+ write: true,
5168
+ min: -128,
5169
+ max: 127,
5170
+ },
5171
+ },
5172
+ commands: {},
5173
+ commandsResponse: {},
5174
+ });
5175
+
5176
+ endpoint.saveClusterAttributeReportConfig(Zcl.Clusters.genBasic.ID, Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC, [
4934
5177
  {
4935
5178
  status: Zcl.Status.SUCCESS,
4936
5179
  direction: Zcl.Direction.CLIENT_TO_SERVER,
4937
- attrId: genBasic.attributes.schneiderMeterRadioPower.ID,
5180
+ attrId: 0xe200,
4938
5181
  dataType: Zcl.DataType.INT8,
4939
5182
  minRepIntval: 80,
4940
5183
  maxRepIntval: 300,
@@ -4949,7 +5192,7 @@ describe("Controller", () => {
4949
5192
  {
4950
5193
  status: Zcl.Status.SUCCESS,
4951
5194
  direction: Zcl.Direction.CLIENT_TO_SERVER,
4952
- attrId: genBasic.attributes.schneiderMeterRadioPower.ID,
5195
+ attrId: 0xe200,
4953
5196
  dataType: Zcl.DataType.INT8,
4954
5197
  minRepIntval: 15,
4955
5198
  maxRepIntval: 213,
@@ -4972,9 +5215,16 @@ describe("Controller", () => {
4972
5215
 
4973
5216
  expect(deepClone(endpoint.configuredReportings)).toStrictEqual([
4974
5217
  {
4975
- cluster: deepClone(genBasic),
5218
+ cluster: expect.objectContaining({
5219
+ ID: 0x0000,
5220
+ name: "genBasic",
5221
+ attributes: expect.objectContaining({
5222
+ zclVersion: expect.objectContaining({ID: 0x0000}),
5223
+ schneiderMeterRadioPower: expect.objectContaining({ID: 0xe200}),
5224
+ }),
5225
+ }),
4976
5226
  attribute: expect.objectContaining({
4977
- ID: genBasic.attributes.schneiderMeterRadioPower.ID,
5227
+ ID: 0xe200,
4978
5228
  name: "schneiderMeterRadioPower",
4979
5229
  type: Zcl.DataType.INT8,
4980
5230
  manufacturerCode: Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC,
@@ -4985,7 +5235,7 @@ describe("Controller", () => {
4985
5235
  },
4986
5236
  ]);
4987
5237
 
4988
- await endpoint.readReportingConfig("genBasic", [{attribute: "schneiderMeterRadioPower"}], {
5238
+ await endpoint.readReportingConfig<"genBasic", SchneiderGenBasic>("genBasic", [{attribute: "schneiderMeterRadioPower"}], {
4989
5239
  manufacturerCode: Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC,
4990
5240
  });
4991
5241
 
@@ -5002,9 +5252,9 @@ describe("Controller", () => {
5002
5252
  Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC,
5003
5253
  9,
5004
5254
  "readReportConfig",
5005
- genBasic.ID,
5006
- [{direction: Zcl.Direction.CLIENT_TO_SERVER, attrId: genBasic.attributes.schneiderMeterRadioPower.ID}],
5007
- {},
5255
+ Zcl.Clusters.genBasic.ID,
5256
+ [{direction: Zcl.Direction.CLIENT_TO_SERVER, attrId: 0xe200}],
5257
+ device.customClusters,
5008
5258
  ),
5009
5259
  ),
5010
5260
  );
@@ -5012,9 +5262,16 @@ describe("Controller", () => {
5012
5262
 
5013
5263
  expect(deepClone(endpoint.configuredReportings)).toStrictEqual([
5014
5264
  {
5015
- cluster: deepClone(genBasic),
5265
+ cluster: expect.objectContaining({
5266
+ ID: 0x0000,
5267
+ name: "genBasic",
5268
+ attributes: expect.objectContaining({
5269
+ zclVersion: expect.objectContaining({ID: 0x0000}),
5270
+ schneiderMeterRadioPower: expect.objectContaining({ID: 0xe200}),
5271
+ }),
5272
+ }),
5016
5273
  attribute: expect.objectContaining({
5017
- ID: genBasic.attributes.schneiderMeterRadioPower.ID,
5274
+ ID: 0xe200,
5018
5275
  name: "schneiderMeterRadioPower",
5019
5276
  type: Zcl.DataType.INT8,
5020
5277
  manufacturerCode: Zcl.ManufacturerCode.SCHNEIDER_ELECTRIC,
@@ -5203,10 +5460,11 @@ describe("Controller", () => {
5203
5460
  const endpoint = device.getEndpoint(1)!;
5204
5461
  mocksendZclFrameToEndpoint.mockClear();
5205
5462
  device.addCustomCluster("manuSpecificAssaDoorLock", {
5463
+ name: "manuSpecificAssaDoorLock",
5206
5464
  ID: 0xfc00,
5207
5465
  attributes: {},
5208
5466
  commands: {
5209
- getBatteryLevel: {ID: 0x12, parameters: []},
5467
+ getBatteryLevel: {name: "getBatteryLevel", ID: 0x12, parameters: []},
5210
5468
  },
5211
5469
  commandsResponse: {},
5212
5470
  });
@@ -5223,6 +5481,7 @@ describe("Controller", () => {
5223
5481
  const device = controller.getDeviceByIeeeAddr("0x129")!;
5224
5482
  const endpoint = device.getEndpoint(1)!;
5225
5483
  mocksendZclFrameToEndpoint.mockClear();
5484
+ device.addCustomCluster("lightingColorCtrl", CUSTOM_CLUSTERS.lightingColorCtrl);
5226
5485
  await endpoint.command("lightingColorCtrl", "tuyaMoveToHueAndSaturationBrightness", {hue: 1, saturation: 1, transtime: 0, brightness: 22});
5227
5486
  expect(mocksendZclFrameToEndpoint.mock.calls[0][0]).toBe("0x129");
5228
5487
  expect(mocksendZclFrameToEndpoint.mock.calls[0][1]).toBe(129);
@@ -5468,14 +5727,15 @@ describe("Controller", () => {
5468
5727
  const device = controller.getDeviceByIeeeAddr("0x129")!;
5469
5728
  // @ts-expect-error private
5470
5729
  device._manufacturerID = Zcl.ManufacturerCode.VIESSMANN_ELEKTRONIK_GMBH;
5730
+ device.addCustomCluster("hvacThermostat", CUSTOM_CLUSTERS.hvacThermostat);
5471
5731
  const endpoint = device.getEndpoint(1)!;
5472
- await endpoint.write("hvacThermostat", {viessmannWindowOpenInternal: 1});
5732
+ await endpoint.write<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", {viessmannWindowOpenInternal: 1});
5473
5733
  expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(1);
5474
5734
  const call = mocksendZclFrameToEndpoint.mock.calls[0];
5475
5735
  expect(call[0]).toBe("0x129");
5476
5736
  expect(call[1]).toBe(129);
5477
5737
  expect(call[2]).toBe(1);
5478
- expect(deepClone(call[3])).toStrictEqual(
5738
+ expect(deepClone(call[3])).toMatchObject(
5479
5739
  deepClone(
5480
5740
  Zcl.Frame.create(
5481
5741
  Zcl.FrameType.GLOBAL,
@@ -5486,7 +5746,7 @@ describe("Controller", () => {
5486
5746
  "write",
5487
5747
  513,
5488
5748
  [{attrId: 16384, attrData: 1, dataType: 48}],
5489
- {},
5749
+ CUSTOM_CLUSTERS,
5490
5750
  ),
5491
5751
  ),
5492
5752
  );
@@ -5746,17 +6006,29 @@ describe("Controller", () => {
5746
6006
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
5747
6007
  mocksendZclFrameToEndpoint.mockClear();
5748
6008
  const device = controller.getDeviceByIeeeAddr("0x129")!;
5749
- // @ts-expect-error private
5750
- device._manufacturerID = Zcl.ManufacturerCode.VIESSMANN_ELEKTRONIK_GMBH;
6009
+ readManufacturerCode = Zcl.ManufacturerCode.VIESSMANN_ELEKTRONIK_GMBH;
6010
+ device.addCustomCluster("hvacThermostat", CUSTOM_CLUSTERS.hvacThermostat);
5751
6011
  const endpoint = device.getEndpoint(1)!;
5752
- await endpoint.read("hvacThermostat", ["viessmannWindowOpenInternal"]);
6012
+ await endpoint.read<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", ["viessmannWindowOpenInternal"]);
5753
6013
  expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(1);
5754
6014
  const call = mocksendZclFrameToEndpoint.mock.calls[0];
5755
6015
  expect(call[0]).toBe("0x129");
5756
6016
  expect(call[1]).toBe(129);
5757
6017
  expect(call[2]).toBe(1);
5758
- expect(deepClone(call[3])).toStrictEqual(
5759
- deepClone(Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.CLIENT_TO_SERVER, true, 4641, 9, "read", 513, [{attrId: 16384}], {})),
6018
+ expect(deepClone(call[3])).toMatchObject(
6019
+ deepClone(
6020
+ Zcl.Frame.create(
6021
+ Zcl.FrameType.GLOBAL,
6022
+ Zcl.Direction.CLIENT_TO_SERVER,
6023
+ true,
6024
+ 4641,
6025
+ 9,
6026
+ "read",
6027
+ 513,
6028
+ [{attrId: 16384}],
6029
+ CUSTOM_CLUSTERS,
6030
+ ),
6031
+ ),
5760
6032
  );
5761
6033
  expect(call[4]).toBe(10000);
5762
6034
  });
@@ -5914,7 +6186,7 @@ describe("Controller", () => {
5914
6186
  expect(Array.from(group6.members)).toStrictEqual([device2.getEndpoint(1)]);
5915
6187
  expect(Array.from(group7.members)).toStrictEqual([device2.getEndpoint(1)]);
5916
6188
  expect(deepClone(call[3])).toStrictEqual(
5917
- deepClone(Zcl.Frame.create(Zcl.FrameType.SPECIFIC, Zcl.Direction.CLIENT_TO_SERVER, true, undefined, 23, "removeAll", 4, {}, {})),
6189
+ deepClone(Zcl.Frame.create(Zcl.FrameType.SPECIFIC, Zcl.Direction.CLIENT_TO_SERVER, true, undefined, 22, "removeAll", 4, {}, {})),
5918
6190
  );
5919
6191
  });
5920
6192
 
@@ -7323,10 +7595,11 @@ describe("Controller", () => {
7323
7595
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
7324
7596
  const device = controller.getDeviceByIeeeAddr("0x129")!;
7325
7597
  device.addCustomCluster("myCustomCluster", {
7598
+ name: "myCustomCluster",
7326
7599
  ID: 9123,
7327
7600
  commands: {},
7328
7601
  commandsResponse: {},
7329
- attributes: {superAttribute: {ID: 0, type: Zcl.DataType.UINT8}},
7602
+ attributes: {superAttribute: {name: "superAttribute", ID: 0, type: Zcl.DataType.UINT8}},
7330
7603
  });
7331
7604
  const buffer = Buffer.from([24, 169, 99, 0, 1, 24, 3, 0, 0, 24, 1]);
7332
7605
  const header = Zcl.Header.fromBuffer(buffer);
@@ -8346,13 +8619,6 @@ describe("Controller", () => {
8346
8619
  mocksendZclFrameToEndpoint.mockClear();
8347
8620
  mocksendZclFrameToEndpoint.mockReturnValueOnce(null);
8348
8621
 
8349
- // onZclData is called via mockAdapterEvents, but we need to wait until it has finished
8350
- const origOnZclData = device.onZclData;
8351
- device.onZclData = (a, b, c) => {
8352
- const f = origOnZclData.call(device, a, b, c);
8353
- vi.advanceTimersByTime(10);
8354
- return f;
8355
- };
8356
8622
  const nextTick = new Promise(process.nextTick);
8357
8623
 
8358
8624
  const result = endpoint.write("genOnOff", {onTime: 1}, {disableResponse: true, sendPolicy: "bulk"});
@@ -8424,6 +8690,128 @@ describe("Controller", () => {
8424
8690
  expect(fastpollstop[3].payload).toStrictEqual({});
8425
8691
  });
8426
8692
 
8693
+ it("Send to device with pendingRequestTimeout > 0, retry on timeout error", async () => {
8694
+ await controller.start();
8695
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
8696
+ const frame = Zcl.Frame.create(
8697
+ Zcl.FrameType.GLOBAL,
8698
+ Zcl.Direction.SERVER_TO_CLIENT,
8699
+ true,
8700
+ undefined,
8701
+ 1,
8702
+ "report",
8703
+ "genPowerCfg",
8704
+ [{attrId: 33, dataType: 32, attrData: 84}],
8705
+ {},
8706
+ );
8707
+ const data = {
8708
+ wasBroadcast: false,
8709
+ address: "0x129",
8710
+ clusterID: frame.cluster.ID,
8711
+ data: frame.toBuffer(),
8712
+ header: frame.header,
8713
+ endpoint: 1,
8714
+ linkquality: 50,
8715
+ groupID: 0,
8716
+ };
8717
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
8718
+ const endpoint = device.getEndpoint(1)!;
8719
+ const group = controller.createGroup(1);
8720
+ device.checkinInterval = 3_600;
8721
+
8722
+ expect(device.pendingRequestTimeout).toStrictEqual(3_600_000);
8723
+
8724
+ mocksendZclFrameToEndpoint.mockClear();
8725
+ mocksendZclFrameToEndpoint.mockImplementationOnce(async () => {
8726
+ await vi.advanceTimersByTimeAsync(10_000);
8727
+ throw new Error("timeout");
8728
+ });
8729
+
8730
+ const p = endpoint.addToGroup(group);
8731
+
8732
+ expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(1);
8733
+
8734
+ await vi.advanceTimersByTimeAsync(1000);
8735
+ await mockAdapterEvents.zclPayload(data);
8736
+
8737
+ await expect(p).resolves.toStrictEqual(undefined);
8738
+
8739
+ expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(2);
8740
+ });
8741
+
8742
+ it("Send to device with pendingRequestTimeout > 0, fails on non-timeout error", async () => {
8743
+ await controller.start();
8744
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
8745
+ const frame = Zcl.Frame.create(
8746
+ Zcl.FrameType.GLOBAL,
8747
+ Zcl.Direction.SERVER_TO_CLIENT,
8748
+ true,
8749
+ undefined,
8750
+ 1,
8751
+ "report",
8752
+ "genPowerCfg",
8753
+ [{attrId: 33, dataType: 32, attrData: 84}],
8754
+ {},
8755
+ );
8756
+ const data = {
8757
+ wasBroadcast: false,
8758
+ address: "0x129",
8759
+ clusterID: frame.cluster.ID,
8760
+ data: frame.toBuffer(),
8761
+ header: frame.header,
8762
+ endpoint: 1,
8763
+ linkquality: 50,
8764
+ groupID: 0,
8765
+ };
8766
+ const failureFrame = Zcl.Frame.create(
8767
+ Zcl.FrameType.GLOBAL,
8768
+ Zcl.Direction.SERVER_TO_CLIENT,
8769
+ true,
8770
+ undefined,
8771
+ 2,
8772
+ "defaultRsp",
8773
+ "genGroups",
8774
+ {cmdId: 0, statusCode: Zcl.Status.UNSUPPORTED_CLUSTER},
8775
+ {},
8776
+ );
8777
+ const failureData = {
8778
+ wasBroadcast: false,
8779
+ address: "0x129",
8780
+ clusterID: failureFrame.cluster.ID,
8781
+ data: failureFrame.toBuffer(),
8782
+ header: failureFrame.header,
8783
+ endpoint: 1,
8784
+ linkquality: 50,
8785
+ groupID: 0,
8786
+ };
8787
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
8788
+ const endpoint = device.getEndpoint(1)!;
8789
+ const group = controller.createGroup(1);
8790
+ device.checkinInterval = 3_600;
8791
+ expect(device.pendingRequestTimeout).toStrictEqual(3_600_000);
8792
+
8793
+ mocksendZclFrameToEndpoint.mockClear();
8794
+ mocksendZclFrameToEndpoint.mockImplementationOnce(async () => {
8795
+ await vi.advanceTimersByTimeAsync(150);
8796
+ await mockAdapterEvents.zclPayload(failureData);
8797
+
8798
+ return failureData;
8799
+ });
8800
+
8801
+ const p = endpoint.addToGroup(group);
8802
+
8803
+ expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(1);
8804
+
8805
+ await expect(p).rejects.toThrow(/UNSUPPORTED_CLUSTER/);
8806
+
8807
+ expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(1);
8808
+
8809
+ await vi.advanceTimersByTimeAsync(1000);
8810
+ await mockAdapterEvents.zclPayload(data);
8811
+
8812
+ expect(mocksendZclFrameToEndpoint).toHaveBeenCalledTimes(1);
8813
+ });
8814
+
8427
8815
  it("Handle retransmitted Xiaomi messages", async () => {
8428
8816
  await controller.start();
8429
8817
  await mockAdapterEvents.deviceJoined({networkAddress: 175, ieeeAddr: "0x175"});
@@ -8479,111 +8867,274 @@ describe("Controller", () => {
8479
8867
  expect(result.missingRouters[0].ieeeAddr).toBe("0x129");
8480
8868
  });
8481
8869
 
8482
- // ZCLFrame with manufacturer specific flag and manufacturer code defined, to generic device
8483
- // ZCLFrameConverter should not modify specific frames!
8484
- it("Should resolve manufacturer specific cluster attribute names on specific ZCL frames: generic target device", async () => {
8485
- const buffer = Buffer.from([28, 33, 16, 13, 1, 2, 240, 0, 48, 4]);
8486
- await controller.start();
8487
- await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
8870
+ describe("ZCL frame converter attributeKeyValue", () => {
8871
+ // ZCLFrame with manufacturer specific flag and manufacturer code defined, to generic device
8872
+ // ZCLFrameConverter should not modify specific frames!
8873
+ it("Should resolve manufacturer specific cluster attribute names on specific ZCL frames: generic target device", async () => {
8874
+ const buffer = Buffer.from([28, 33, 16, 13, 1, 2, 240, 0, 48, 4]);
8875
+ await controller.start();
8876
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
8877
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
8878
+ device.addCustomCluster("closuresWindowCovering", CUSTOM_CLUSTERS.closuresWindowCovering);
8488
8879
 
8489
- const frame = Zcl.Frame.fromBuffer(
8490
- Zcl.Utils.getCluster("closuresWindowCovering", undefined, {}).ID,
8491
- Zcl.Header.fromBuffer(buffer),
8492
- buffer,
8493
- {},
8494
- );
8495
- await mockAdapterEvents.zclPayload({
8496
- wasBroadcast: false,
8497
- address: "0x129",
8498
- clusterID: frame.cluster.ID,
8499
- data: frame.toBuffer(),
8500
- header: frame.header,
8501
- endpoint: 1,
8502
- linkquality: 50,
8503
- groupID: 0,
8880
+ const frame = Zcl.Frame.fromBuffer(
8881
+ Zcl.Utils.getCluster("closuresWindowCovering", undefined, CUSTOM_CLUSTERS).ID,
8882
+ Zcl.Header.fromBuffer(buffer),
8883
+ buffer,
8884
+ CUSTOM_CLUSTERS,
8885
+ );
8886
+ await mockAdapterEvents.zclPayload({
8887
+ wasBroadcast: false,
8888
+ address: "0x129",
8889
+ clusterID: frame.cluster.ID,
8890
+ data: frame.toBuffer(),
8891
+ header: frame.header,
8892
+ endpoint: 1,
8893
+ linkquality: 50,
8894
+ groupID: 0,
8895
+ });
8896
+ expect(events.message.length).toBe(1);
8897
+ expect(events.message[0].data).toMatchObject({calibrationMode: 4});
8898
+ expect(events.message[0].data).not.toMatchObject({tuyaMotorReversal: 4});
8504
8899
  });
8505
- expect(events.message.length).toBe(1);
8506
- expect(events.message[0].data).toMatchObject({calibrationMode: 4});
8507
- expect(events.message[0].data).not.toMatchObject({tuyaMotorReversal: 4});
8508
- });
8509
8900
 
8510
- // ZCLFrame with manufacturer specific flag and manufacturer code defined, to specific device
8511
- // ZCLFrameConverter should not modify specific frames!
8512
- it("Should resolve manufacturer specific cluster attribute names on specific ZCL frames: specific target device", async () => {
8513
- const buffer = Buffer.from([28, 33, 16, 13, 1, 2, 240, 0, 48, 4]);
8514
- await controller.start();
8515
- await mockAdapterEvents.deviceJoined({networkAddress: 177, ieeeAddr: "0x177"});
8516
- const frame = Zcl.Frame.fromBuffer(
8517
- Zcl.Utils.getCluster("closuresWindowCovering", undefined, {}).ID,
8518
- Zcl.Header.fromBuffer(buffer),
8519
- buffer,
8520
- {},
8521
- );
8522
- await mockAdapterEvents.zclPayload({
8523
- wasBroadcast: false,
8524
- address: "0x177",
8525
- clusterID: frame.cluster.ID,
8526
- data: frame.toBuffer(),
8527
- header: frame.header,
8528
- endpoint: 1,
8529
- linkquality: 50,
8530
- groupID: 0,
8901
+ // ZCLFrame with manufacturer specific flag and manufacturer code defined, to specific device
8902
+ // ZCLFrameConverter should not modify specific frames!
8903
+ it("Should resolve manufacturer specific cluster attribute names on specific ZCL frames: specific target device", async () => {
8904
+ const buffer = Buffer.from([28, 33, 16, 13, 1, 2, 240, 0, 48, 4]);
8905
+ await controller.start();
8906
+ await mockAdapterEvents.deviceJoined({networkAddress: 177, ieeeAddr: "0x177"});
8907
+ const device = controller.getDeviceByIeeeAddr("0x177")!;
8908
+ device.addCustomCluster("closuresWindowCovering", CUSTOM_CLUSTERS.closuresWindowCovering);
8909
+
8910
+ const frame = Zcl.Frame.fromBuffer(
8911
+ Zcl.Utils.getCluster("closuresWindowCovering", undefined, CUSTOM_CLUSTERS).ID,
8912
+ Zcl.Header.fromBuffer(buffer),
8913
+ buffer,
8914
+ CUSTOM_CLUSTERS,
8915
+ );
8916
+ await mockAdapterEvents.zclPayload({
8917
+ wasBroadcast: false,
8918
+ address: "0x177",
8919
+ clusterID: frame.cluster.ID,
8920
+ data: frame.toBuffer(),
8921
+ header: frame.header,
8922
+ endpoint: 1,
8923
+ linkquality: 50,
8924
+ groupID: 0,
8925
+ });
8926
+ expect(events.message.length).toBe(1);
8927
+ expect(events.message[0].data).toMatchObject({calibrationMode: 4});
8928
+ expect(events.message[0].data).not.toMatchObject({tuyaMotorReversal: 4});
8531
8929
  });
8532
- expect(events.message.length).toBe(1);
8533
- expect(events.message[0].data).toMatchObject({calibrationMode: 4});
8534
- expect(events.message[0].data).not.toMatchObject({tuyaMotorReversal: 4});
8535
- });
8536
8930
 
8537
- // ZCLFrame without manufacturer specific flag or manufacturer code set, to generic device
8538
- it("Should resolve generic cluster attribute names on generic ZCL frames: generic target device", async () => {
8539
- const buffer = Buffer.from([24, 242, 10, 2, 240, 48, 4]);
8540
- await controller.start();
8541
- await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
8542
- const frame = Zcl.Frame.fromBuffer(
8543
- Zcl.Utils.getCluster("closuresWindowCovering", undefined, {}).ID,
8544
- Zcl.Header.fromBuffer(buffer),
8545
- buffer,
8546
- {},
8547
- );
8548
- await mockAdapterEvents.zclPayload({
8549
- wasBroadcast: false,
8550
- address: "0x129",
8551
- clusterID: frame.cluster.ID,
8552
- data: frame.toBuffer(),
8553
- header: frame.header,
8554
- endpoint: 1,
8555
- linkquality: 50,
8556
- groupID: 0,
8931
+ // ZCLFrame without manufacturer specific flag or manufacturer code set, to generic device
8932
+ it("Should resolve generic cluster attribute names on generic ZCL frames: generic target device", async () => {
8933
+ const buffer = Buffer.from([24, 242, 10, 2, 240, 48, 4]);
8934
+ await controller.start();
8935
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
8936
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
8937
+ device.addCustomCluster("closuresWindowCovering", CUSTOM_CLUSTERS.closuresWindowCovering);
8938
+
8939
+ const frame = Zcl.Frame.fromBuffer(
8940
+ Zcl.Utils.getCluster("closuresWindowCovering", undefined, CUSTOM_CLUSTERS).ID,
8941
+ Zcl.Header.fromBuffer(buffer),
8942
+ buffer,
8943
+ CUSTOM_CLUSTERS,
8944
+ );
8945
+ await mockAdapterEvents.zclPayload({
8946
+ wasBroadcast: false,
8947
+ address: "0x129",
8948
+ clusterID: frame.cluster.ID,
8949
+ data: frame.toBuffer(),
8950
+ header: frame.header,
8951
+ endpoint: 1,
8952
+ linkquality: 50,
8953
+ groupID: 0,
8954
+ });
8955
+ expect(events.message.length).toBe(1);
8956
+ expect(events.message[0].data).toMatchObject({tuyaMotorReversal: 4});
8957
+ expect(events.message[0].data).not.toMatchObject({calibrationMode: 4});
8958
+ });
8959
+
8960
+ // ZCLFrame without manufacturer specific flag set or manufacturer code set, to specific device (Legrand only)
8961
+ it("Should resolve manufacturer specific cluster attribute names on generic ZCL frames: Legrand target device", async () => {
8962
+ const buffer = Buffer.from([24, 242, 10, 2, 240, 48, 4]);
8963
+ await controller.start();
8964
+ await mockAdapterEvents.deviceJoined({networkAddress: 177, ieeeAddr: "0x177"});
8965
+ const device = controller.getDeviceByIeeeAddr("0x177")!;
8966
+ device.addCustomCluster("closuresWindowCovering", CUSTOM_CLUSTERS.closuresWindowCovering);
8967
+
8968
+ const frame = Zcl.Frame.fromBuffer(
8969
+ Zcl.Utils.getCluster("closuresWindowCovering", undefined, CUSTOM_CLUSTERS).ID,
8970
+ Zcl.Header.fromBuffer(buffer),
8971
+ buffer,
8972
+ CUSTOM_CLUSTERS,
8973
+ );
8974
+ await mockAdapterEvents.zclPayload({
8975
+ wasBroadcast: false,
8976
+ address: "0x177",
8977
+ clusterID: frame.cluster.ID,
8978
+ data: frame.toBuffer(),
8979
+ header: frame.header,
8980
+ endpoint: 1,
8981
+ linkquality: 50,
8982
+ groupID: 0,
8983
+ });
8984
+ expect(events.message.length).toBe(1);
8985
+ expect(events.message[0].data).toMatchObject({calibrationMode: 4});
8986
+ expect(events.message[0].data).not.toMatchObject({tuyaMotorReversal: 4});
8987
+ });
8988
+
8989
+ it("Should drop non-success records from readRsp payloads", async () => {
8990
+ await controller.start();
8991
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
8992
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
8993
+ device.updateGenBasic({swBuildId: "2.16"});
8994
+
8995
+ const frame = Zcl.Frame.create(
8996
+ Zcl.FrameType.GLOBAL,
8997
+ Zcl.Direction.SERVER_TO_CLIENT,
8998
+ true,
8999
+ undefined,
9000
+ 10,
9001
+ "readRsp",
9002
+ Zcl.Clusters.genBasic.ID,
9003
+ [
9004
+ {attrId: 0x0005, status: Zcl.Status.SUCCESS, dataType: Zcl.DataType.CHAR_STR, attrData: "myModelID"},
9005
+ {attrId: 0x4000, status: Zcl.Status.UNSUPPORTED_ATTRIBUTE},
9006
+ ],
9007
+ {},
9008
+ );
9009
+ await mockAdapterEvents.zclPayload({
9010
+ wasBroadcast: false,
9011
+ address: "0x129",
9012
+ clusterID: frame.cluster.ID,
9013
+ data: frame.toBuffer(),
9014
+ header: frame.header,
9015
+ endpoint: 1,
9016
+ linkquality: 50,
9017
+ groupID: 0,
9018
+ });
9019
+
9020
+ expect(events.message.length).toBe(1);
9021
+ expect(events.message[0].data).toStrictEqual({modelId: "myModelID"});
9022
+ expect(device.softwareBuildID).toStrictEqual("2.16");
9023
+ expect(device.modelID).toStrictEqual("myModelID");
8557
9024
  });
8558
- expect(events.message.length).toBe(1);
8559
- expect(events.message[0].data).toMatchObject({tuyaMotorReversal: 4});
8560
- expect(events.message[0].data).not.toMatchObject({calibrationMode: 4});
8561
9025
  });
8562
9026
 
8563
- // ZCLFrame without manufacturer specific flag set or manufacturer code set, to specific device (Legrand only)
8564
- it("Should resolve manufacturer specific cluster attribute names on generic ZCL frames: Legrand target device", async () => {
8565
- const buffer = Buffer.from([24, 242, 10, 2, 240, 48, 4]);
8566
- await controller.start();
8567
- await mockAdapterEvents.deviceJoined({networkAddress: 177, ieeeAddr: "0x177"});
8568
- const frame = Zcl.Frame.fromBuffer(
8569
- Zcl.Utils.getCluster("closuresWindowCovering", undefined, {}).ID,
8570
- Zcl.Header.fromBuffer(buffer),
8571
- buffer,
8572
- {},
8573
- );
8574
- await mockAdapterEvents.zclPayload({
8575
- wasBroadcast: false,
8576
- address: "0x177",
8577
- clusterID: frame.cluster.ID,
8578
- data: frame.toBuffer(),
8579
- header: frame.header,
8580
- endpoint: 1,
8581
- linkquality: 50,
8582
- groupID: 0,
9027
+ describe("ZCL frame converter attributeList", () => {
9028
+ // ZCLFrame with manufacturer specific flag and manufacturer code defined, to generic device
9029
+ // ZCLFrameConverter should not modify specific frames!
9030
+ it("Should resolve manufacturer specific cluster attribute names on specific ZCL frames: generic target device", async () => {
9031
+ const buffer = Buffer.from([28, 33, 16, 13, 0, 2, 240]);
9032
+ await controller.start();
9033
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
9034
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
9035
+ device.addCustomCluster("closuresWindowCovering", CUSTOM_CLUSTERS.closuresWindowCovering);
9036
+
9037
+ const frame = Zcl.Frame.fromBuffer(
9038
+ Zcl.Utils.getCluster("closuresWindowCovering", undefined, CUSTOM_CLUSTERS).ID,
9039
+ Zcl.Header.fromBuffer(buffer),
9040
+ buffer,
9041
+ {},
9042
+ );
9043
+ await mockAdapterEvents.zclPayload({
9044
+ wasBroadcast: false,
9045
+ address: "0x129",
9046
+ clusterID: frame.cluster.ID,
9047
+ data: frame.toBuffer(),
9048
+ header: frame.header,
9049
+ endpoint: 1,
9050
+ linkquality: 50,
9051
+ groupID: 0,
9052
+ });
9053
+ expect(events.message.length).toBe(1);
9054
+ expect(events.message[0].data).toStrictEqual(["calibrationMode"]);
9055
+ });
9056
+
9057
+ // ZCLFrame with manufacturer specific flag and manufacturer code defined, to specific device
9058
+ // ZCLFrameConverter should not modify specific frames!
9059
+ it("Should resolve manufacturer specific cluster attribute names on specific ZCL frames: specific target device", async () => {
9060
+ const buffer = Buffer.from([28, 33, 16, 13, 0, 2, 240]);
9061
+ await controller.start();
9062
+ await mockAdapterEvents.deviceJoined({networkAddress: 177, ieeeAddr: "0x177"});
9063
+ const device = controller.getDeviceByIeeeAddr("0x177")!;
9064
+ device.addCustomCluster("closuresWindowCovering", CUSTOM_CLUSTERS.closuresWindowCovering);
9065
+ const frame = Zcl.Frame.fromBuffer(
9066
+ Zcl.Utils.getCluster("closuresWindowCovering", undefined, CUSTOM_CLUSTERS).ID,
9067
+ Zcl.Header.fromBuffer(buffer),
9068
+ buffer,
9069
+ {},
9070
+ );
9071
+ await mockAdapterEvents.zclPayload({
9072
+ wasBroadcast: false,
9073
+ address: "0x177",
9074
+ clusterID: frame.cluster.ID,
9075
+ data: frame.toBuffer(),
9076
+ header: frame.header,
9077
+ endpoint: 1,
9078
+ linkquality: 50,
9079
+ groupID: 0,
9080
+ });
9081
+ expect(events.message.length).toBe(1);
9082
+ expect(events.message[0].data).toStrictEqual(["calibrationMode"]);
9083
+ });
9084
+
9085
+ // ZCLFrame without manufacturer specific flag or manufacturer code set, to generic device
9086
+ it("Should resolve generic cluster attribute names on generic ZCL frames: generic target device", async () => {
9087
+ const buffer = Buffer.from([24, 242, 0, 2, 240]);
9088
+ await controller.start();
9089
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
9090
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
9091
+ device.addCustomCluster("closuresWindowCovering", CUSTOM_CLUSTERS.closuresWindowCovering);
9092
+ const frame = Zcl.Frame.fromBuffer(
9093
+ Zcl.Utils.getCluster("closuresWindowCovering", undefined, CUSTOM_CLUSTERS).ID,
9094
+ Zcl.Header.fromBuffer(buffer),
9095
+ buffer,
9096
+ {},
9097
+ );
9098
+ await mockAdapterEvents.zclPayload({
9099
+ wasBroadcast: false,
9100
+ address: "0x129",
9101
+ clusterID: frame.cluster.ID,
9102
+ data: frame.toBuffer(),
9103
+ header: frame.header,
9104
+ endpoint: 1,
9105
+ linkquality: 50,
9106
+ groupID: 0,
9107
+ });
9108
+ expect(events.message.length).toBe(1);
9109
+ expect(events.message[0].data).toStrictEqual(["tuyaMotorReversal"]);
9110
+ });
9111
+
9112
+ // ZCLFrame without manufacturer specific flag set or manufacturer code set, to specific device (Legrand only)
9113
+ it("Should resolve manufacturer specific cluster attribute names on generic ZCL frames: Legrand target device", async () => {
9114
+ const buffer = Buffer.from([24, 242, 0, 2, 240]);
9115
+ await controller.start();
9116
+ await mockAdapterEvents.deviceJoined({networkAddress: 177, ieeeAddr: "0x177"});
9117
+ const device = controller.getDeviceByIeeeAddr("0x177")!;
9118
+ device.addCustomCluster("closuresWindowCovering", CUSTOM_CLUSTERS.closuresWindowCovering);
9119
+ const frame = Zcl.Frame.fromBuffer(
9120
+ Zcl.Utils.getCluster("closuresWindowCovering", undefined, CUSTOM_CLUSTERS).ID,
9121
+ Zcl.Header.fromBuffer(buffer),
9122
+ buffer,
9123
+ {},
9124
+ );
9125
+ await mockAdapterEvents.zclPayload({
9126
+ wasBroadcast: false,
9127
+ address: "0x177",
9128
+ clusterID: frame.cluster.ID,
9129
+ data: frame.toBuffer(),
9130
+ header: frame.header,
9131
+ endpoint: 1,
9132
+ linkquality: 50,
9133
+ groupID: 0,
9134
+ });
9135
+ expect(events.message.length).toBe(1);
9136
+ expect(events.message[0].data).toStrictEqual(["calibrationMode"]);
8583
9137
  });
8584
- expect(events.message.length).toBe(1);
8585
- expect(events.message[0].data).toMatchObject({calibrationMode: 4});
8586
- expect(events.message[0].data).not.toMatchObject({tuyaMotorReversal: 4});
8587
9138
  });
8588
9139
 
8589
9140
  it("zclCommand", async () => {
@@ -9347,10 +9898,11 @@ describe("Controller", () => {
9347
9898
  const device = controller.getDeviceByIeeeAddr("0x177")!;
9348
9899
 
9349
9900
  device.addCustomCluster("manuHerdsman", {
9901
+ name: "manuHerdsman",
9350
9902
  ID: 64513,
9351
9903
  commands: {},
9352
9904
  commandsResponse: {},
9353
- attributes: {customAttr: {ID: 0, type: Zcl.DataType.UINT8, write: true}},
9905
+ attributes: {customAttr: {name: "customAttr", ID: 0, type: Zcl.DataType.UINT8, write: true}},
9354
9906
  });
9355
9907
 
9356
9908
  const group = controller.createGroup(34);
@@ -9402,20 +9954,21 @@ describe("Controller", () => {
9402
9954
 
9403
9955
  const device2 = controller.getDeviceByIeeeAddr("0x178")!;
9404
9956
  device2.addCustomCluster("manuHerdsman", {
9957
+ name: "manuHerdsman",
9405
9958
  ID: 64513,
9406
9959
  commands: {},
9407
9960
  commandsResponse: {},
9408
- attributes: {customAttr: {ID: 0, type: Zcl.DataType.UINT8}},
9961
+ attributes: {customAttr: {name: "customAttr", ID: 0, type: Zcl.DataType.UINT8}},
9409
9962
  });
9410
9963
  group.addMember(device2.getEndpoint(2)!);
9411
9964
 
9412
9965
  await expect(async () => {
9413
9966
  await group.write("manuHerdsman", {customAttr: 14}, {});
9414
- }).rejects.toThrow(new Error(`Cluster with name 'manuHerdsman' does not exist`));
9967
+ }).rejects.toThrow(new Zcl.StatusError(Zcl.Status.UNSUPPORTED_CLUSTER, "manuHerdsman"));
9415
9968
 
9416
9969
  await expect(async () => {
9417
9970
  await group.read("manuHerdsman", ["customAttr"], {});
9418
- }).rejects.toThrow(new Error(`Cluster with name 'manuHerdsman' does not exist`));
9971
+ }).rejects.toThrow(new Zcl.StatusError(Zcl.Status.UNSUPPORTED_CLUSTER, "manuHerdsman"));
9419
9972
 
9420
9973
  await group.write<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", {customAttr: 14}, {direction: Zcl.Direction.SERVER_TO_CLIENT});
9421
9974
  await group.read<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", ["customAttr"], {direction: Zcl.Direction.SERVER_TO_CLIENT});
@@ -9441,20 +9994,21 @@ describe("Controller", () => {
9441
9994
 
9442
9995
  const device3 = controller.getDeviceByIeeeAddr("0x176")!;
9443
9996
  device3.addCustomCluster("manuHerdsman", {
9997
+ name: "manuHerdsman",
9444
9998
  ID: 64513,
9445
9999
  commands: {},
9446
10000
  commandsResponse: {},
9447
- attributes: {customAttr: {ID: 0, type: Zcl.DataType.UINT8}},
10001
+ attributes: {customAttr: {name: "customAttr", ID: 0, type: Zcl.DataType.UINT8}},
9448
10002
  });
9449
10003
  group.addMember(device3.getEndpoint(1)!);
9450
10004
 
9451
10005
  await expect(async () => {
9452
10006
  await group.write("manuHerdsman", {customAttr: 56}, {});
9453
- }).rejects.toThrow(new Error(`Cluster with name 'manuHerdsman' does not exist`));
10007
+ }).rejects.toThrow(new Zcl.StatusError(Zcl.Status.UNSUPPORTED_CLUSTER, "manuHerdsman"));
9454
10008
 
9455
10009
  await expect(async () => {
9456
10010
  await group.read("manuHerdsman", ["customAttr"], {});
9457
- }).rejects.toThrow(new Error(`Cluster with name 'manuHerdsman' does not exist`));
10011
+ }).rejects.toThrow(new Zcl.StatusError(Zcl.Status.UNSUPPORTED_CLUSTER, "manuHerdsman"));
9458
10012
  });
9459
10013
 
9460
10014
  it("does not read/write to group with non-common custom clusters", async () => {
@@ -9464,10 +10018,11 @@ describe("Controller", () => {
9464
10018
  const device = controller.getDeviceByIeeeAddr("0x177")!;
9465
10019
 
9466
10020
  device.addCustomCluster("manuHerdsman", {
10021
+ name: "manuHerdsman",
9467
10022
  ID: 64513,
9468
10023
  commands: {},
9469
10024
  commandsResponse: {},
9470
- attributes: {customAttr: {ID: 0, type: Zcl.DataType.UINT8}},
10025
+ attributes: {customAttr: {name: "customAttr", ID: 0, type: Zcl.DataType.UINT8}},
9471
10026
  });
9472
10027
 
9473
10028
  const group = controller.createGroup(34);
@@ -9481,11 +10036,11 @@ describe("Controller", () => {
9481
10036
 
9482
10037
  await expect(async () => {
9483
10038
  await group.write("manuHerdsman", {customAttr: 34}, {});
9484
- }).rejects.toThrow(new Error(`Cluster with name 'manuHerdsman' does not exist`));
10039
+ }).rejects.toThrow(new Zcl.StatusError(Zcl.Status.UNSUPPORTED_CLUSTER, "manuHerdsman"));
9485
10040
 
9486
10041
  await expect(async () => {
9487
10042
  await group.read("manuHerdsman", ["customAttr"], {});
9488
- }).rejects.toThrow(new Error(`Cluster with name 'manuHerdsman' does not exist`));
10043
+ }).rejects.toThrow(new Zcl.StatusError(Zcl.Status.UNSUPPORTED_CLUSTER, "manuHerdsman"));
9489
10044
  });
9490
10045
 
9491
10046
  it("sends & receives command to group with custom cluster when common to all members", async () => {
@@ -9495,12 +10050,14 @@ describe("Controller", () => {
9495
10050
  const device = controller.getDeviceByIeeeAddr("0x179")!;
9496
10051
 
9497
10052
  device.addCustomCluster("manuSpecificInovelli", {
10053
+ name: "manuSpecificInovelli",
9498
10054
  ID: 64561,
9499
10055
  manufacturerCode: Zcl.ManufacturerCode.V_MARK_ENTERPRISES_INC,
9500
10056
  // omitted for brevity (unused here)
9501
10057
  attributes: {},
9502
10058
  commands: {
9503
10059
  ledEffect: {
10060
+ name: "ledEffect",
9504
10061
  ID: 1,
9505
10062
  parameters: [
9506
10063
  {name: "effect", type: Zcl.DataType.UINT8},
@@ -9510,6 +10067,7 @@ describe("Controller", () => {
9510
10067
  ],
9511
10068
  },
9512
10069
  individualLedEffect: {
10070
+ name: "individualLedEffect",
9513
10071
  ID: 3,
9514
10072
  parameters: [
9515
10073
  {name: "led", type: Zcl.DataType.UINT8},
@@ -9521,10 +10079,7 @@ describe("Controller", () => {
9521
10079
  },
9522
10080
  },
9523
10081
  commandsResponse: {
9524
- bogus: {
9525
- ID: 1,
9526
- parameters: [{name: "xyz", type: Zcl.DataType.UINT8}],
9527
- },
10082
+ bogus: {name: "bogus", ID: 1, parameters: [{name: "xyz", type: Zcl.DataType.UINT8}]},
9528
10083
  },
9529
10084
  });
9530
10085
 
@@ -9617,7 +10172,7 @@ describe("Controller", () => {
9617
10172
  level: 200,
9618
10173
  duration: 15,
9619
10174
  });
9620
- }).rejects.toThrow(new Error(`Cluster with name 'manuSpecificInovelli' does not exist`));
10175
+ }).rejects.toThrow(new Zcl.StatusError(Zcl.Status.UNSUPPORTED_CLUSTER, "manuSpecificInovelli"));
9621
10176
  });
9622
10177
 
9623
10178
  it("Updates a device genBasic properties", async () => {
@@ -9863,10 +10418,12 @@ describe("Controller", () => {
9863
10418
 
9864
10419
  const myCustomClusters: CustomClusters = {
9865
10420
  zhSpe: {
10421
+ name: "zhSpe",
9866
10422
  ID: 0xffc1,
9867
10423
  attributes: {},
9868
10424
  commands: {
9869
10425
  readMe: {
10426
+ name: "readMe",
9870
10427
  ID: 0,
9871
10428
  parameters: [{name: "size", type: Zcl.DataType.UINT8}],
9872
10429
  },
@@ -9949,7 +10506,7 @@ describe("Controller", () => {
9949
10506
 
9950
10507
  // coverage: prevent crash in onZclPayload when ZCL metadata validation fails
9951
10508
  expect(mockLogger.debug).toHaveBeenCalledWith(
9952
- "Ignoring attribute modelId from response: Error: modelId requires max length of 32",
10509
+ "Ignoring attribute modelId from response: Error: Status 'INVALID_VALUE' modelId requires max length of 32",
9953
10510
  "zh:controller:zcl",
9954
10511
  );
9955
10512
  expect(device.genBasic.modelId).toStrictEqual(" other multi-endpoint device");
@@ -9959,6 +10516,7 @@ describe("Controller", () => {
9959
10516
  await controller.start();
9960
10517
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
9961
10518
  const device = controller.getDeviceByIeeeAddr("0x129")!;
10519
+ device.addCustomCluster("hvacThermostat", CUSTOM_CLUSTERS.hvacThermostat);
9962
10520
  const endpoint = device.getEndpoint(1)!;
9963
10521
  const zclCommandSpy = vi.spyOn(endpoint, "zclCommand");
9964
10522
 
@@ -9985,24 +10543,24 @@ describe("Controller", () => {
9985
10543
  await expect(endpoint.write("hvacThermostat", writePayloadRaw)).rejects.toThrow(
9986
10544
  "Cannot have attributes with different manufacturerCode in single 'write' call",
9987
10545
  );
9988
- await expect(endpoint.read("hvacThermostat", readPayload)).rejects.toThrow(
10546
+ await expect(endpoint.read<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", readPayload)).rejects.toThrow(
9989
10547
  "Cannot have attributes with different manufacturerCode in single 'read' call",
9990
10548
  );
9991
- await expect(endpoint.read("hvacThermostat", readPayloadRaw)).rejects.toThrow(
10549
+ await expect(endpoint.read<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", readPayloadRaw)).rejects.toThrow(
9992
10550
  "Cannot have attributes with different manufacturerCode in single 'read' call",
9993
10551
  );
9994
- await expect(endpoint.configureReporting("hvacThermostat", configureReportingPayload)).rejects.toThrow(
9995
- "Cannot have attributes with different manufacturerCode in single 'configureReporting' call",
9996
- );
9997
- await expect(endpoint.configureReporting("hvacThermostat", configureReportingPayloadRaw)).rejects.toThrow(
9998
- "Cannot have attributes with different manufacturerCode in single 'configureReporting' call",
9999
- );
10000
- await expect(endpoint.readReportingConfig("hvacThermostat", readReportingConfigPayload)).rejects.toThrow(
10001
- "Cannot have attributes with different manufacturerCode in single 'readReportingConfig' call",
10002
- );
10003
- await expect(endpoint.readReportingConfig("hvacThermostat", readReportingConfigPayloadRaw)).rejects.toThrow(
10004
- "Cannot have attributes with different manufacturerCode in single 'readReportingConfig' call",
10005
- );
10552
+ await expect(
10553
+ endpoint.configureReporting<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", configureReportingPayload),
10554
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'configureReporting' call");
10555
+ await expect(
10556
+ endpoint.configureReporting<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", configureReportingPayloadRaw),
10557
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'configureReporting' call");
10558
+ await expect(
10559
+ endpoint.readReportingConfig<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", readReportingConfigPayload),
10560
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'readReportingConfig' call");
10561
+ await expect(
10562
+ endpoint.readReportingConfig<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", readReportingConfigPayloadRaw),
10563
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'readReportingConfig' call");
10006
10564
 
10007
10565
  //-- with override manufacturer code
10008
10566
 
@@ -10014,24 +10572,40 @@ describe("Controller", () => {
10014
10572
  await expect(endpoint.write("hvacThermostat", writePayloadRaw, manufOpts)).rejects.toThrow(
10015
10573
  "Cannot have attributes with different manufacturerCode in single 'write' call",
10016
10574
  );
10017
- await expect(endpoint.read("hvacThermostat", readPayload, manufOpts)).rejects.toThrow(
10018
- "Cannot have attributes with different manufacturerCode in single 'read' call",
10019
- );
10020
- await expect(endpoint.read("hvacThermostat", readPayloadRaw, manufOpts)).rejects.toThrow(
10021
- "Cannot have attributes with different manufacturerCode in single 'read' call",
10022
- );
10023
- await expect(endpoint.configureReporting("hvacThermostat", configureReportingPayload, manufOpts)).rejects.toThrow(
10024
- "Cannot have attributes with different manufacturerCode in single 'configureReporting' call",
10025
- );
10026
- await expect(endpoint.configureReporting("hvacThermostat", configureReportingPayloadRaw, manufOpts)).rejects.toThrow(
10027
- "Cannot have attributes with different manufacturerCode in single 'configureReporting' call",
10028
- );
10029
- await expect(endpoint.readReportingConfig("hvacThermostat", readReportingConfigPayload, manufOpts)).rejects.toThrow(
10030
- "Cannot have attributes with different manufacturerCode in single 'readReportingConfig' call",
10031
- );
10032
- await expect(endpoint.readReportingConfig("hvacThermostat", readReportingConfigPayloadRaw, manufOpts)).rejects.toThrow(
10033
- "Cannot have attributes with different manufacturerCode in single 'readReportingConfig' call",
10034
- );
10575
+ await expect(
10576
+ endpoint.read<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", readPayload, manufOpts),
10577
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'read' call");
10578
+ await expect(
10579
+ endpoint.read<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>("hvacThermostat", readPayloadRaw, manufOpts),
10580
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'read' call");
10581
+ await expect(
10582
+ endpoint.configureReporting<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>(
10583
+ "hvacThermostat",
10584
+ configureReportingPayload,
10585
+ manufOpts,
10586
+ ),
10587
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'configureReporting' call");
10588
+ await expect(
10589
+ endpoint.configureReporting<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>(
10590
+ "hvacThermostat",
10591
+ configureReportingPayloadRaw,
10592
+ manufOpts,
10593
+ ),
10594
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'configureReporting' call");
10595
+ await expect(
10596
+ endpoint.readReportingConfig<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>(
10597
+ "hvacThermostat",
10598
+ readReportingConfigPayload,
10599
+ manufOpts,
10600
+ ),
10601
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'readReportingConfig' call");
10602
+ await expect(
10603
+ endpoint.readReportingConfig<"hvacThermostat", CustomClustersTypes["hvacThermostat"]>(
10604
+ "hvacThermostat",
10605
+ readReportingConfigPayloadRaw,
10606
+ manufOpts,
10607
+ ),
10608
+ ).rejects.toThrow("Cannot have attributes with different manufacturerCode in single 'readReportingConfig' call");
10035
10609
 
10036
10610
  expect(zclCommandSpy).toHaveBeenCalledTimes(0);
10037
10611
  });