zigbee-herdsman 5.0.4 → 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 (122) hide show
  1. package/.github/dependabot.yml +3 -0
  2. package/.github/workflows/ci.yml +1 -1
  3. package/.github/workflows/typedoc.yaml +1 -1
  4. package/.release-please-manifest.json +1 -1
  5. package/CHANGELOG.md +29 -0
  6. package/biome.json +1 -1
  7. package/dist/adapter/ember/ezsp/buffalo.d.ts +0 -2
  8. package/dist/adapter/ember/ezsp/buffalo.d.ts.map +1 -1
  9. package/dist/adapter/ember/ezsp/buffalo.js +0 -4
  10. package/dist/adapter/ember/ezsp/buffalo.js.map +1 -1
  11. package/dist/adapter/ember/uart/ash.d.ts.map +1 -1
  12. package/dist/adapter/ember/uart/ash.js +0 -2
  13. package/dist/adapter/ember/uart/ash.js.map +1 -1
  14. package/dist/buffalo/buffalo.d.ts +5 -0
  15. package/dist/buffalo/buffalo.d.ts.map +1 -1
  16. package/dist/buffalo/buffalo.js +7 -0
  17. package/dist/buffalo/buffalo.js.map +1 -1
  18. package/dist/controller/controller.d.ts.map +1 -1
  19. package/dist/controller/controller.js +8 -11
  20. package/dist/controller/controller.js.map +1 -1
  21. package/dist/controller/events.d.ts +2 -1
  22. package/dist/controller/events.d.ts.map +1 -1
  23. package/dist/controller/helpers/request.d.ts.map +1 -1
  24. package/dist/controller/helpers/request.js +2 -1
  25. package/dist/controller/helpers/request.js.map +1 -1
  26. package/dist/controller/helpers/zclFrameConverter.d.ts +2 -4
  27. package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
  28. package/dist/controller/helpers/zclFrameConverter.js +2 -0
  29. package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
  30. package/dist/controller/model/device.d.ts +13 -24
  31. package/dist/controller/model/device.d.ts.map +1 -1
  32. package/dist/controller/model/device.js +88 -129
  33. package/dist/controller/model/device.js.map +1 -1
  34. package/dist/controller/model/endpoint.d.ts +17 -16
  35. package/dist/controller/model/endpoint.d.ts.map +1 -1
  36. package/dist/controller/model/endpoint.js +31 -16
  37. package/dist/controller/model/endpoint.js.map +1 -1
  38. package/dist/controller/model/group.d.ts +6 -6
  39. package/dist/controller/model/group.d.ts.map +1 -1
  40. package/dist/controller/model/group.js +5 -3
  41. package/dist/controller/model/group.js.map +1 -1
  42. package/dist/controller/model/index.d.ts +1 -0
  43. package/dist/controller/model/index.d.ts.map +1 -1
  44. package/dist/controller/model/index.js +3 -1
  45. package/dist/controller/model/index.js.map +1 -1
  46. package/dist/controller/model/zigbeeEntity.d.ts +8 -0
  47. package/dist/controller/model/zigbeeEntity.d.ts.map +1 -0
  48. package/dist/controller/model/zigbeeEntity.js +11 -0
  49. package/dist/controller/model/zigbeeEntity.js.map +1 -0
  50. package/dist/controller/tstype.d.ts +39 -0
  51. package/dist/controller/tstype.d.ts.map +1 -1
  52. package/dist/zspec/zcl/buffaloZcl.d.ts +32 -17
  53. package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -1
  54. package/dist/zspec/zcl/buffaloZcl.js +257 -121
  55. package/dist/zspec/zcl/buffaloZcl.js.map +1 -1
  56. package/dist/zspec/zcl/definition/cluster.d.ts.map +1 -1
  57. package/dist/zspec/zcl/definition/cluster.js +156 -33
  58. package/dist/zspec/zcl/definition/cluster.js.map +1 -1
  59. package/dist/zspec/zcl/definition/clusters-typegen.d.ts +2 -0
  60. package/dist/zspec/zcl/definition/clusters-typegen.d.ts.map +1 -0
  61. package/dist/zspec/zcl/definition/clusters-typegen.js +348 -0
  62. package/dist/zspec/zcl/definition/clusters-typegen.js.map +1 -0
  63. package/dist/zspec/zcl/definition/clusters-types.d.ts +7238 -0
  64. package/dist/zspec/zcl/definition/clusters-types.d.ts.map +1 -0
  65. package/dist/zspec/zcl/definition/clusters-types.js +3 -0
  66. package/dist/zspec/zcl/definition/clusters-types.js.map +1 -0
  67. package/dist/zspec/zcl/definition/enums.d.ts +14 -6
  68. package/dist/zspec/zcl/definition/enums.d.ts.map +1 -1
  69. package/dist/zspec/zcl/definition/enums.js +15 -6
  70. package/dist/zspec/zcl/definition/enums.js.map +1 -1
  71. package/dist/zspec/zcl/definition/foundation.d.ts.map +1 -1
  72. package/dist/zspec/zcl/definition/foundation.js +43 -15
  73. package/dist/zspec/zcl/definition/foundation.js.map +1 -1
  74. package/dist/zspec/zcl/definition/tstype.d.ts +105 -11
  75. package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
  76. package/dist/zspec/zcl/index.d.ts +1 -0
  77. package/dist/zspec/zcl/index.d.ts.map +1 -1
  78. package/dist/zspec/zcl/index.js.map +1 -1
  79. package/dist/zspec/zcl/utils.d.ts +1 -1
  80. package/dist/zspec/zcl/utils.d.ts.map +1 -1
  81. package/dist/zspec/zcl/utils.js +1 -1
  82. package/dist/zspec/zcl/utils.js.map +1 -1
  83. package/dist/zspec/zcl/zclFrame.d.ts.map +1 -1
  84. package/dist/zspec/zcl/zclFrame.js +32 -20
  85. package/dist/zspec/zcl/zclFrame.js.map +1 -1
  86. package/dist/zspec/zdo/buffaloZdo.d.ts +0 -6
  87. package/dist/zspec/zdo/buffaloZdo.d.ts.map +1 -1
  88. package/dist/zspec/zdo/buffaloZdo.js +0 -8
  89. package/dist/zspec/zdo/buffaloZdo.js.map +1 -1
  90. package/package.json +3 -3
  91. package/src/adapter/ember/ezsp/buffalo.ts +0 -5
  92. package/src/adapter/ember/uart/ash.ts +0 -2
  93. package/src/adapter/ezsp/driver/driver.ts +1 -1
  94. package/src/buffalo/buffalo.ts +8 -0
  95. package/src/controller/controller.ts +13 -16
  96. package/src/controller/events.ts +2 -1
  97. package/src/controller/greenPower.ts +4 -4
  98. package/src/controller/helpers/request.ts +3 -1
  99. package/src/controller/helpers/zclFrameConverter.ts +13 -17
  100. package/src/controller/model/device.ts +103 -148
  101. package/src/controller/model/endpoint.ts +112 -64
  102. package/src/controller/model/group.ts +33 -9
  103. package/src/controller/model/index.ts +1 -0
  104. package/src/controller/model/zigbeeEntity.ts +30 -0
  105. package/src/controller/tstype.ts +251 -16
  106. package/src/zspec/zcl/buffaloZcl.ts +323 -238
  107. package/src/zspec/zcl/definition/cluster.ts +156 -33
  108. package/src/zspec/zcl/definition/clusters-typegen.ts +588 -0
  109. package/src/zspec/zcl/definition/clusters-types.ts +7331 -0
  110. package/src/zspec/zcl/definition/enums.ts +14 -5
  111. package/src/zspec/zcl/definition/foundation.ts +43 -15
  112. package/src/zspec/zcl/definition/tstype.ts +118 -8
  113. package/src/zspec/zcl/index.ts +1 -0
  114. package/src/zspec/zcl/utils.ts +1 -1
  115. package/src/zspec/zcl/zclFrame.ts +37 -19
  116. package/src/zspec/zdo/buffaloZdo.ts +0 -9
  117. package/test/controller.test.ts +356 -896
  118. package/test/greenpower.test.ts +0 -12
  119. package/test/zcl.test.ts +13 -11
  120. package/test/zspec/zcl/buffalo.test.ts +216 -74
  121. package/test/zspec/zcl/frame.test.ts +62 -28
  122. package/test/zspec/zcl/utils.test.ts +4 -4
