@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
@@ -15,7 +15,7 @@ import * as ZSpec from "../../../zspec";
15
15
  import * as Zcl from "../../../zspec/zcl";
16
16
  import * as Zdo from "../../../zspec/zdo";
17
17
  import type * as ZdoTypes from "../../../zspec/zdo/definition/tstypes";
18
- import {Adapter} from "../../adapter";
18
+ import {Adapter, type ClusterWaitressMatcher, type ZclWaitressPayload} from "../../adapter";
19
19
  import type {ZclPayload} from "../../events";
20
20
  import {SerialPort} from "../../serialPort";
21
21
  import type * as TsType from "../../tstype";
@@ -24,16 +24,8 @@ import {bigUInt64ToHexBE} from "./utils";
24
24
 
25
25
  const NS = "zh:zoh";
26
26
 
27
- interface WaitressMatcher {
28
- sender: number | string;
29
- clusterId: number;
30
- endpoint?: number;
31
- commandId?: number;
32
- transactionSequenceNumber?: number;
33
- }
34
-
35
27
  type ZdoResponse = {
36
- sender: number | string;
28
+ address: number | string;
37
29
  clusterId: number;
38
30
  response: ZdoTypes.GenericZdoResponse;
39
31
  seqNum: number;
@@ -79,8 +71,8 @@ export class ZoHAdapter extends Adapter {
79
71
  public readonly stackConfig: StackConfig;
80
72
  public readonly driver: OTRCPDriver;
81
73
  private readonly queue: Queue;
82
- private readonly zclWaitress: Waitress<ZclPayload, WaitressMatcher>;
83
- private readonly zdoWaitress: Waitress<ZdoResponse, WaitressMatcher>;
74
+ private readonly zclWaitress: Waitress<ZclWaitressPayload, ClusterWaitressMatcher>;
75
+ private readonly zdoWaitress: Waitress<ZdoResponse, ClusterWaitressMatcher>;
84
76
 
85
77
  constructor(
86
78
  networkOptions: TsType.NetworkOptions,
@@ -145,8 +137,8 @@ export class ZoHAdapter extends Adapter {
145
137
  dirname(this.backupPath),
146
138
  );
147
139
  this.queue = new Queue(this.adapterOptions.concurrent || /* v8 ignore next */ 8); // ORed to avoid 0 (not checked in settings/queue constructor)
148
- this.zclWaitress = new Waitress(this.zclWaitressValidator, this.waitressTimeoutFormatter);
149
- this.zdoWaitress = new Waitress(this.zdoWaitressValidator, this.waitressTimeoutFormatter);
140
+ this.zclWaitress = new Waitress(Adapter.zclWaitressValidator, Adapter.clusterWaitressTimeoutFormatter);
141
+ this.zdoWaitress = new Waitress(this.zdoWaitressValidator, Adapter.clusterWaitressTimeoutFormatter);
150
142
 
151
143
  this.interpanLock = false;
152
144
  }
@@ -235,7 +227,7 @@ export class ZoHAdapter extends Adapter {
235
227
 
236
228
  return await new Promise((resolve, reject): void => {
237
229
  const openError = async (err: Error): Promise<void> => {
238
- await this.stop();
230
+ await this.closePort();
239
231
 
240
232
  reject(err);
241
233
  };
@@ -312,7 +304,7 @@ export class ZoHAdapter extends Adapter {
312
304
  await wait(150);
313
305
  }
314
306
  } catch (error) {
315
- await this.stop();
307
+ await this.closePort();
316
308
 
317
309
  throw error;
318
310
  }
@@ -461,18 +453,13 @@ export class ZoHAdapter extends Adapter {
461
453
  _frameType: Zcl.FrameType,
462
454
  _direction: Zcl.Direction,
463
455
  transactionSequenceNumber: number | undefined,
464
- clusterID: number,
465
- commandIdentifier: number,
456
+ clusterId: number,
457
+ commandId: number,
458
+ defaultRspCommandId: number | undefined,
466
459
  timeout: number,
467
460
  ): {promise: Promise<ZclPayload>; cancel: () => void} {
468
461
  const waiter = this.zclWaitress.waitFor(
469
- {
470
- sender: networkAddress,
471
- endpoint,
472
- clusterId: clusterID,
473
- commandId: commandIdentifier,
474
- transactionSequenceNumber,
475
- },
462
+ {address: networkAddress, endpoint, clusterId, commandId, defaultRspCommandId, transactionSequenceNumber},
476
463
  timeout,
477
464
  );
478
465
  const cancel = (): void => this.zclWaitress.remove(waiter.ID);
@@ -540,7 +527,7 @@ export class ZoHAdapter extends Adapter {
540
527
  const resp = await this.zdoWaitress
541
528
  .waitFor(
542
529
  {
543
- sender: responseClusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE ? ieeeAddress : networkAddress,
530
+ address: responseClusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE ? ieeeAddress : networkAddress,
544
531
  clusterId: responseClusterId,
545
532
  transactionSequenceNumber: zdoSeqNum,
546
533
  },
@@ -645,10 +632,11 @@ export class ZoHAdapter extends Adapter {
645
632
  const resp = await this.zclWaitress
646
633
  .waitFor(
647
634
  {
648
- sender: networkAddress,
635
+ address: networkAddress,
649
636
  clusterId: zclFrame.cluster.ID,
650
637
  endpoint,
651
638
  commandId: commandResponseId,
639
+ defaultRspCommandId: undefined,
652
640
  transactionSequenceNumber: zclFrame.header.transactionSequenceNumber,
653
641
  },
654
642
  timeout,
@@ -774,11 +762,11 @@ export class ZoHAdapter extends Adapter {
774
762
  // special case to properly resolve a NETWORK_ADDRESS_RESPONSE following a NETWORK_ADDRESS_REQUEST (based on EUI64 from ZDO payload)
775
763
  // NOTE: if response has invalid status (no EUI64 available), response waiter will eventually time out
776
764
  if (Zdo.Buffalo.checkStatus<Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE>(result)) {
777
- this.zdoWaitress.resolve({sender: result[1].eui64, clusterId: apsHeader.clusterId, response: result, seqNum: apsPayload[0]});
765
+ this.zdoWaitress.resolve({address: result[1].eui64, clusterId: apsHeader.clusterId, response: result, seqNum: apsPayload[0]});
778
766
  }
779
767
  } else {
780
768
  // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
781
- this.zdoWaitress.resolve({sender: sender16!, clusterId: apsHeader.clusterId!, response: result, seqNum: apsPayload[0]});
769
+ this.zdoWaitress.resolve({address: sender16!, clusterId: apsHeader.clusterId!, response: result, seqNum: apsPayload[0]});
782
770
  }
783
771
 
784
772
  // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
@@ -811,7 +799,10 @@ export class ZoHAdapter extends Adapter {
811
799
  destinationEndpoint: apsHeader.destEndpoint!,
812
800
  };
813
801
 
814
- this.zclWaitress.resolve(payload);
802
+ if (payload.header !== undefined) {
803
+ this.zclWaitress.resolve(payload as ZclWaitressPayload);
804
+ }
805
+
815
806
  this.emit("zclPayload", payload);
816
807
  }
817
808
  }
@@ -879,7 +870,10 @@ export class ZoHAdapter extends Adapter {
879
870
  destinationEndpoint: ZSpec.GP_ENDPOINT,
880
871
  };
881
872
 
882
- this.zclWaitress.resolve(zclPayload);
873
+ if (zclPayload.header !== undefined) {
874
+ this.zclWaitress.resolve(zclPayload as ZclWaitressPayload);
875
+ }
876
+
883
877
  this.emit("zclPayload", zclPayload);
884
878
  }
885
879
 
@@ -907,25 +901,8 @@ export class ZoHAdapter extends Adapter {
907
901
  onDeviceAuthorized(_source16: number, _source64: bigint): void {}
908
902
  /* v8 ignore stop */
909
903
 
910
- private waitressTimeoutFormatter(matcher: WaitressMatcher, timeout: number): string {
911
- return `Timeout after ${timeout}ms [sender=${matcher.sender} clusterId=${matcher.clusterId} cmdId=${matcher.commandId}]`;
912
- }
913
-
914
- private zclWaitressValidator(payload: ZclPayload, matcher: WaitressMatcher): boolean {
915
- return (
916
- // no sender in Touchlink
917
- (matcher.sender === undefined || payload.address === matcher.sender) &&
918
- payload.clusterID === matcher.clusterId &&
919
- payload.endpoint === matcher.endpoint &&
920
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
921
- payload.header!.commandIdentifier === matcher.commandId &&
922
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
923
- (matcher.transactionSequenceNumber === undefined || payload.header!.transactionSequenceNumber === matcher.transactionSequenceNumber)
924
- );
925
- }
926
-
927
- private zdoWaitressValidator(payload: ZdoResponse, matcher: WaitressMatcher): boolean {
928
- return matcher.sender === payload.sender && matcher.clusterId === payload.clusterId && matcher.transactionSequenceNumber === payload.seqNum;
904
+ private zdoWaitressValidator(payload: ZdoResponse, matcher: ClusterWaitressMatcher): boolean {
905
+ return matcher.address === payload.address && matcher.clusterId === payload.clusterId && matcher.transactionSequenceNumber === payload.seqNum;
929
906
  }
930
907
  // #endregion
931
908
  }
@@ -11,6 +11,7 @@ import type {Eui64} from "../zspec/tstypes";
11
11
  import * as Zcl from "../zspec/zcl";
12
12
  import type {TPartialClusterAttributes} from "../zspec/zcl/definition/clusters-types";
13
13
  import type {CustomClusters, FrameControl} from "../zspec/zcl/definition/tstype";
14
+ import {ZclStatusError} from "../zspec/zcl/zclStatusError";
14
15
  import * as Zdo from "../zspec/zdo";
15
16
  import type * as ZdoTypes from "../zspec/zdo/definition/tstypes";
16
17
  import Database from "./database";
@@ -305,7 +306,7 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
305
306
  zcl.manufacturerCode,
306
307
  0,
307
308
  zcl.commandKey,
308
- clusterKey ?? Zcl.Clusters.touchlink.ID,
309
+ clusterKey ?? "touchlink",
309
310
  zcl.payload,
310
311
  customClusters,
311
312
  );
@@ -735,6 +736,7 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
735
736
  this.selfAndDeviceEmit(device, "lastSeenChanged", {device, reason: "deviceAnnounce"});
736
737
  device.implicitCheckin();
737
738
  this.checkDeviceNetworkAddress(device, payload.eui64, payload.nwkAddress);
739
+ device.resetTransient(false);
738
740
  this.selfAndDeviceEmit(device, "deviceAnnounce", {device});
739
741
  }
740
742
 
@@ -883,10 +885,13 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
883
885
  undefined,
884
886
  this.database.id,
885
887
  );
888
+
889
+ device.resetTransient(true);
886
890
  this.selfAndDeviceEmit(device, "deviceJoined", {device});
887
891
  } else if (device.isDeleted) {
888
892
  logger.debug(`Deleted device '${payload.ieeeAddr}' joined, undeleting`, NS);
889
893
  device.undelete();
894
+ device.resetTransient(true);
890
895
  this.selfAndDeviceEmit(device, "deviceJoined", {device});
891
896
  }
892
897
 
@@ -950,6 +955,7 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
950
955
  private async onZclPayload(payload: AdapterEvents.ZclPayload): Promise<void> {
951
956
  let frame: Zcl.Frame | undefined;
952
957
  let device: Device | undefined;
958
+ let defaultResponse: Zcl.Status | undefined;
953
959
 
954
960
  if (payload.clusterID === Zcl.Clusters.touchlink.ID) {
955
961
  // This is handled by touchlink
@@ -1014,6 +1020,10 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
1014
1020
  frame = Zcl.Frame.fromBuffer(payload.clusterID, payload.header, payload.data, device ? device.customClusters : {});
1015
1021
  } catch (error) {
1016
1022
  logger.debug(`Failed to parse frame: ${error}`, NS);
1023
+
1024
+ if (error instanceof ZclStatusError) {
1025
+ defaultResponse = error.code;
1026
+ }
1017
1027
  }
1018
1028
  }
1019
1029
 
@@ -1151,7 +1161,7 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
1151
1161
  }
1152
1162
 
1153
1163
  if (frame) {
1154
- await device.onZclData(payload, frame, endpoint);
1164
+ await device.onZclData(payload, frame, endpoint, defaultResponse);
1155
1165
  }
1156
1166
  }
1157
1167
  }
