zigbee-herdsman 6.0.0 → 6.0.1

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 (79) hide show
  1. package/.github/workflows/ci.yml +1 -1
  2. package/.github/workflows/typedoc.yaml +1 -1
  3. package/.release-please-manifest.json +1 -1
  4. package/CHANGELOG.md +10 -0
  5. package/dist/controller/controller.d.ts.map +1 -1
  6. package/dist/controller/controller.js +7 -10
  7. package/dist/controller/controller.js.map +1 -1
  8. package/dist/controller/events.d.ts +1 -1
  9. package/dist/controller/events.d.ts.map +1 -1
  10. package/dist/controller/helpers/request.d.ts.map +1 -1
  11. package/dist/controller/helpers/request.js +2 -1
  12. package/dist/controller/helpers/request.js.map +1 -1
  13. package/dist/controller/helpers/zclFrameConverter.d.ts +2 -4
  14. package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
  15. package/dist/controller/helpers/zclFrameConverter.js +2 -0
  16. package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
  17. package/dist/controller/model/device.d.ts +13 -24
  18. package/dist/controller/model/device.d.ts.map +1 -1
  19. package/dist/controller/model/device.js +88 -129
  20. package/dist/controller/model/device.js.map +1 -1
  21. package/dist/controller/model/endpoint.d.ts +17 -16
  22. package/dist/controller/model/endpoint.d.ts.map +1 -1
  23. package/dist/controller/model/endpoint.js +31 -16
  24. package/dist/controller/model/endpoint.js.map +1 -1
  25. package/dist/controller/model/group.d.ts +6 -6
  26. package/dist/controller/model/group.d.ts.map +1 -1
  27. package/dist/controller/model/group.js +5 -3
  28. package/dist/controller/model/group.js.map +1 -1
  29. package/dist/controller/model/index.d.ts +1 -0
  30. package/dist/controller/model/index.d.ts.map +1 -1
  31. package/dist/controller/model/index.js +3 -1
  32. package/dist/controller/model/index.js.map +1 -1
  33. package/dist/controller/model/zigbeeEntity.d.ts +8 -0
  34. package/dist/controller/model/zigbeeEntity.d.ts.map +1 -0
  35. package/dist/controller/model/zigbeeEntity.js +11 -0
  36. package/dist/controller/model/zigbeeEntity.js.map +1 -0
  37. package/dist/controller/tstype.d.ts +22 -0
  38. package/dist/controller/tstype.d.ts.map +1 -1
  39. package/dist/zspec/zcl/definition/cluster.d.ts.map +1 -1
  40. package/dist/zspec/zcl/definition/cluster.js +2 -9
  41. package/dist/zspec/zcl/definition/cluster.js.map +1 -1
  42. package/dist/zspec/zcl/definition/clusters-typegen.js +61 -13
  43. package/dist/zspec/zcl/definition/clusters-typegen.js.map +1 -1
  44. package/dist/zspec/zcl/definition/clusters-types.d.ts +173 -140
  45. package/dist/zspec/zcl/definition/clusters-types.d.ts.map +1 -1
  46. package/dist/zspec/zcl/definition/enums.d.ts +10 -0
  47. package/dist/zspec/zcl/definition/enums.d.ts.map +1 -1
  48. package/dist/zspec/zcl/definition/enums.js +12 -1
  49. package/dist/zspec/zcl/definition/enums.js.map +1 -1
  50. package/dist/zspec/zcl/definition/tstype.d.ts +1 -1
  51. package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
  52. package/dist/zspec/zcl/index.d.ts +1 -0
  53. package/dist/zspec/zcl/index.d.ts.map +1 -1
  54. package/dist/zspec/zcl/index.js.map +1 -1
  55. package/dist/zspec/zcl/utils.d.ts +1 -1
  56. package/dist/zspec/zcl/utils.d.ts.map +1 -1
  57. package/dist/zspec/zcl/utils.js +1 -1
  58. package/dist/zspec/zcl/utils.js.map +1 -1
  59. package/package.json +1 -1
  60. package/src/adapter/ezsp/driver/driver.ts +1 -1
  61. package/src/controller/controller.ts +11 -15
  62. package/src/controller/events.ts +1 -1
  63. package/src/controller/helpers/request.ts +3 -1
  64. package/src/controller/helpers/zclFrameConverter.ts +13 -17
  65. package/src/controller/model/device.ts +103 -148
  66. package/src/controller/model/endpoint.ts +112 -64
  67. package/src/controller/model/group.ts +33 -9
  68. package/src/controller/model/index.ts +1 -0
  69. package/src/controller/model/zigbeeEntity.ts +30 -0
  70. package/src/controller/tstype.ts +234 -0
  71. package/src/zspec/zcl/definition/cluster.ts +2 -9
  72. package/src/zspec/zcl/definition/clusters-typegen.ts +96 -19
  73. package/src/zspec/zcl/definition/clusters-types.ts +195 -146
  74. package/src/zspec/zcl/definition/enums.ts +11 -0
  75. package/src/zspec/zcl/definition/tstype.ts +0 -1
  76. package/src/zspec/zcl/index.ts +1 -0
  77. package/src/zspec/zcl/utils.ts +1 -1
  78. package/test/controller.test.ts +291 -93
  79. package/test/zspec/zcl/utils.test.ts +4 -4
@@ -199,9 +199,12 @@ let iasZoneReadState170Count = 0;
199
199
  let enroll170 = true;
200
200
  let configureReportStatus = 0;
201
201
  let configureReportDefaultRsp = false;
202
+ let lastSentZclFrameToEndpoint: Buffer | undefined;
202
203
 