@@ -1,26 +1,37 @@
1
1
  import assert from "node:assert";
2
-
3
2
  import type {Events as AdapterEvents} from "../../adapter";
4
3
  import {logger} from "../../utils/logger";
5
4
  import * as ZSpec from "../../zspec";
6
5
  import {BroadcastAddress} from "../../zspec/enums";
7
6
  import type {Eui64} from "../../zspec/tstypes";
8
7
  import * as Zcl from "../../zspec/zcl";
8
+ import type {TFoundation} from "../../zspec/zcl/definition/clusters-types";
9
9
  import type * as ZclTypes from "../../zspec/zcl/definition/tstype";
10
10
  import * as Zdo from "../../zspec/zdo";
11
11
  import Request from "../helpers/request";
12
12
  import RequestQueue from "../helpers/requestQueue";
13
13
  import * as ZclFrameConverter from "../helpers/zclFrameConverter";
14
14
  import zclTransactionSequenceNumber from "../helpers/zclTransactionSequenceNumber";
15
- import type {KeyValue, SendPolicy} from "../tstype";
15
+ import type {
16
+ ClusterOrRawAttributeKeys,
17
+ ClusterOrRawAttributes,
18
+ ClusterOrRawPayload,
19
+ ClusterOrRawWriteAttributes,
20
+ FoundationOrRawPayload,
21
+ KeyValue,
22
+ PartialClusterOrRawWriteAttributes,
23
+ SendPolicy,
24
+ TCustomCluster,
25
+ } from "../tstype";
16
26
  import Device from "./device";