@@ -107,6 +107,8 @@ interface GreenPowerEventMap {
107
107
  deviceLeave: [sourceID: number];
108
108
  }
109
109
 
110
+ const COMMISSIONING_NOTIFICATION_COMMAND_ID = Zcl.Clusters.greenPower.commands.commissioningNotification.ID;
111
+
110
112
  export class GreenPower extends EventEmitter<GreenPowerEventMap> {
111
113
  private adapter: Adapter;
112
114
  private databaseID: number;
@@ -219,7 +221,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
219
221
  undefined,
220
222
  zclTransactionSequenceNumber.next(),
221
223
  "pairing",
222
- Zcl.Clusters.greenPower.ID,
224
+ "greenPower",
223
225
  payload,
224
226
  {},
225
227
  );
@@ -248,7 +250,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
248
250
  try {
249
251
  // notification: A.3.3.4.1
250
252
  // commissioningNotification: A.3.3.4.3
251
- const isCommissioningNotification = frame.header.commandIdentifier === Zcl.Clusters.greenPower.commands.commissioningNotification.ID;
253
+ const isCommissioningNotification = frame.header.commandIdentifier === COMMISSIONING_NOTIFICATION_COMMAND_ID;
252
254
  const securityLevel = isCommissioningNotification ? (frame.payload.options >> 4) & 0x3 : (frame.payload.options >> 6) & 0x3;
253
255
 
254
256
  if (
@@ -380,7 +382,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
380
382
  undefined,
381
383
  zclTransactionSequenceNumber.next(),
382
384
  "response",
383
- Zcl.Clusters.greenPower.ID,
385
+ "greenPower",
384
386
  payloadResponse,
385
387
  {},
386
388
  );
@@ -522,7 +524,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
522
524
  undefined,
523
525
  zclTransactionSequenceNumber.next(),
524
526
  "response",
525
- Zcl.Clusters.greenPower.ID,
527
+ "greenPower",
526
528
  payload,
527
529
  {},
528
530
  );