203
204
  const restoreMocksendZclFrameToEndpoint = () => {
204
205
  mocksendZclFrameToEndpoint.mockImplementation((_ieeeAddr, networkAddress, endpoint, frame: Zcl.Frame) => {
206
+ lastSentZclFrameToEndpoint = frame.toBuffer();
207
+
205
208
  if (
206
209
  frame.header.isGlobal &&
207
210
  frame.isCommand("read") &&
@@ -401,6 +404,7 @@ const mocksRestore = [mockAdapterPermitJoin, mockAdapterStop, mocksendZclFrameTo
401
404
  const events: {
402
405
  deviceJoined: Events.DeviceJoinedPayload[];
403
406
  deviceInterview: Events.DeviceInterviewPayload[];
407
+ deviceInterviewRaw: Events.DeviceInterviewPayload[];
404
408
  adapterDisconnected: number[];
405
409
  deviceAnnounce: Events.DeviceAnnouncePayload[];
406
410
  deviceLeave: Events.DeviceLeavePayload[];
@@ -411,6 +415,7 @@ const events: {
411
415
  } = {
412
416
  deviceJoined: [],
413
417
  deviceInterview: [],
418
+ deviceInterviewRaw: [],
414
419
  adapterDisconnected: [],
415
420
  deviceAnnounce: [],
416
421
  deviceLeave: [],
@@ -490,7 +495,10 @@ describe("Controller", () => {
490
495
  controller = new Controller(options);
491
496
  controller.on("permitJoinChanged", (data) => events.permitJoinChanged.push(data));
492
497
  controller.on("deviceJoined", (data) => events.deviceJoined.push(data));
493
- controller.on("deviceInterview", (data) => events.deviceInterview.push(deepClone(data)));
498
+ controller.on("deviceInterview", (data) => {
499
+ events.deviceInterview.push(deepClone(data));
500
+ events.deviceInterviewRaw.push(data);
501
+ });
494
502
  controller.on("adapterDisconnected", () => events.adapterDisconnected.push(1));
495
503
  controller.on("deviceAnnounce", (data) => events.deviceAnnounce.push(data));
496
504
  controller.on("deviceLeave", (data) => events.deviceLeave.push(data));
@@ -1286,18 +1294,21 @@ describe("Controller", () => {
1286
1294
  },
1287
1295
  ],
1288
1296
  _manufacturerID: 1212,
1289
- _manufacturerName: "KoenAndCo",
1290
- _powerSource: "Mains (single phase)",
1291
- _modelID: "myModelID",
1292
- _applicationVersion: 2,
1293
- _stackVersion: 101,
1294
- _zclVersion: 1,
1295
- _hardwareVersion: 3,
1296
- _dateCode: "201901",
1297
- _softwareBuildID: "1.01",
1298
1297
  _interviewState: InterviewState.Successful,
1299
1298
  };
1299
+ const deviceGenBasic = {
1300
+ manufacturerName: "KoenAndCo",
1301
+ powerSource: Zcl.PowerSource["Mains (single phase)"],
1302
+ modelId: "myModelID",
1303
+ appVersion: 2,
1304
+ stackVersion: 101,
1305
+ zclVersion: 1,
1306
+ hwVersion: 3,
1307
+ dateCode: "201901",
1308
+ swBuildId: "1.01",
1309
+ };
1300
1310
  expect(events.deviceInterview[1]).toStrictEqual({status: "successful", device: device});
1311
+ expect(events.deviceInterviewRaw[1].device.genBasic).toStrictEqual(deviceGenBasic);
1301
1312
  expect(deepClone(controller.getDeviceByNetworkAddress(129))).toStrictEqual(device);
1302
1313
  expect(events.deviceInterview.length).toBe(2);
1303
1314
  expect(databaseContents()).toStrictEqual(
@@ -1316,7 +1327,10 @@ describe("Controller", () => {
1316
1327
  it("Join a device and explictly accept it", async () => {
1317
1328
  controller = new Controller(options);
1318
1329
  controller.on("deviceJoined", (device) => events.deviceJoined.push(device));
1319
- controller.on("deviceInterview", (device) => events.deviceInterview.push(deepClone(device)));
1330
+ controller.on("deviceInterview", (data) => {
1331
+ events.deviceInterview.push(deepClone(data));
1332
+ events.deviceInterviewRaw.push(data);
1333
+ });
1320
1334
  await controller.start();
1321
1335
  expect(databaseContents().includes("0x129")).toBeFalsy();
1322
1336
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
@@ -1371,18 +1385,21 @@ describe("Controller", () => {
1371
1385
  },
1372
1386
  ],
1373
1387
  _manufacturerID: 1212,
1374
- _manufacturerName: "KoenAndCo",
1375
- _powerSource: "Mains (single phase)",
1376
- _modelID: "myModelID",
1377
- _applicationVersion: 2,
1378
- _stackVersion: 101,
1379
- _zclVersion: 1,
1380
- _hardwareVersion: 3,
1381
- _dateCode: "201901",
1382
- _softwareBuildID: "1.01",
1383
1388
  _interviewState: InterviewState.Successful,
1384
1389
  };
1390
+ const deviceGenBasic = {
1391
+ manufacturerName: "KoenAndCo",
1392
+ powerSource: Zcl.PowerSource["Mains (single phase)"],
1393
+ modelId: "myModelID",
1394
+ appVersion: 2,
1395
+ stackVersion: 101,
1396
+ zclVersion: 1,
1397
+ hwVersion: 3,
1398
+ dateCode: "201901",
1399
+ swBuildId: "1.01",
1400
+ };
1385
1401
  expect(events.deviceInterview[1]).toStrictEqual({status: "successful", device: device});
1402
+ expect(events.deviceInterviewRaw[1].device.genBasic).toStrictEqual(deviceGenBasic);
1386
1403
  expect(deepClone(controller.getDeviceByIeeeAddr("0x129"))).toStrictEqual(device);
1387
1404
  expect(events.deviceInterview.length).toBe(2);
1388
1405
  expect(databaseContents().includes("0x129")).toBeTruthy();
@@ -1429,8 +1446,8 @@ describe("Controller", () => {
1429
1446
  await controller.start();
1430
1447
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
1431
1448
  const device = controller.getDeviceByIeeeAddr("0x129")!;
1432
- device.powerSource = "test123";
1433
- expect(device.powerSource).toBe("test123");
1449
+ device.powerSource = "DC Source";
1450
+ expect(device.powerSource).toBe("DC Source");
1434
1451
  });
1435
1452
 
1436
1453
  it("Get device should return same instance", async () => {
@@ -2245,8 +2262,7 @@ describe("Controller", () => {
2245
2262
  expect(events.deviceInterview[1].status).toBe("successful");
2246
2263
  // @ts-expect-error private but deep cloned
2247
2264
  expect(events.deviceInterview[1].device._ieeeAddr).toBe("0x161");
2248
- // @ts-expect-error private but deep cloned
2249
- expect(events.deviceInterview[1].device._modelID).toBe("myDevice9123");
2265
+ expect(events.deviceInterviewRaw[1].device.genBasic.modelId).toBe("myDevice9123");
2250
2266
  });
2251
2267
 
2252
2268
  it("Device joins with endpoints [2,1], as 2 is the only endpoint supporting genBasic it should read modelID from that", async () => {
@@ -2259,8 +2275,7 @@ describe("Controller", () => {
2259
2275
  expect(events.deviceInterview[1].status).toBe("successful");
2260
2276
  // @ts-expect-error private but deep cloned
2261
2277
  expect(events.deviceInterview[1].device._ieeeAddr).toBe("0x162");
2262
- // @ts-expect-error private but deep cloned
2263
- expect(events.deviceInterview[1].device._modelID).toBe("myDevice9124");
2278
+ expect(events.deviceInterviewRaw[1].device.genBasic.modelId).toBe("myDevice9124");
2264
2279
  });
2265
2280
 
2266
2281
  it("Device joins and interview iAs enrollment succeeds", async () => {
@@ -3107,12 +3122,20 @@ describe("Controller", () => {
3107
3122
  ],
3108
3123
  _type: "EndDevice",
3109
3124
  _manufacturerID: 4151,
3110
- _manufacturerName: "LUMI",
3111
3125
  meta: {},
3112
- _powerSource: "Battery",
3113
- _modelID: "lumi.occupancy",
3114
3126
  _interviewState: InterviewState.Successful,
3115
3127
  });
3128
+ expect(controller.getDeviceByIeeeAddr("0x150")?.genBasic).toStrictEqual({
3129
+ appVersion: undefined,
3130
+ dateCode: undefined,
3131
+ hwVersion: undefined,
3132
+ manufacturerName: "LUMI",
3133
+ modelId: "lumi.occupancy",
3134
+ powerSource: Zcl.PowerSource.Battery,
3135
+ stackVersion: undefined,
3136
+ swBuildId: undefined,
3137
+ zclVersion: undefined,
3138
+ });
3116
3139
  });
3117
3140
 
3118
3141
  it("Xiaomi end device joins (node descriptor succeeds, but active endpoint response fails)", async () => {
@@ -3160,12 +3183,20 @@ describe("Controller", () => {
3160
3183
  ],
3161
3184
  _type: "EndDevice",
3162
3185
  _manufacturerID: 1219,
3163
- _manufacturerName: "LUMI",
3164
3186
  meta: {},
3165
- _powerSource: "Battery",
3166
- _modelID: "lumi.occupancy",
3167
3187
  _interviewState: InterviewState.Successful,
3168
3188
  });
3189
+ expect(controller.getDeviceByIeeeAddr("0x151")?.genBasic).toStrictEqual({
3190
+ appVersion: undefined,
3191
+ dateCode: undefined,
3192
+ hwVersion: undefined,
3193
+ manufacturerName: "LUMI",
3194
+ modelId: "lumi.occupancy",
3195
+ powerSource: Zcl.PowerSource.Battery,
3196
+ stackVersion: undefined,
3197
+ swBuildId: undefined,
3198
+ zclVersion: undefined,
3199
+ });
3169
3200
  });
3170
3201
 
3171
3202
  it("Should use cached node descriptor when device is re-interviewed, but retrieve it when ignoreCache=true", async () => {
@@ -3640,7 +3671,7 @@ describe("Controller", () => {
3640
3671
  expect(call[2]).toBe(1);
3641
3672
  expect(call[3].cluster.name).toBe("genPollCtrl");
3642
3673
  expect(call[3].command.name).toBe("checkinRsp");
3643
- expect(call[3].payload).toStrictEqual({startFastPolling: false, fastPollTimeout: 0});
3674
+ expect(call[3].payload).toStrictEqual({startFastPolling: 0, fastPollTimeout: 0});
3644
3675
  });
3645
3676
 
3646
3677
  it("Poll control unsupported", async () => {
@@ -3888,6 +3919,36 @@ describe("Controller", () => {
3888
3919
  );
3889
3920
  });
3890
3921
 
3922
+ it("throws when trying to configure reporting on endpoint with bad attribute", async () => {
3923
+ await controller.start();
3924
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
3925
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
3926
+ const endpoint = device.getEndpoint(1)!;
3927
+ mocksendZclFrameToEndpoint.mockClear();
3928
+
3929
+ await expect(async () => {
3930
+ await endpoint.configureReporting("genPowerCfg", [
3931
+ {
3932
+ attribute: "doesnotexist",
3933
+ minimumReportInterval: 1,
3934
+ maximumReportInterval: 10,
3935
+ reportableChange: 1,
3936
+ },
3937
+ ]);
3938
+ }).rejects.toThrow(`Invalid attribute 'doesnotexist' for cluster 'genPowerCfg'`);
3939
+
3940
+ await expect(async () => {
3941
+ await endpoint.configureReporting("genBasic", [
3942
+ {
3943
+ attribute: 99999,
3944
+ minimumReportInterval: 1,
3945
+ maximumReportInterval: 10,
3946
+ reportableChange: 1,
3947
+ },
3948
+ ]);
3949
+ }).rejects.toThrow(`Invalid attribute '99999' for cluster 'genBasic'`);
3950
+ });
3951
+
3891
3952
  it("Should replace legacy configured reportings without manufacturerCode", async () => {
3892
3953
  await controller.start();
3893
3954
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
@@ -4451,6 +4512,24 @@ describe("Controller", () => {
4451
4512
  expect(error).toStrictEqual(new Error("whoops!"));
4452
4513
  });
4453
4514
 
4515
+ it("Endpoint waitForCommand frame fails to parse", async () => {
4516
+ await controller.start();
4517
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
4518
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
4519
+ const endpoint = device.getEndpoint(1)!;
4520
+ mocksendZclFrameToEndpoint.mockClear();
4521
+ // The buffer below ([24, 169, 10, 0, 0, 24]) is intentionally malformed:
4522
+ // It is missing expected payload bytes for a valid ZCL frame, causing Zcl.Frame.fromBuffer to throw a parsing error.
4523
+ // This triggers the error handling path being tested.
4524
+ const buffer = Buffer.from([24, 169, 10, 0, 0, 24]);
4525
+ const header = Zcl.Header.fromBuffer(buffer);
4526
+ const promise = new Promise((resolve, _reject) => resolve({clusterID: Zcl.Utils.getCluster("msOccupancySensing").ID, data: buffer, header}));
4527
+ mockAdapterWaitFor.mockReturnValueOnce({promise, cancel: () => {}});
4528
+ await expect(endpoint.waitForCommand("genOta", "upgradeEndRequest", 10, 20).promise).rejects.toThrow(
4529
+ `The value of "offset" is out of range. It must be >= 0 and <= 5. Received 6`,
4530
+ );
4531
+ });
4532
+
4454
4533
  it("Device without meta should set meta to {}", async () => {
4455
4534
  Device.resetCache();
4456
4535
  const line = JSON.stringify({
@@ -4490,8 +4569,6 @@ describe("Controller", () => {
4490
4569
  _eventsCount: 0,
4491
4570
  _pendingRequestTimeout: 0,
4492
4571
  _skipDefaultResponse: false,
4493
- _applicationVersion: 17,
4494
- _dateCode: "20170302",
4495
4572
  _customClusters: {},
4496
4573
  _endpoints: [
4497
4574
  {
@@ -4511,21 +4588,25 @@ describe("Controller", () => {
4511
4588
  profileID: 49246,
4512
4589
  },
4513
4590
  ],
4514
- _hardwareVersion: 1,
4515
4591
  _ieeeAddr: "0x90fd9ffffe4b64ae",
4516
4592
  _interviewState: InterviewState.Successful,
4517
4593
  _manufacturerID: 4476,
4518
- _manufacturerName: "IKEA of Sweden",
4519
4594
  meta: {},
4520
- _modelID: "TRADFRI remote control",
4521
4595
  _networkAddress: 19468,
4522
- _powerSource: "Battery",
4523
- _softwareBuildID: "1.2.214",
4524
- _stackVersion: 87,
4525
4596
  _type: "EndDevice",
4526
- _zclVersion: 1,
4527
4597
  };
4528
4598
  expect(deepClone(controller.getDeviceByIeeeAddr("0x90fd9ffffe4b64ae"))).toStrictEqual(expected);
4599
+ expect(controller.getDeviceByIeeeAddr("0x90fd9ffffe4b64ae")?.genBasic).toStrictEqual({
4600
+ manufacturerName: "IKEA of Sweden",
4601
+ modelId: "TRADFRI remote control",
4602
+ powerSource: Zcl.PowerSource.Battery,
4603
+ swBuildId: "1.2.214",
4604
+ stackVersion: 87,
4605
+ zclVersion: 1,
4606
+ appVersion: 17,
4607
+ dateCode: "20170302",
4608
+ hwVersion: 1,
4609
+ });
4529
4610
  });
4530
4611
 
4531
4612
  it("Read from group", async () => {
@@ -4573,7 +4654,7 @@ describe("Controller", () => {
4573
4654
  it("Write to group", async () => {
4574
4655
  await controller.start();
4575
4656
  const group = await controller.createGroup(2);
4576
- await group.write("genBasic", {49: {value: 0x000b, type: 0x19}, deviceEnabled: true}, {});
4657
+ await group.write("genBasic", {49: {value: 0x000b, type: 0x19}, deviceEnabled: 1}, {});
4577
4658
  expect(mocksendZclFrameToGroup).toHaveBeenCalledTimes(1);
4578
4659
  expect(mocksendZclFrameToGroup.mock.calls[0][0]).toBe(2);
4579
4660
  expect(deepClone(mocksendZclFrameToGroup.mock.calls[0][1])).toStrictEqual(
@@ -4588,7 +4669,7 @@ describe("Controller", () => {
4588
4669
  0,
4589
4670
  [
4590
4671
  {attrData: 11, attrId: 49, dataType: 25},
4591
- {attrData: true, attrId: 18, dataType: 16},
4672
+ {attrData: 1, attrId: 18, dataType: 16},
4592
4673
  ],
4593
4674
  {},
4594
4675
  ),
@@ -4602,7 +4683,7 @@ describe("Controller", () => {
4602
4683
  const group = await controller.createGroup(2);
4603
4684
  let error;
4604
4685
  try {
4605
- await group.write("genBasic", {UNKNOWN: {value: 0x000b, type: 0x19}, deviceEnabled: true}, {});
4686
+ await group.write("genBasic", {UNKNOWN: {value: 0x000b, type: 0x19}, deviceEnabled: 1}, {});
4606
4687
  } catch (e) {
4607
4688
  error = e;
4608
4689
  }
@@ -5293,8 +5374,6 @@ describe("Controller", () => {
5293
5374
  _eventsCount: 0,
5294
5375
  _pendingRequestTimeout: 0,
5295
5376
  _skipDefaultResponse: false,
5296
- _applicationVersion: 17,
5297
- _dateCode: "20170331",
5298
5377
  _customClusters: {},
5299
5378
  _endpoints: [
5300
5379
  {
@@ -5314,27 +5393,29 @@ describe("Controller", () => {
5314
5393
  profileID: 49246,
5315
5394
  },
5316
5395
  ],
5317
- _hardwareVersion: 1,
5318
5396
  _ieeeAddr: "0x000b57fffec6a5b2",
5319
5397
  _interviewState: InterviewState.Successful,
5320
5398
  _manufacturerID: 4476,
5321
- _manufacturerName: "IKEA of Sweden",
5322
5399
  meta: {reporting: 1},
5323
- _modelID: "TRADFRI bulb E27 WS opal 980lm",
5324
5400
  _networkAddress: 40369,
5325
- _powerSource: "Mains (single phase)",
5326
- _softwareBuildID: "1.2.217",
5327
- _stackVersion: 87,
5328
5401
  _type: "Router",
5329
- _zclVersion: 1,
5402
+ });
5403
+ expect(controller.getDeviceByIeeeAddr("0x000b57fffec6a5b2")?.genBasic).toStrictEqual({
5404
+ appVersion: 17,
5405
+ dateCode: "20170331",
5406
+ hwVersion: 1,
5407
+ manufacturerName: "IKEA of Sweden",
5408
+ modelId: "TRADFRI bulb E27 WS opal 980lm",
5409
+ powerSource: Zcl.PowerSource["Mains (single phase)"],
5410
+ swBuildId: "1.2.217",
5411
+ stackVersion: 87,
5412
+ zclVersion: 1,
5330
5413
  });
5331
5414
  expect(deepClone(controller.getDeviceByIeeeAddr("0x0017880104e45517"))).toStrictEqual({
5332
5415
  ID: 4,
5333
5416
  _events: {},
5334
5417
  _eventsCount: 0,
5335
5418
  _pendingRequestTimeout: 0,
5336
- _applicationVersion: 2,
5337
- _dateCode: "20160302",
5338
5419
  _customClusters: {},
5339
5420
  _endpoints: [
5340
5421
  {
@@ -5370,30 +5451,32 @@ describe("Controller", () => {
5370
5451
  pendingRequests: {id: 2, deviceIeeeAddress: "0x0017880104e45517", sendInProgress: false},
5371
5452
  },
5372
5453
  ],
5373
- _hardwareVersion: 1,
5374
5454
  _ieeeAddr: "0x0017880104e45517",
5375
5455
  _interviewState: InterviewState.Successful,
5376
5456
  _lastSeen: 123,
5377
5457
  _manufacturerID: 4107,
5378
- _manufacturerName: "Philips",
5379
- _modelID: "RWL021",
5380
5458
  _networkAddress: 6538,
5381
- _powerSource: "Battery",
5382
- _softwareBuildID: "5.45.1.17846",
5383
- _stackVersion: 1,
5384
5459
  _type: "EndDevice",
5385
- _zclVersion: 1,
5386
5460
  _skipDefaultResponse: false,
5387
5461
  meta: {configured: 1},
5388
5462
  });
5463
+ expect(controller.getDeviceByIeeeAddr("0x0017880104e45517")?.genBasic).toStrictEqual({
5464
+ appVersion: 2,
5465
+ dateCode: "20160302",
5466
+ hwVersion: 1,
5467
+ manufacturerName: "Philips",
5468
+ modelId: "RWL021",
5469
+ powerSource: Zcl.PowerSource.Battery,
5470
+ swBuildId: "5.45.1.17846",
5471
+ stackVersion: 1,
5472
+ zclVersion: 1,
5473
+ });
5389
5474
  expect(deepClone(controller.getDeviceByIeeeAddr("0x0017880104e45518"))).toStrictEqual({
5390
5475
  ID: 6,
5391
5476
  _checkinInterval: 123456,
5392
5477
  _events: {},
5393
5478
  _eventsCount: 0,
5394
5479
  _pendingRequestTimeout: 123456000,
5395
- _applicationVersion: 2,
5396
- _dateCode: "20160302",
5397
5480
  _customClusters: {},
5398
5481
  _endpoints: [
5399
5482
  {
@@ -5429,21 +5512,25 @@ describe("Controller", () => {
5429
5512
  pendingRequests: {id: 2, deviceIeeeAddress: "0x0017880104e45518", sendInProgress: false},
5430
5513
  },
5431
5514
  ],
5432
- _hardwareVersion: 1,
5433
5515
  _ieeeAddr: "0x0017880104e45518",
5434
5516
  _interviewState: InterviewState.Successful,
5435
5517
  _manufacturerID: 4107,
5436
- _manufacturerName: "Philips",
5437
- _modelID: "RWL021",
5438
5518
  _networkAddress: 6536,
5439
- _powerSource: "Battery",
5440
- _softwareBuildID: "5.45.1.17846",
5441
- _stackVersion: 1,
5442
5519
  _type: "EndDevice",
5443
- _zclVersion: 1,
5444
5520
  _skipDefaultResponse: false,
5445
5521
  meta: {configured: 1},
5446
5522
  });
5523
+ expect(controller.getDeviceByIeeeAddr("0x0017880104e45518")?.genBasic).toStrictEqual({
5524
+ appVersion: 2,
5525
+ dateCode: "20160302",
5526
+ hwVersion: 1,
5527
+ manufacturerName: "Philips",
5528
+ modelId: "RWL021",
5529
+ powerSource: Zcl.PowerSource.Battery,
5530
+ swBuildId: "5.45.1.17846",
5531
+ stackVersion: 1,
5532
+ zclVersion: 1,
5533
+ });
5447
5534
  expect((await controller.getGroups()).length).toBe(2);
5448
5535
 
5449
5536
  const group1 = controller.getGroupByID(1)!;
@@ -5957,25 +6044,23 @@ describe("Controller", () => {
5957
6044
  });
5958
6045
 
5959
6046
  it("Write structured", async () => {
5960
- await controller.start();
5961
6047
  await controller.start();
5962
6048
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
5963
6049
  const device = controller.getDeviceByIeeeAddr("0x129")!;
5964
6050
  const endpoint = device.getEndpoint(1)!;
5965
6051
  mocksendZclFrameToEndpoint.mockReturnValueOnce(null);
5966
6052
 
5967
- await endpoint.writeStructured("genPowerCfg", {});
6053
+ await endpoint.writeStructured("genPowerCfg", []);
5968
6054
  });
5969
6055
 
5970
6056
  it("Write structured with disable response", async () => {
5971
- await controller.start();
5972
6057
  await controller.start();
5973
6058
  await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
5974
6059
  const device = controller.getDeviceByIeeeAddr("0x129")!;
5975
6060
  const endpoint = device.getEndpoint(1)!;
5976
6061
  mocksendZclFrameToEndpoint.mockReturnValueOnce(null);
5977
6062
 
5978
- await endpoint.writeStructured("genPowerCfg", {}, {disableResponse: true});
6063
+ await endpoint.writeStructured("genPowerCfg", [], {disableResponse: true});
5979
6064
  });
5980
6065
 
5981
6066
  it("Write structured error", async () => {
@@ -5986,17 +6071,71 @@ describe("Controller", () => {
5986
6071
  mocksendZclFrameToEndpoint.mockRejectedValueOnce(new Error("timeout occurred"));
5987
6072
  let error;
5988
6073
  try {
5989
- await endpoint.writeStructured("genPowerCfg", {});
6074
+ await endpoint.writeStructured("genPowerCfg", []);
5990
6075
  } catch (e) {
5991
6076
  error = e;
5992
6077
  }
5993
6078
  expect(error).toStrictEqual(
5994
6079
  new Error(
5995
- `ZCL command 0x129/1 genPowerCfg.writeStructured({}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"reservedBits":0,"writeUndiv":false}) failed (timeout occurred)`,
6080
+ `ZCL command 0x129/1 genPowerCfg.writeStructured([], {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"reservedBits":0,"writeUndiv":false}) failed (timeout occurred)`,
5996
6081
  ),
5997
6082
  );
5998
6083
  });
5999
6084
 
6085
+ it("Write with custom payload", async () => {
6086
+ await controller.start();
6087
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
6088
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
6089
+ const endpoint = device.getEndpoint(1)!;
6090
+
6091
+ const writeOptions = {
6092
+ frameType: 0,
6093
+ manufacturerCode: 0x1ad2,
6094
+ disableDefaultResponse: true,
6095
+ disableResponse: true,
6096
+ reservedBits: 3,
6097
+ direction: 1,
6098
+ writeUndiv: true,
6099
+ transactionSequenceNumber: 0xe9,
6100
+ };
6101
+
6102
+ await endpoint.writeStructured(
6103
+ "genPowerCfg",
6104
+ [
6105
+ {
6106
+ attrId: 0x0000,
6107
+ // @ts-expect-error workaround write custom payload, special case "do not write anything"
6108
+ selector: null,
6109
+ elementData: [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
6110
+ // dataType: undefined,
6111
+ },
6112
+ ],
6113
+ writeOptions,
6114
+ );
6115
+
6116
+ expect(lastSentZclFrameToEndpoint).toStrictEqual(
6117
+ // Note: 0x00 before start of payload is from having dataType=undefined (gets written as zero)
6118
+ Buffer.from([0x7c, 0xd2, 0x1a, 0xe9, 0x0f, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
6119
+ );
6120
+
6121
+ await endpoint.write(
6122
+ "genPowerCfg",
6123
+ {
6124
+ // @ts-expect-error workaround write custom payload
6125
+ 4865: {
6126
+ value: [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
6127
+ // type: undefined,
6128
+ },
6129
+ },
6130
+ writeOptions,
6131
+ );
6132
+
6133
+ expect(lastSentZclFrameToEndpoint).toStrictEqual(
6134
+ // Note: 0x00 before start of payload is from having dataType=undefined (gets written as zero)
6135
+ Buffer.from([0x7c, 0xd2, 0x1a, 0xe9, 0x03, 0x01, 0x13, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
6136
+ );
6137
+ });
6138
+
6000
6139
  it("Green power", async () => {
6001
6140
  await controller.start();
6002
6141
  const data = {
@@ -6097,7 +6236,6 @@ describe("Controller", () => {
6097
6236
  _interviewState: InterviewState.Successful,
6098
6237
  _lastSeen: Date.now(),
6099
6238
  _linkquality: 50,
6100
- _modelID: "GreenPower_2",
6101
6239
  _networkAddress: 0xf4fe,
6102
6240
  _type: "GreenPower",
6103
6241
  meta: {},
@@ -6117,13 +6255,16 @@ describe("Controller", () => {
6117
6255
  _eventsCount: 0,
6118
6256
  _ieeeAddr: "0x000000000046f4fe",
6119
6257
  _interviewState: InterviewState.Successful,
6120
- _modelID: "GreenPower_2",
6121
6258
  _networkAddress: 0xf4fe,
6122
6259
  _type: "GreenPower",
6123
6260
  meta: {},
6124
6261
  _gpSecurityKey: [0xf1, 0xec, 0x92, 0xab, 0xff, 0x8f, 0x13, 0x63, 0xe1, 0x46, 0xbe, 0xb5, 0x18, 0xc9, 0x0c, 0xab],
6125
6262
  },
6126
6263
  });
6264
+ expect(deepClone(events.deviceInterviewRaw[0].device.genBasic)).toStrictEqual({
6265
+ modelId: "GreenPower_2",
6266
+ powerSource: 0,
6267
+ });
6127
6268
  expect(controller.getDeviceByIeeeAddr("0x000000000046f4fe")!.networkAddress).toBe(0xf4fe);
6128
6269
  expect(events.message.length).toBe(2);
6129
6270
 
@@ -6692,13 +6833,13 @@ describe("Controller", () => {
6692
6833
  _interviewState: InterviewState.Successful,
6693
6834
  _lastSeen: Date.now(),
6694
6835
  _linkquality: 50,
6695
- _modelID: "GreenPower_2",
6696
6836
  _networkAddress: 0x71f8,
6697
6837
  _type: "GreenPower",
6698
6838
  meta: {},
6699
6839
  _gpSecurityKey: [0x21, 0x7f, 0x8c, 0xb2, 0x90, 0xd9, 0x90, 0x14, 0x15, 0xd0, 0x5c, 0xb1, 0x64, 0x7c, 0x44, 0x6c],
6700
6840
  },
6701
6841
  });
6842
+ expect(controller.getDeviceByIeeeAddr("0x00000000017171f8")?.genBasic.modelId).toStrictEqual("GreenPower_2");
6702
6843
  expect(events.deviceInterview.length).toBe(3); // gpp[started] + gpp[successful] + gpd
6703
6844
  expect(deepClone(events.deviceInterview[2])).toStrictEqual({
6704
6845
  status: "successful",
@@ -6712,13 +6853,16 @@ describe("Controller", () => {
6712
6853
  _endpoints: [],
6713
6854
  _ieeeAddr: "0x00000000017171f8",
6714
6855
  _interviewState: InterviewState.Successful,
6715
- _modelID: "GreenPower_2",
6716
6856
  _networkAddress: 0x71f8,
6717
6857
  _type: "GreenPower",
6718
6858
  meta: {},
6719
6859
  _gpSecurityKey: [0x21, 0x7f, 0x8c, 0xb2, 0x90, 0xd9, 0x90, 0x14, 0x15, 0xd0, 0x5c, 0xb1, 0x64, 0x7c, 0x44, 0x6c],
6720
6860
  },
6721
6861
  });
6862
+ expect(deepClone(events.deviceInterviewRaw[2].device.genBasic)).toStrictEqual({
6863
+ modelId: "GreenPower_2",
6864
+ powerSource: Zcl.PowerSource.Unknown,
6865
+ });
6722
6866
  expect(controller.getDeviceByIeeeAddr("0x00000000017171f8")!.networkAddress).toBe(0x71f8);
6723
6867
  expect(events.message.length).toBe(2);
6724
6868
 
@@ -6818,12 +6962,12 @@ describe("Controller", () => {
6818
6962
  _interviewState: InterviewState.Successful,
6819
6963
  _lastSeen: Date.now(),
6820
6964
  _linkquality: 50,
6821
- _modelID: "GreenPower_2",
6822
6965
  _networkAddress: 0x71f8,
6823
6966
  _type: "GreenPower",
6824
6967
  meta: {},
6825
6968
  _gpSecurityKey: [0x21, 0x7f, 0x8c, 0xb2, 0x90, 0xd9, 0x90, 0x14, 0x15, 0xd0, 0x5c, 0xb1, 0x64, 0x7c, 0x44, 0x6c],
6826
6969
  });
6970
+ expect(Device.byIeeeAddr("0x00000000017171f8", true)?.genBasic.modelId).toStrictEqual("GreenPower_2");
6827
6971
 
6828
6972
  // Re-add device
6829
6973
  vi.spyOn(Zcl.Frame, "fromBuffer").mockReturnValueOnce(expectedFrame); // Mock because no Buffalo write for 0xe0 is implemented
@@ -6865,12 +7009,12 @@ describe("Controller", () => {
6865
7009
  _interviewState: InterviewState.Successful,
6866
7010
  _lastSeen: Date.now(),
6867
7011
  _linkquality: 50,
6868
- _modelID: "GreenPower_2",
6869
7012
  _networkAddress: 0x71f8,
6870
7013
  _type: "GreenPower",
6871
7014
  meta: {},
6872
7015
  _gpSecurityKey: [0x21, 0x7f, 0x8c, 0xb2, 0x90, 0xd9, 0x90, 0x14, 0x15, 0xd0, 0x5c, 0xb1, 0x64, 0x7c, 0x44, 0x6c],
6873
7016
  });
7017
+ expect(Device.byIeeeAddr("0x00000000017171f8")?.genBasic.modelId).toStrictEqual("GreenPower_2");
6874
7018
  });
6875
7019
 
6876
7020
  it("Get input/ouptut clusters", async () => {
@@ -7455,7 +7599,7 @@ describe("Controller", () => {
7455
7599
  expect(checkinrsp[2]).toBe(1);
7456
7600
  expect(checkinrsp[3].cluster.name).toBe("genPollCtrl");
7457
7601
  expect(checkinrsp[3].command.name).toBe("checkinRsp");
7458
- expect(checkinrsp[3].payload).toStrictEqual({startFastPolling: true, fastPollTimeout: 0});
7602
+ expect(checkinrsp[3].payload).toStrictEqual({startFastPolling: 1, fastPollTimeout: 0});
7459
7603
 
7460
7604
  expect(await result).toBe(undefined);
7461
7605
 
@@ -8223,6 +8367,12 @@ describe("Controller", () => {
8223
8367
  });
8224
8368
 
8225
8369
  it("reads/writes to group with custom cluster when common to all members", async () => {
8370
+ interface CustomManuHerdsman {
8371
+ attributes: {customAttr: number};
8372
+ commands: never;
8373
+ commandResponses: never;
8374
+ }
8375
+
8226
8376
  await controller.start();
8227
8377
  await mockAdapterEvents.deviceJoined({networkAddress: 177, ieeeAddr: "0x177"});
8228
8378
 
@@ -8239,8 +8389,8 @@ describe("Controller", () => {
8239
8389
 
8240
8390
  group.addMember(device.getEndpoint(1)!);
8241
8391
 
8242
- await group.write("manuHerdsman", {customAttr: 15}, {});
8243
- await group.read("manuHerdsman", ["customAttr"], {});
8392
+ await group.write<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", {customAttr: 15}, {});
8393
+ await group.read<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", ["customAttr"], {});
8244
8394
 
8245
8395
  expect(mocksendZclFrameToGroup).toHaveBeenCalledTimes(2);
8246
8396
  expect(mocksendZclFrameToGroup.mock.calls[0][0]).toBe(34);
@@ -8299,22 +8449,22 @@ describe("Controller", () => {
8299
8449
  await group.read("manuHerdsman", ["customAttr"], {});
8300
8450
  }).rejects.toThrow(new Error(`Cluster with name 'manuHerdsman' does not exist`));
8301
8451
 
8302
- await group.write("manuHerdsman", {customAttr: 14}, {direction: Zcl.Direction.SERVER_TO_CLIENT});
8303
- await group.read("manuHerdsman", ["customAttr"], {direction: Zcl.Direction.SERVER_TO_CLIENT});
8452
+ await group.write<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", {customAttr: 14}, {direction: Zcl.Direction.SERVER_TO_CLIENT});
8453
+ await group.read<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", ["customAttr"], {direction: Zcl.Direction.SERVER_TO_CLIENT});
8304
8454
 
8305
8455
  expect(mocksendZclFrameToGroup).toHaveBeenCalledTimes(4);
8306
8456
 
8307
8457
  group.removeMember(device2.getEndpoint(2)!);
8308
8458
 
8309
- await group.write("manuHerdsman", {customAttr: 16}, {});
8310
- await group.read("manuHerdsman", ["customAttr"], {});
8459
+ await group.write<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", {customAttr: 16}, {});
8460
+ await group.read<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", ["customAttr"], {});
8311
8461
 
8312
8462
  expect(mocksendZclFrameToGroup).toHaveBeenCalledTimes(6);
8313
8463
 
8314
8464
  group.addMember(device2.getEndpoint(1)!);
8315
8465
 
8316
- await group.write("manuHerdsman", {customAttr: 8}, {});
8317
- await group.read("manuHerdsman", ["customAttr"], {});
8466
+ await group.write<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", {customAttr: 8}, {});
8467
+ await group.read<"manuHerdsman", CustomManuHerdsman>("manuHerdsman", ["customAttr"], {});
8318
8468
 
8319
8469
  expect(mocksendZclFrameToGroup).toHaveBeenCalledTimes(8);
8320
8470
 
@@ -8501,4 +8651,52 @@ describe("Controller", () => {
8501
8651
  });
8502
8652
  }).rejects.toThrow(new Error(`Cluster with name 'manuSpecificInovelli' does not exist`));
8503
8653
  });
8654
+
8655
+ it("Updates a device genBasic properties", async () => {
8656
+ await controller.start();
8657
+ expect(databaseContents().includes("0x129")).toBeFalsy();
8658
+ await mockAdapterEvents.deviceJoined({networkAddress: 129, ieeeAddr: "0x129"});
8659
+
8660
+ const device = controller.getDeviceByIeeeAddr("0x129")!;
8661
+
8662
+ expect(device.applicationVersion).toStrictEqual(2);
8663
+ expect(device.dateCode).toStrictEqual("201901");
8664
+ expect(device.hardwareVersion).toStrictEqual(3);
8665
+ expect(device.manufacturerName).toStrictEqual("KoenAndCo");
8666
+ expect(device.modelID).toStrictEqual("myModelID");
8667
+ expect(device.powerSource).toStrictEqual("Mains (single phase)");
8668
+ expect(device.softwareBuildID).toStrictEqual("1.01");
8669
+ expect(device.stackVersion).toStrictEqual(101);
8670
+ expect(device.zclVersion).toStrictEqual(1);
8671
+
8672
+ device.applicationVersion = 3;
8673
+ device.dateCode = "202501";
8674
+ device.hardwareVersion = 4;
8675
+ device.manufacturerName = "Test";
8676
+ device.modelID = "Me";
8677
+ device.powerSource = "DC Source";
8678
+ device.softwareBuildID = "2.01";
8679
+ device.stackVersion = 202;
8680
+ device.zclVersion = 2;
8681
+
8682
+ expect(device.applicationVersion).toStrictEqual(3);
8683
+ expect(device.dateCode).toStrictEqual("202501");
8684
+ expect(device.hardwareVersion).toStrictEqual(4);
8685
+ expect(device.manufacturerName).toStrictEqual("Test");
8686
+ expect(device.modelID).toStrictEqual("Me");
8687
+ expect(device.powerSource).toStrictEqual("DC Source");
8688
+ expect(device.softwareBuildID).toStrictEqual("2.01");
8689
+ expect(device.stackVersion).toStrictEqual(202);
8690
+ expect(device.zclVersion).toStrictEqual(2);
8691
+
8692
+ expect(device.genBasic.appVersion).toStrictEqual(3);
8693
+ expect(device.genBasic.dateCode).toStrictEqual("202501");
8694
+ expect(device.genBasic.hwVersion).toStrictEqual(4);
8695
+ expect(device.genBasic.manufacturerName).toStrictEqual("Test");
8696
+ expect(device.genBasic.modelId).toStrictEqual("Me");
8697
+ expect(device.genBasic.powerSource).toStrictEqual(Zcl.PowerSource["DC Source"]);
8698
+ expect(device.genBasic.swBuildId).toStrictEqual("2.01");
8699
+ expect(device.genBasic.stackVersion).toStrictEqual(202);
8700
+ expect(device.genBasic.zclVersion).toStrictEqual(2);
8701
+ });
8504
8702
  });