17
27
  import Entity from "./entity";
18
28
  import Group from "./group";
29
+ import {ZigbeeEntity} from "./zigbeeEntity";
19
30
 
20
31
  const NS = "zh:controller:endpoint";
21
32
 
22
- export interface ConfigureReportingItem {
23
- attribute: string | number | {ID: number; type: number};
33
+ export interface ConfigureReportingItem<Cl extends string | number, Custom extends TCustomCluster | undefined = undefined> {
34
+ attribute: ClusterOrRawAttributeKeys<Cl, Custom>[number] | {ID: number; type: number};
24
35
  minimumReportInterval: number;
25
36
  maximumReportInterval: number;
26
37
  reportableChange: number;
@@ -86,7 +97,7 @@ interface ConfiguredReporting {
86
97
  reportableChange: number;
87
98
  }
88
99
 
89
- export class Endpoint extends Entity {
100
+ export class Endpoint extends ZigbeeEntity {
90
101
  public deviceID?: number;
91
102
  public inputClusters: number[];
92
103
  public outputClusters: number[];
@@ -372,16 +383,22 @@ export class Endpoint extends Entity {
372
383
  if (invalid) throw new Zcl.StatusError(invalid);
373
384
  }
374
385
 
375
- public async report(clusterKey: number | string, attributes: KeyValue, options?: Options): Promise<void> {
386
+ public async report<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
387
+ clusterKey: Cl,
388
+ attributes: PartialClusterOrRawWriteAttributes<Cl, Custom>,
389
+ options?: Options,
390
+ ): Promise<void> {
376
391
  const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
377
- const payload: {attrId: number; dataType: number; attrData: number | string | boolean}[] = [];
392
+ const payload: TFoundation["report"] = [];
378
393
 
379
- for (const [nameOrID, value] of Object.entries(attributes)) {
394
+ for (const nameOrID in attributes) {
380
395
  const attribute = cluster.getAttribute(nameOrID);
381
396
 
382
397
  if (attribute) {
383
- payload.push({attrId: attribute.ID, attrData: value, dataType: attribute.type});
398
+ payload.push({attrId: attribute.ID, attrData: attributes[nameOrID], dataType: attribute.type});
384
399
  } else if (!Number.isNaN(Number(nameOrID))) {
400
+ const value = attributes[nameOrID];
401
+
385
402
  payload.push({attrId: Number(nameOrID), attrData: value.value, dataType: value.type});
386
403
  } else {
387
404
  throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
@@ -391,23 +408,29 @@ export class Endpoint extends Entity {
391
408
  await this.zclCommand(cluster, "report", payload, options, attributes);
392
409
  }
393
410
 
394
- public async write(clusterKey: number | string, attributes: KeyValue, options?: Options): Promise<void> {
411
+ public async write<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
412
+ clusterKey: Cl,
413
+ attributes: PartialClusterOrRawWriteAttributes<Cl, Custom>,
414
+ options?: Options,
415
+ ): Promise<void> {
395
416
  const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
396
417
  const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
397
- optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(
418
+ optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet<Cl, Custom>(
398
419
  cluster,
399
420
  Object.keys(attributes),
400
421
  optionsWithDefaults.manufacturerCode,
401
422
  "write",
402
423
  );
424
+ const payload: TFoundation["write"] = [];
403
425
 
404
- const payload: {attrId: number; dataType: number; attrData: number | string | boolean}[] = [];
405
- for (const [nameOrID, value] of Object.entries(attributes)) {
426
+ for (const nameOrID in attributes) {
406
427
  const attribute = cluster.getAttribute(nameOrID);
407
428
 
408
429
  if (attribute) {
409
- payload.push({attrId: attribute.ID, attrData: value, dataType: attribute.type});
430
+ payload.push({attrId: attribute.ID, attrData: attributes[nameOrID], dataType: attribute.type});
410
431
  } else if (!Number.isNaN(Number(nameOrID))) {
432
+ const value = attributes[nameOrID];
433
+
411
434
  payload.push({attrId: Number(nameOrID), attrData: value.value, dataType: value.type});
412
435
  } else {
413
436
  throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
@@ -417,17 +440,21 @@ export class Endpoint extends Entity {
417
440
  await this.zclCommand(cluster, optionsWithDefaults.writeUndiv ? "writeUndiv" : "write", payload, optionsWithDefaults, attributes, true);
418
441
  }
419
442
 
420
- public async writeResponse(
421
- clusterKey: number | string,
443
+ public async writeResponse<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
444
+ clusterKey: Cl,
422
445
  transactionSequenceNumber: number,
423
- attributes: KeyValue,
446
+ attributes: Partial<Record<ClusterOrRawAttributeKeys<Cl, Custom>[number], TFoundation["writeRsp"][number]>> &
447
+ Record<number, TFoundation["writeRsp"][number]>,
424
448
  options?: Options,
425
449
  ): Promise<void> {
426
450
  assert(options?.transactionSequenceNumber === undefined, "Use parameter");
427
451
  const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
428
- const payload: {status: number; attrId: number}[] = [];
452
+ const payload: TFoundation["writeRsp"] = [];
453
+
454
+ for (const nameOrID in attributes) {
455
+ // biome-ignore lint/style/noNonNullAssertion: from loop
456
+ const value = attributes[nameOrID]!;
429
457
 
430
- for (const [nameOrID, value] of Object.entries(attributes)) {
431
458
  if (value.status !== undefined) {
432
459
  const attribute = cluster.getAttribute(nameOrID);
433
460
 
@@ -452,17 +479,22 @@ export class Endpoint extends Entity {
452
479
  );
453
480
  }
454
481
 
455
- public async read(clusterKey: number | string, attributes: (string | number)[], options?: Options): Promise<KeyValue> {
482
+ // XXX: ideally, the return type should limit to the contents of the `attributes` param
483
+ public async read<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
484
+ clusterKey: Cl,
485
+ attributes: ClusterOrRawAttributeKeys<Cl, Custom>,
486
+ options?: Options,
487
+ ): Promise<ClusterOrRawAttributes<Cl, Custom>> {
456
488
  const device = this.getDevice();
457
489
  const cluster = this.getCluster(clusterKey, device, options?.manufacturerCode);
458
490
  const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
459
- optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(
491
+ optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet<Cl, Custom>(
460
492
  cluster,
461
493
  attributes,
462
494
  optionsWithDefaults.manufacturerCode,
463
495
  "read",
464
496
  );
465
- const payload: {attrId: number}[] = [];
497
+ const payload: TFoundation["read"] = [];
466
498
 
467
499
  for (const attribute of attributes) {
468
500
  if (typeof attribute === "number") {
@@ -480,29 +512,30 @@ export class Endpoint extends Entity {
480
512
 
481
513
  const resultFrame = await this.zclCommand(cluster, "read", payload, optionsWithDefaults, attributes, true);
482
514
 
483
- if (resultFrame) {
484
- return ZclFrameConverter.attributeKeyValue(resultFrame, device.manufacturerID, device.customClusters);
485
- }
486
-
487
- return {};
515
+ return resultFrame
516
+ ? ZclFrameConverter.attributeKeyValue<Cl, Custom>(resultFrame, device.manufacturerID, device.customClusters)
517
+ : ({} as ClusterOrRawWriteAttributes<Cl, Custom>);
488
518
  }
489
519
 
490
- public async readResponse(
491
- clusterKey: number | string,
520
+ public async readResponse<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
521
+ clusterKey: Cl,
492
522
  transactionSequenceNumber: number,
493
- attributes: KeyValue,
523
+ attributes: PartialClusterOrRawWriteAttributes<Cl, Custom>,
494
524
  options?: Options,
495
525
  ): Promise<void> {
496
526
  assert(options?.transactionSequenceNumber === undefined, "Use parameter");
497
527
 
498
528
  const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
499
- const payload: {attrId: number; status: number; dataType: number; attrData: number | string}[] = [];
500
- for (const [nameOrID, value] of Object.entries(attributes)) {
529
+ const payload: TFoundation["readRsp"] = [];
530
+
531
+ for (const nameOrID in attributes) {
501
532
  const attribute = cluster.getAttribute(nameOrID);
502
533
 
503
534
  if (attribute) {
504
- payload.push({attrId: attribute.ID, attrData: value, dataType: attribute.type, status: 0});
535
+ payload.push({attrId: attribute.ID, attrData: attributes[nameOrID], dataType: attribute.type, status: 0});
505
536
  } else if (!Number.isNaN(Number(nameOrID))) {
537
+ const value = attributes[nameOrID];
538
+
506
539
  payload.push({attrId: Number(nameOrID), attrData: value.value, dataType: value.type, status: 0});
507
540
  } else {
508
541
  throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
@@ -688,19 +721,23 @@ export class Endpoint extends Entity {
688
721
  await this.zclCommand(clusterID, "defaultRsp", payload, {direction: Zcl.Direction.SERVER_TO_CLIENT, ...options, transactionSequenceNumber});
689
722
  }
690
723
 
691
- public async configureReporting(clusterKey: number | string, items: ConfigureReportingItem[], options?: Options): Promise<void> {
724
+ public async configureReporting<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
725
+ clusterKey: Cl,
726
+ items: ConfigureReportingItem<Cl, Custom>[],
727
+ options?: Options,
728
+ ): Promise<void> {
692
729
  const cluster = this.getCluster(clusterKey, undefined, options?.manufacturerCode);
693
730
  const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
694
- optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(
731
+ optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet<Cl, Custom>(
695
732
  cluster,
696
733
  items,
697
734
  optionsWithDefaults.manufacturerCode,
698
735
  "configureReporting",
699
736
  );
700
737
 
701
- const payload = items.map((item): KeyValue => {
702
- let dataType: number | undefined;
703
- let attrId: number | undefined;
738
+ const payload = items.map((item): TFoundation["configReport"][number] => {
739
+ let dataType: number;
740
+ let attrId: number;
704
741
 
705
742
  if (typeof item.attribute === "object") {
706
743
  dataType = item.attribute.type;
@@ -711,13 +748,15 @@ export class Endpoint extends Entity {
711
748
  if (attribute) {
712
749
  dataType = attribute.type;
713
750
  attrId = attribute.ID;
751
+ } else {
752
+ throw new Error(`Invalid attribute '${item.attribute}' for cluster '${clusterKey}'`);
714
753
  }
715
754
  }
716
755
 
717
756
  return {
718
757
  direction: Zcl.Direction.CLIENT_TO_SERVER,
719
- attrId, // TODO: biome migration - can be undefined?
720
- dataType, // TODO: biome migration - can be undefined?
758
+ attrId,
759
+ dataType,
721
760
  minRepIntval: item.minimumReportInterval,
722
761
  maxRepIntval: item.maximumReportInterval,
723
762
  repChange: item.reportableChange,
@@ -742,9 +781,10 @@ export class Endpoint extends Entity {
742
781
  this._configuredReportings.push({
743
782
  cluster: cluster.ID,
744
783
  attrId: entry.attrId,
745
- minRepIntval: entry.minRepIntval,
746
- maxRepIntval: entry.maxRepIntval,
747
- repChange: entry.repChange,
784
+ minRepIntval: entry.minRepIntval as number,
785
+ maxRepIntval: entry.maxRepIntval as number,
786
+ // expects items[].attribute to always point to a number DataType
787
+ repChange: entry.repChange as number,
748
788
  manufacturerCode: optionsWithDefaults.manufacturerCode,
749
789
  });
750
790
  }
@@ -753,15 +793,19 @@ export class Endpoint extends Entity {
753
793
  this.save();
754
794
  }
755
795
 
756
- public async writeStructured(clusterKey: number | string, payload: KeyValue, options?: Options): Promise<void> {
796
+ public async writeStructured<Cl extends number | string>(
797
+ clusterKey: Cl,
798
+ payload: TFoundation["writeStructured"],
799
+ options?: Options,
800
+ ): Promise<void> {
757
801
  await this.zclCommand(clusterKey, "writeStructured", payload, options);
758
802
  // TODO: support `writeStructuredResponse`
759
803
  }
760
804
 
761
- public async command(
762
- clusterKey: number | string,
763
- commandKey: number | string,
764
- payload: KeyValue,
805
+ public async command<Cl extends number | string, Co extends number | string, Custom extends TCustomCluster | undefined = undefined>(
806
+ clusterKey: Cl,
807
+ commandKey: Co,
808
+ payload: ClusterOrRawPayload<Cl, Co, Custom>,
765
809
  options?: Options,
766
810
  ): Promise<undefined | KeyValue> {
767
811
  const frame = await this.zclCommand(clusterKey, commandKey, payload, options, undefined, false, Zcl.FrameType.SPECIFIC);
@@ -770,10 +814,10 @@ export class Endpoint extends Entity {
770
814
  }
771
815
  }
772
816
 
773
- public async commandResponse(
774
- clusterKey: number | string,
775
- commandKey: number | string,
776
- payload: KeyValue,
817
+ public async commandResponse<Cl extends number | string, Co extends number | string, Custom extends TCustomCluster | undefined = undefined>(
818
+ clusterKey: Cl,
819
+ commandKey: Co,
820
+ payload: ClusterOrRawPayload<Cl, Co, Custom>,
777
821
  options?: Options,
778
822
  transactionSequenceNumber?: number,
779
823
  ): Promise<void> {
@@ -844,8 +888,12 @@ export class Endpoint extends Entity {
844
888
  const promise = new Promise<{header: Zcl.Header; payload: KeyValue}>((resolve, reject) => {
845
889
  waiter.promise.then(
846
890
  (payload) => {
847
- const frame = Zcl.Frame.fromBuffer(payload.clusterID, payload.header, payload.data, device.customClusters);
848
- resolve({header: frame.header, payload: frame.payload});
891
+ try {
892
+ const frame = Zcl.Frame.fromBuffer(payload.clusterID, payload.header, payload.data, device.customClusters);
893
+ resolve({header: frame.header, payload: frame.payload});
894
+ } catch (error) {
895
+ reject(error);
896
+ }
849
897
  },
850
898
  (error) => reject(error),
851
899
  );
@@ -875,9 +923,9 @@ export class Endpoint extends Entity {
875
923
  };
876
924
  }
877
925
 
878
- private ensureManufacturerCodeIsUniqueAndGet(
926
+ private ensureManufacturerCodeIsUniqueAndGet<Cl extends string | number, Custom extends TCustomCluster | undefined = undefined>(
879
927
  cluster: ZclTypes.Cluster,
880
- attributes: (string | number)[] | ConfigureReportingItem[],
928
+ attributes: (string | number)[] | ConfigureReportingItem<Cl, Custom>[],
881
929
  fallbackManufacturerCode: number | undefined, // XXX: problematic undefined for a "fallback"?
882
930
  caller: string,
883
931
  ): number | undefined {
@@ -958,10 +1006,10 @@ export class Endpoint extends Entity {
958
1006
  }
959
1007
  }
960
1008
 
961
- public async zclCommand(
962
- clusterKey: number | string | ZclTypes.Cluster,
963
- commandKey: number | string | ZclTypes.Command,
964
- payload: KeyValue,
1009
+ public async zclCommand<Cl extends number | string, Co extends number | string, Custom extends TCustomCluster | undefined = undefined>(
1010
+ clusterKey: Cl | ZclTypes.Cluster,
1011
+ commandKey: Co | ZclTypes.Command,
1012
+ payload: ClusterOrRawPayload<Cl, Co, Custom> | FoundationOrRawPayload<Co>,
965
1013
  options?: Options,
966
1014
  logPayload?: KeyValue,
967
1015
  checkStatus = false,
@@ -1015,12 +1063,12 @@ export class Endpoint extends Entity {
1015
1063
  }
1016
1064
  }
1017
1065
 
1018
- public async zclCommandBroadcast(
1066
+ public async zclCommandBroadcast<Cl extends number | string, Co extends number | string, Custom extends TCustomCluster | undefined = undefined>(
1019
1067
  endpoint: number,
1020
1068
  destination: BroadcastAddress,
1021
- clusterKey: number | string,
1022
- commandKey: number | string,
1023
- payload: unknown,
1069
+ clusterKey: Cl,
1070
+ commandKey: Co,
1071
+ payload: ClusterOrRawPayload<Cl, Co, Custom> | FoundationOrRawPayload<Co>,
1024
1072
  options?: Options,
1025
1073
  ): Promise<void> {
1026
1074
  const device = this.getDevice();
@@ -1,12 +1,21 @@
1
1
  import assert from "node:assert";
2
2
  import {logger} from "../../utils/logger";
3
3
  import * as Zcl from "../../zspec/zcl";
4
+ import type {TFoundation} from "../../zspec/zcl/definition/clusters-types";
4
5
  import type {CustomClusters} from "../../zspec/zcl/definition/tstype";
5
6
  import zclTransactionSequenceNumber from "../helpers/zclTransactionSequenceNumber";
6
- import type {DatabaseEntry, KeyValue} from "../tstype";
7
+ import type {
8
+ ClusterOrRawAttributeKeys,
9
+ ClusterOrRawPayload,
10
+ DatabaseEntry,
11
+ KeyValue,
12
+ PartialClusterOrRawWriteAttributes,
13
+ TCustomCluster,
14
+ } from "../tstype";
7
15
  import Device from "./device";
8
16
  import type Endpoint from "./endpoint";
9
17
  import Entity from "./entity";
18
+ import {ZigbeeEntity} from "./zigbeeEntity";
10
19
 
11
20
  const NS = "zh:controller:group";
12
21
 
@@ -23,7 +32,7 @@ interface OptionsWithDefaults extends Options {
23
32
  reservedBits: number;
24
33
  }
25
34
 
26
- export class Group extends Entity {
35
+ export class Group extends ZigbeeEntity {
27
36
  private databaseID: number;
28
37
  public readonly groupID: number;
29
38
  private readonly _members: Endpoint[];
@@ -248,18 +257,24 @@ export class Group extends Entity {
248
257
  * Zigbee functions
249
258
  */
250
259
 
251
- public async write(clusterKey: number | string, attributes: KeyValue, options?: Options): Promise<void> {
260
+ public async write<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
261
+ clusterKey: Cl,
262
+ attributes: PartialClusterOrRawWriteAttributes<Cl, Custom>,
263
+ options?: Options,
264
+ ): Promise<void> {
252
265
  const customClusters = this.#customClusters[options?.direction === Zcl.Direction.SERVER_TO_CLIENT ? 1 : 0 /* default to CLIENT_TO_SERVER */];
253
266
  const cluster = Zcl.Utils.getCluster(clusterKey, options?.manufacturerCode, customClusters);
254
267
  const optionsWithDefaults = this.getOptionsWithDefaults(options, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
255
- const payload: {attrId: number; dataType: number; attrData: number | string | boolean}[] = [];
268
+ const payload: TFoundation["write"] = [];
256
269
 
257
- for (const [nameOrID, value] of Object.entries(attributes)) {
270
+ for (const nameOrID in attributes) {
258
271
  const attribute = cluster.getAttribute(nameOrID);
259
272
 
260
273
  if (attribute) {
261
- payload.push({attrId: attribute.ID, attrData: value, dataType: attribute.type});
274
+ payload.push({attrId: attribute.ID, attrData: attributes[nameOrID], dataType: attribute.type});
262
275
  } else if (!Number.isNaN(Number(nameOrID))) {
276
+ const value = attributes[nameOrID];
277
+
263
278
  payload.push({attrId: Number(nameOrID), attrData: value.value, dataType: value.type});
264
279
  } else {
265
280
  throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
@@ -295,11 +310,15 @@ export class Group extends Entity {
295
310
  }
296
311
  }
297
312
 
298
- public async read(clusterKey: number | string, attributes: (string | number)[], options?: Options): Promise<void> {
313
+ public async read<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
314
+ clusterKey: Cl,
315
+ attributes: ClusterOrRawAttributeKeys<Cl, Custom>,
316
+ options?: Options,
317
+ ): Promise<undefined> {
299
318
  const customClusters = this.#customClusters[options?.direction === Zcl.Direction.SERVER_TO_CLIENT ? 1 : 0 /* default to CLIENT_TO_SERVER */];
300
319
  const cluster = Zcl.Utils.getCluster(clusterKey, options?.manufacturerCode, customClusters);
301
320
  const optionsWithDefaults = this.getOptionsWithDefaults(options, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
302
- const payload: {attrId: number}[] = [];
321
+ const payload: TFoundation["read"] = [];
303
322
 
304
323
  for (const attribute of attributes) {
305
324
  if (typeof attribute === "number") {
@@ -344,7 +363,12 @@ export class Group extends Entity {
344
363
  }
345
364
  }
346
365
 
347
- public async command(clusterKey: number | string, commandKey: number | string, payload: KeyValue, options?: Options): Promise<void> {
366
+ public async command<Cl extends number | string, Co extends number | string, Custom extends TCustomCluster | undefined = undefined>(
367
+ clusterKey: Cl,
368
+ commandKey: Co,
369
+ payload: ClusterOrRawPayload<Cl, Co, Custom>,
370
+ options?: Options,
371
+ ): Promise<undefined> {
348
372
  const customClusters = this.#customClusters[options?.direction === Zcl.Direction.SERVER_TO_CLIENT ? 1 : 0 /* default to CLIENT_TO_SERVER */];
349
373
  const cluster = Zcl.Utils.getCluster(clusterKey, options?.manufacturerCode, customClusters);
350
374
  const optionsWithDefaults = this.getOptionsWithDefaults(options, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
@@ -2,3 +2,4 @@ export {Device} from "./device";
2
2
  export {Endpoint} from "./endpoint";
3
3
  export {Entity} from "./entity";
4
4
  export {Group} from "./group";
5
+ export {ZigbeeEntity} from "./zigbeeEntity";
@@ -0,0 +1,30 @@
1
+ import type {
2
+ ClusterOrRawAttributeKeys,
3
+ ClusterOrRawAttributes,
4
+ ClusterOrRawPayload,
5
+ KeyValue,
6
+ PartialClusterOrRawWriteAttributes,
7
+ TCustomCluster,
8
+ } from "../tstype";
9
+ import Entity from "./entity";
10
+
11
+ export abstract class ZigbeeEntity extends Entity {
12
+ public abstract read<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
13
+ clusterKey: Cl,
14
+ attributes: ClusterOrRawAttributeKeys<Cl, Custom>,
15
+ options?: KeyValue,
16
+ ): Promise<ClusterOrRawAttributes<Cl, Custom> | undefined>;
17
+
18
+ public abstract write<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
19
+ clusterKey: Cl,
20
+ attributes: PartialClusterOrRawWriteAttributes<Cl, Custom>,
21
+ options?: KeyValue,
22
+ ): Promise<void>;
23
+
24
+ public abstract command<Cl extends number | string, Co extends number | string, Custom extends TCustomCluster | undefined = undefined>(
25
+ clusterKey: Cl,
26
+ commandKey: Co,
27
+ payload: ClusterOrRawPayload<Cl, Co, Custom>,
28
+ options?: KeyValue,
29
+ ): Promise<undefined | KeyValue>;
30
+ }