@@ -535,10 +537,15 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
535
537
  logger.debug(`[APP_DESCRIPTION] ${logStr}`, NS);
536
538
  break;
537
539
  }
538
- case 0xa1: {
539
- // GP Manufacturer-specific Attribute Reporting
540
- break;
541
- }
540
+ case 0xa0: // Attribute Reporting
541
+ case 0xa1: // Manufacturer-Specific Attribute Reporting
542
+ case 0xa2: // Multi-Cluster Reporting
543
+ case 0xa3: // Manufacturer-specific Multi-Cluster Reporting
544
+ case 0xa4: // Request Attributes => TODO: handle response
545
+ case 0xa5: // Read Attributes Response
546
+ {
547
+ break;
548
+ }
542
549
  /* v8 ignore stop */
543
550
  default: {
544
551
  // NOTE: this is spammy because it logs everything that is handed back to Controller without special processing here
@@ -594,7 +601,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
594
601
  undefined,
595
602
  zclTransactionSequenceNumber.next(),
596
603
  "commisioningMode",
597
- Zcl.Clusters.greenPower.ID,
604
+ "greenPower",
598
605
  payload,
599
606
  {},
600
607
  );
@@ -6,7 +6,7 @@ import {performance} from "node:perf_hooks";
6
6
  import {logger} from "../../utils/logger";
7
7
  import * as Zcl from "../../zspec/zcl";
8
8
  import type {TClusterCommandPayload, TClusterPayload} from "../../zspec/zcl/definition/clusters-types";
9
- import type {TZclFrame} from "../../zspec/zcl/zclFrame";
9
+ import type {TFoundationZclFrame, TZclFrame} from "../../zspec/zcl/zclFrame";
10
10
  import type Endpoint from "../model/endpoint";
11
11
  import type {OtaDataSettings, OtaImage, OtaImageElement, OtaImageHeader, OtaSource, ZigbeeOtaImageMeta} from "../tstype";
12
12
 
@@ -58,6 +58,7 @@ const ZIGBEE_OTA_PREVIOUS_URL = "https://raw.githubusercontent.com/Koenkk/zigbee
58
58
  const UPGRADE_END_REQUEST_ID = Zcl.Clusters.genOta.commands.upgradeEndRequest.ID;
59
59
  const IMAGE_BLOCK_REQUEST_ID = Zcl.Clusters.genOta.commands.imageBlockRequest.ID;
60
60
  const IMAGE_PAGE_REQUEST_ID = Zcl.Clusters.genOta.commands.imagePageRequest.ID;
61
+ const IMAGE_BLOCK_RESPONSE_ID = Zcl.Clusters.genOta.commandsResponse.imageBlockResponse.ID;
61
62
 
62
63
  /** uint32 LE */
63
64
  export const UPGRADE_FILE_IDENTIFIER = 0x0beef11e;
@@ -390,9 +391,9 @@ export class OtaSession {
390
391
  private readonly waitForOtaCommand: <Co extends string>(
391
392
  endpointId: number,
392
393
  commandId: number,
393
- transactionSequenceNumber: number | undefined,
394
+ defaultRspCommandId: number | undefined,
394
395
  timeout: number,
395
- ) => {promise: Promise<TZclFrame<"genOta", Co>>; cancel: () => void},
396
+ ) => {promise: Promise<TZclFrame<"genOta", Co> | TFoundationZclFrame<"defaultRsp">>; cancel: () => void},
396
397
  ) {
397
398
  this.#startTime = performance.now();
398
399
 
@@ -445,12 +446,18 @@ export class OtaSession {
445
446
  );
446
447
  }
447
448
 
448
- public async run(): Promise<TZclFrame<"genOta", "upgradeEndRequest">> {
449
+ public async run(abortSignal: AbortSignal): Promise<TZclFrame<"genOta", "upgradeEndRequest"> | TFoundationZclFrame<"defaultRsp">> {
449
450
  // can take a long time, use max (int32 - 1), ~24 days
450
- const upgradeEndRequest = this.waitForOtaCommand<"upgradeEndRequest">(this.endpoint.ID, UPGRADE_END_REQUEST_ID, undefined, 2147483647);
451
+ // never match on defaultRsp
452
+ const upgradeEndRequest = this.waitForOtaCommand<"upgradeEndRequest">(this.endpoint.ID, UPGRADE_END_REQUEST_ID, -1, 2147483647);
451
453
 
452
454
  try {
453
455
  for await (const request of this.commandStream(upgradeEndRequest)) {
456
+ if (request.header.isGlobal) {
457
+ // ignore default responses, device should continue requesting blocks
458
+ continue;
459
+ }
460
+
454
461
  if (request.command.ID === UPGRADE_END_REQUEST_ID) {
455
462
  return request as TZclFrame<"genOta", "upgradeEndRequest">;
456
463
  }
@@ -465,7 +472,10 @@ export class OtaSession {
465
472
  request.header.transactionSequenceNumber,
466
473
  pageOffset,
467
474
  pagePayload.pageSize,
475
+ abortSignal.aborted,
468
476
  );
477
+
478
+ abortSignal.throwIfAborted();
469
479
  }
470
480
  } else {
471
481
  await this.sendImageBlockResponse(
@@ -473,7 +483,9 @@ export class OtaSession {
473
483
  request.header.transactionSequenceNumber,
474
484
  0,
475
485
  0,
486
+ abortSignal.aborted,
476
487
  );
488
+ abortSignal.throwIfAborted();
477
489
  }
478
490
  /* v8 ignore start */
479
491
  }
@@ -489,27 +501,30 @@ export class OtaSession {
489
501
  upgradeEndRequest.cancel();
490
502
 
491
503
  const err = error as Error;
492
- err.message = `Device ${this.ieeeAddr} did not start/finish firmware download after being notified. (${err.message})`;
493
504
 
494
- throw err;
505
+ if (err.name === "AbortError") {
506
+ throw new Error(`OTA for device ${this.ieeeAddr} was aborted`);
507
+ }
508
+
509
+ throw new Error(`Device ${this.ieeeAddr} did not start/finish firmware download after being notified. (${err.message})`);
495
510
  }
496
511
  }
497
512
 
498
513
  private async *commandStream(upgradeEndRequest: {
499
- promise: Promise<TZclFrame<"genOta", "upgradeEndRequest">>;
514
+ promise: Promise<TZclFrame<"genOta", "upgradeEndRequest"> | TFoundationZclFrame<"defaultRsp">>;
500
515
  cancel: () => void;
501
- }): AsyncGenerator<OtaDataRequest | OtaUpgradeEndRequest> {
516
+ }): AsyncGenerator<OtaDataRequest | OtaUpgradeEndRequest | TFoundationZclFrame<"defaultRsp">> {
502
517
  while (true) {
503
518
  const imageBlockRequest = this.waitForOtaCommand<"imageBlockRequest">(
504
519
  this.endpoint.ID,
505
520
  IMAGE_BLOCK_REQUEST_ID,
506
- undefined,
521
+ IMAGE_BLOCK_RESPONSE_ID,
507
522
  this.dataSettings.requestTimeout,
508
523
  );
509
524
  const imagePageRequest = this.waitForOtaCommand<"imagePageRequest">(
510
525
  this.endpoint.ID,
511
526
  IMAGE_PAGE_REQUEST_ID,
512
- undefined,
527
+ IMAGE_BLOCK_RESPONSE_ID,
513
528
  this.dataSettings.requestTimeout,
514
529
  );
515
530
  const dataRequest = Promise.race([imageBlockRequest.promise, imagePageRequest.promise]);
@@ -528,6 +543,7 @@ export class OtaSession {
528
543
  requestTsn: number,
529
544
  pageOffset: number,
530
545
  pageSize: number,
546
+ abort: boolean,
531
547
  ): Promise<number> {
532
548
  // throttle if needed
533
549
  let callNow = performance.now();
@@ -543,6 +559,16 @@ export class OtaSession {
543
559
 
544
560
  this.#lastBlockResponseTime = callNow;
545
561
 
562
+ if (abort) {
563
+ try {
564
+ await this.endpoint.commandResponse("genOta", "imageBlockResponse", {status: Zcl.Status.ABORT}, undefined, requestTsn);
565
+ } catch (error) {
566
+ logger.debug(() => `Abort image block response failed for ${this.ieeeAddr}: ${(error as Error).message}`, NS);
567
+ }
568
+
569
+ return 0;
570
+ }
571
+
546
572
  try {
547
573
  const blockPayload = buildImageBlockPayload(this.image, requestPayload, pageOffset, pageSize, this.dataSettings.baseSize);
548
574
 
@@ -1,23 +1,17 @@
1
1
  import {logger} from "../../utils/logger";
2
2
  import * as Zcl from "../../zspec/zcl";
3
3
  import type {TFoundation} from "../../zspec/zcl/definition/clusters-types";
4
- import type {Cluster, CustomClusters} from "../../zspec/zcl/definition/tstype";
4
+ import type {CustomClusters} from "../../zspec/zcl/definition/tstype";
5
5
  import type {ClusterOrRawWriteAttributes, TCustomCluster} from "../tstype";
6
6
 
7
7
  const NS = "zh:controller:zcl";
8
8
 
9
- // Legrand devices (e.g. 4129) fail to set the manufacturerSpecific flag and
10
- // manufacturerCode in the frame header, despite using specific attributes.
11
- // This leads to incorrect reported attribute names.
12
- // Remap the attributes using the target device's manufacturer ID
13
- // if the header is lacking the information.
14
- function getCluster(frame: Zcl.Frame, deviceManufacturerID: number | undefined, customClusters: CustomClusters): Cluster {
15
- let cluster = frame.cluster;
16
- if (!frame?.header?.manufacturerCode && frame?.cluster && deviceManufacturerID === Zcl.ManufacturerCode.LEGRAND_GROUP) {
17
- cluster = Zcl.Utils.getCluster(frame.cluster.ID, deviceManufacturerID, customClusters);
18
- }
19
- return cluster;
20
- }
9
+ const LEGRAND_GROUP_MANUF_CODE = Zcl.ManufacturerCode.LEGRAND_GROUP;
10
+
11
+ // NOTE: `legrandWorkaround`:
12
+ // Legrand devices fail to set the manufacturerSpecific flag and manufacturerCode in the frame header, despite using specific attributes.
13
+ // This leads to incorrect reported attribute names.
14
+ // Remap the attributes using the target device's manufacturer ID if the header is lacking the information.
21
15
 
22
16
  function attributeKeyValue<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
23
17
  frame: Zcl.Frame,
@@ -25,11 +19,18 @@ function attributeKeyValue<Cl extends number | string, Custom extends TCustomClu
25
19
  customClusters: CustomClusters,
26
20
  ): ClusterOrRawWriteAttributes<Cl, Custom> {
27
21
  const payload: Record<string | number, unknown> = {};
28
- const cluster = getCluster(frame, deviceManufacturerID, customClusters);
22
+ const legrandWorkaround = frame.header?.manufacturerCode === undefined && deviceManufacturerID === LEGRAND_GROUP_MANUF_CODE;
23
+ const cluster = legrandWorkaround ? Zcl.Utils.getCluster(frame.cluster.name, deviceManufacturerID, customClusters) : frame.cluster;
24
+ const manufacturerCode = legrandWorkaround ? deviceManufacturerID : frame.header.manufacturerCode;
29
25
 
30
26
  // TODO: remove this type once Zcl.Frame is typed
31
27
  for (const item of frame.payload as TFoundation["report" | "write" | "readRsp"]) {
32
- const attribute = cluster.getAttribute(item.attrId);
28
+ // Per ZCL spec, non-success `readRsp` records carry no value; don't synthesize one.
29
+ if ("status" in item && item.status !== Zcl.Status.SUCCESS) {
30
+ continue;
31
+ }
32
+
33
+ const attribute = Zcl.Utils.getClusterAttribute(cluster, item.attrId, manufacturerCode);
33
34
 
34
35
  if (attribute) {
35
36
  try {
@@ -49,11 +50,13 @@ function attributeKeyValue<Cl extends number | string, Custom extends TCustomClu
49
50
 
50
51
  function attributeList(frame: Zcl.Frame, deviceManufacturerID: number | undefined, customClusters: CustomClusters): Array<string | number> {
51
52
  const payload: Array<string | number> = [];
52
- const cluster = getCluster(frame, deviceManufacturerID, customClusters);
53
+ const legrandWorkaround = frame.header?.manufacturerCode === undefined && deviceManufacturerID === LEGRAND_GROUP_MANUF_CODE;
54
+ const cluster = legrandWorkaround ? Zcl.Utils.getCluster(frame.cluster.name, deviceManufacturerID, customClusters) : frame.cluster;
55
+ const manufacturerCode = legrandWorkaround ? deviceManufacturerID : frame.header.manufacturerCode;
53
56
 
54
57
  // TODO: remove this type once Zcl.Frame is typed
55
58
  for (const item of frame.payload as TFoundation["read"]) {
56
- const attribute = cluster.getAttribute(item.attrId);
59
+ const attribute = Zcl.Utils.getClusterAttribute(cluster, item.attrId, manufacturerCode);
57
60
 
58
61
  payload.push(attribute?.name ?? item.attrId);
59
62
  }