@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
@@ -59,7 +59,7 @@ export class AdapterBackup {
59
59
 
60
60
  /* get adapter ieee address */
61
61
  const ieeeAddressResponse = await this.znp.requestWithReply(Subsystem.SYS, "getExtAddr", {});
62
- if (!ieeeAddressResponse.payload.extaddress || !ieeeAddressResponse.payload.extaddress.startsWith("0x")) {
62
+ if (!ieeeAddressResponse.payload.extaddress?.startsWith("0x")) {
63
63
  throw new Error("Failed to read adapter IEEE address");
64
64
  }
65
65
  const ieeeAddress = Buffer.from(ieeeAddressResponse.payload.extaddress.split("0x")[1], "hex");
@@ -151,7 +151,7 @@ export class AdapterNvMemory {
151
151
  public async updateItem(id: NvItemsIds, data: Buffer, autoInit = true): Promise<void> {
152
152
  this.checkMemoryAlignmentSetup();
153
153
  const current = await this.readItem(id);
154
- if (!current || !current.equals(data)) {
154
+ if (!current?.equals(data)) {
155
155
  await this.writeItem(id, data, 0, autoInit);
156
156
  }
157
157
  }
@@ -150,7 +150,11 @@ export class ZnpAdapterManager {
150
150
  /* exception for some adapters which may actually use 0xdddddddddddddddd as EPID (backward compatibility) */
151
151
  /* v8 ignore next */
152
152
  this.nwkOptions.hasDefaultExtendedPanId) &&
153
- this.nwkOptions.networkKey.equals(preconfiguredKey.key) &&
153
+ /* On Z-Stack 3.x, the preconfigured (transport) key in NVRAM may retain a default
154
+ * firmware value that does not match the configured network key, even though the
155
+ * active and alternate keys are correct. Only require preconfiguredKey match on
156
+ * Z-Stack 1.2 where it serves as the active key (see lines 128-133 above). */
157
+ (this.options.version !== ZnpVersion.ZStack12 || this.nwkOptions.networkKey.equals(preconfiguredKey.key)) &&
154
158
  this.nwkOptions.networkKey.equals(activeKeyInfo.key) &&
155
159
  this.nwkOptions.networkKey.equals(alternateKeyInfo.key);
156
160
 
@@ -178,7 +182,7 @@ export class ZnpAdapterManager {
178
182
  };
179
183
 
180
184
  /* Determine startup strategy */
181
- if (!hasConfigured || !hasConfigured.isConfigured() || !nib) {
185
+ if (!hasConfigured?.isConfigured() || !nib) {
182
186
  /* Adapter is not configured or not commissioned */
183
187
  logger.debug("(stage-1) adapter is not configured / not commissioned", NS);
184
188
  if (configMatchesBackup) {
@@ -199,6 +203,16 @@ export class ZnpAdapterManager {
199
203
  logger.debug("(stage-1) adapter is configured", NS);
200
204
 
201
205
  if (configMatchesAdapter) {
206
+ /* Warn if preconfigured key doesn't match (Z-Stack 3.x only, non-fatal) */
207
+ if (this.options.version !== ZnpVersion.ZStack12 && !this.nwkOptions.networkKey.equals(preconfiguredKey.key)) {
208
+ logger.warning(
209
+ "Adapter preconfigured (transport) key does not match configured network key " +
210
+ `(preconfigured=${preconfiguredKey.key.toString("hex")}, configured=${this.nwkOptions.networkKey.toString("hex")}). ` +
211
+ "This is typically harmless on Z-Stack 3.x adapters where only the active key matters.",
212
+ NS,
213
+ );
214
+ }
215
+
202
216
  /* Warn if EPID is reversed (backward-compat) */
203
217
  if (isExtendedPanIdReversed) {
204
218
  logger.debug("(stage-2) extended pan id is reversed", NS);
@@ -3,4 +3,5 @@ enum ZnpVersion {
3
3
  ZStack3x0 = 1,
4
4
  ZStack30x = 2,
5
5
  }
6
+
6
7
  export {ZnpVersion};
@@ -1,7 +1,5 @@
1
1
  import assert from "node:assert";
2
-
3
2
  import debounce from "debounce";
4
-
5
3
  import type * as Models from "../../../models";
6
4
  import {Queue, Waitress, wait} from "../../../utils";
7
5
  import {logger} from "../../../utils/logger";
@@ -11,7 +9,7 @@ import type {Eui64} from "../../../zspec/tstypes";
11
9
  import * as Zcl from "../../../zspec/zcl";
12
10
  import * as Zdo from "../../../zspec/zdo";
13
11
  import type * as ZdoTypes from "../../../zspec/zdo/definition/tstypes";
14
- import Adapter from "../../adapter";
12
+ import Adapter, {type ClusterWaitressMatcher, type ZclWaitressPayload} from "../../adapter";
15
13
  import type * as Events from "../../events";
16
14
  import type {AdapterOptions, CoordinatorVersion, NetworkOptions, NetworkParameters, SerialPortOptions, StartResult} from "../../tstype";
17
15
  import * as Constants from "../constants";
@@ -31,16 +29,6 @@ const {ZnpCommandStatus, AddressMode} = Constants.COMMON;
31
29
 
32
30
  const DataConfirmTimeout = 9999; // Not an actual code
33
31
 
34
- interface WaitressMatcher {
35
- address?: number | string;
36
- endpoint: number;
37
- transactionSequenceNumber?: number;
38
- frameType: Zcl.FrameType;
39
- clusterID: number;
40
- commandIdentifier: number;
41
- direction: number;
42
- }
43
-
44
32
  class DataConfirmError extends Error {
45
33
  public code: number;
46
34
  constructor(code: number) {
@@ -72,7 +60,7 @@ export class ZStackAdapter extends Adapter {
72
60
  private supportsLED?: boolean;
73
61
  private interpanLock: boolean;
74
62
  private interpanEndpointRegistered: boolean;
75
- private waitress: Waitress<Events.ZclPayload, WaitressMatcher>;
63
+ private waitress: Waitress<ZclWaitressPayload, ClusterWaitressMatcher>;
76
64
  private konnextConfig: KonnextConfig;
77
65
 
78
66
  public constructor(networkOptions: NetworkOptions, serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions, konnextConfig: KonnextConfig) {
@@ -88,7 +76,7 @@ export class ZStackAdapter extends Adapter {
88
76
  this.interpanLock = false;
89
77
  this.interpanEndpointRegistered = false;
90
78
  this.closing = false;
91
- this.waitress = new Waitress<Events.ZclPayload, WaitressMatcher>(this.waitressValidator, this.waitressTimeoutFormatter);
79
+ this.waitress = new Waitress(Adapter.zclWaitressValidator, Adapter.clusterWaitressTimeoutFormatter);
92
80
 
93
81
  this.znp.on("received", this.onZnpRecieved.bind(this));
94
82
  this.znp.on("close", this.onZnpClose.bind(this));
@@ -530,22 +518,20 @@ export class ZStackAdapter extends Adapter {
530
518
  response = this.waitForInternal(
531
519
  networkAddress,
532
520
  endpoint,
533
- zclFrame.header.frameControl.frameType,
534
- Zcl.Direction.SERVER_TO_CLIENT,
535
521
  zclFrame.header.transactionSequenceNumber,
536
522
  zclFrame.cluster.ID,
537
523
  command.response,
524
+ undefined,
538
525
  timeout,
539
526
  );
540
527
  } else if (!zclFrame.header.frameControl.disableDefaultResponse) {
541
528
  response = this.waitForInternal(
542
529
  networkAddress,
543
530
  endpoint,
544
- Zcl.FrameType.GLOBAL,
545
- Zcl.Direction.SERVER_TO_CLIENT,
546
531
  zclFrame.header.transactionSequenceNumber,
547
532
  zclFrame.cluster.ID,
548
533
  Zcl.Foundation.defaultRsp.ID,
534
+ undefined,
549
535
  timeout,
550
536
  );
551
537
  }
@@ -978,7 +964,11 @@ export class ZStackAdapter extends Adapter {
978
964
  wasBroadcast: object.payload.wasbroadcast === 1,
979
965
  destinationEndpoint: object.payload.dstendpoint,
980
966
  };
981
- this.waitress.resolve(payload);
967
+
968
+ if (payload.header !== undefined) {
969
+ this.waitress.resolve(payload as ZclWaitressPayload);
970
+ }
971
+
982
972
  this.emit("zclPayload", payload);
983
973
  }
984
974
  })
@@ -999,7 +989,10 @@ export class ZStackAdapter extends Adapter {
999
989
  destinationEndpoint: object.payload.dstendpoint,
1000
990
  };
1001
991
 
1002
- this.waitress.resolve(payload);
992
+ if (payload.header !== undefined) {
993
+ this.waitress.resolve(payload as ZclWaitressPayload);
994
+ }
995
+
1003
996
  this.emit("zclPayload", payload);
1004
997
  }
1005
998
  }
@@ -1077,16 +1070,7 @@ export class ZStackAdapter extends Adapter {
1077
1070
  let response: ReturnType<typeof this.waitForInternal> | undefined;
1078
1071
 
1079
1072
  if (!disableResponse && command.response !== undefined) {
1080
- response = this.waitForInternal(
1081
- undefined,
1082
- 0xfe,
1083
- zclFrame.header.frameControl.frameType,
1084
- Zcl.Direction.SERVER_TO_CLIENT,
1085
- undefined,
1086
- zclFrame.cluster.ID,
1087
- command.response,
1088
- timeout,
1089
- );
1073
+ response = this.waitForInternal(undefined, 0xfe, undefined, zclFrame.cluster.ID, command.response, undefined, timeout);
1090
1074
  }
1091
1075
 
1092
1076
  try {
@@ -1125,22 +1109,13 @@ export class ZStackAdapter extends Adapter {
1125
1109
  private waitForInternal(
1126
1110
  networkAddress: number | undefined,
1127
1111
  endpoint: number,
1128
- frameType: Zcl.FrameType,
1129
- direction: Zcl.Direction,
1130
1112
  transactionSequenceNumber: number | undefined,
1131
- clusterID: number,
1132
- commandIdentifier: number,
1113
+ clusterId: number,
1114
+ commandId: number,
1115
+ defaultRspCommandId: number | undefined,
1133
1116
  timeout: number,
1134
1117
  ): {start: () => {promise: Promise<Events.ZclPayload>}; cancel: () => void} {
1135
- const payload = {
1136
- address: networkAddress,
1137
- endpoint,
1138
- clusterID,
1139
- commandIdentifier,
1140
- frameType,
1141
- direction,
1142
- transactionSequenceNumber,
1143
- };
1118
+ const payload = {address: networkAddress, endpoint, clusterId, commandId, defaultRspCommandId, transactionSequenceNumber};
1144
1119
 
1145
1120
  const waiter = this.waitress.waitFor(payload, timeout);
1146
1121
  const cancel = (): void => this.waitress.remove(waiter.ID);
@@ -1148,25 +1123,17 @@ export class ZStackAdapter extends Adapter {
1148
1123
  }
1149
1124
 
1150
1125
  public waitFor(
1151
- networkAddress: number | undefined,
1126
+ networkAddress: number,
1152
1127
  endpoint: number,
1153
- frameType: Zcl.FrameType,
1154
- direction: Zcl.Direction,
1128
+ _frameType: Zcl.FrameType,
1129
+ _direction: Zcl.Direction,
1155
1130
  transactionSequenceNumber: number | undefined,
1156
- clusterID: number,
1157
- commandIdentifier: number,
1131
+ clusterId: number,
1132
+ commandId: number,
1133
+ defaultRspCommandId: number | undefined,
1158
1134
  timeout: number,
1159
1135
  ): {promise: Promise<Events.ZclPayload>; cancel: () => void} {
1160
- const waiter = this.waitForInternal(
1161
- networkAddress,
1162
- endpoint,
1163
- frameType,
1164
- direction,
1165
- transactionSequenceNumber,
1166
- clusterID,
1167
- commandIdentifier,
1168
- timeout,
1169
- );
1136
+ const waiter = this.waitForInternal(networkAddress, endpoint, transactionSequenceNumber, clusterId, commandId, defaultRspCommandId, timeout);
1170
1137
 
1171
1138
  return {cancel: waiter.cancel, promise: waiter.start().promise};
1172
1139
  }
@@ -1186,6 +1153,13 @@ export class ZStackAdapter extends Adapter {
1186
1153
  const transactionID = this.nextTransactionID();
1187
1154
  const response = this.znp.waitFor(Type.AREQ, Subsystem.AF, "dataConfirm", undefined, transactionID, undefined, timeout);
1188
1155
 
1156
+ let options = 0;
1157
+
1158
+ // Zigbee Direct cluster, enable APS layer encryption
1159
+ if (clusterID === Zcl.Clusters.zigbeeDirectConfiguration.ID) {
1160
+ options |= Constants.AF.options.EN_SECURITY;
1161
+ }
1162
+
1189
1163
  await this.znp.request(
1190
1164
  Subsystem.AF,
1191
1165
  "dataRequest",
@@ -1195,7 +1169,7 @@ export class ZStackAdapter extends Adapter {
1195
1169
  srcendpoint: sourceEndpoint,
1196
1170
  clusterid: clusterID,
1197
1171
  transid: transactionID,
1198
- options: 0,
1172
+ options,
1199
1173
  radius: radius,
1200
1174
  len: data.length,
1201
1175
  data: data,
@@ -1301,27 +1275,6 @@ export class ZStackAdapter extends Adapter {
1301
1275
  return typeof address === "number" ? `0x${address.toString(16).padStart(16, "0")}` : address.toString();
1302
1276
  }
1303
1277
 
1304
- private waitressTimeoutFormatter(matcher: WaitressMatcher, timeout: number): string {
1305
- return (
1306
- `Timeout - ${matcher.address} - ${matcher.endpoint}` +
1307
- ` - ${matcher.transactionSequenceNumber} - ${matcher.clusterID}` +
1308
- ` - ${matcher.commandIdentifier} after ${timeout}ms`
1309
- );
1310
- }
1311
-
1312
- private waitressValidator(payload: Events.ZclPayload, matcher: WaitressMatcher): boolean {
1313
- return Boolean(
1314
- payload.header &&
1315
- (!matcher.address || payload.address === matcher.address) &&
1316
- payload.endpoint === matcher.endpoint &&
1317
- (matcher.transactionSequenceNumber === undefined || payload.header.transactionSequenceNumber === matcher.transactionSequenceNumber) &&
1318
- payload.clusterID === matcher.clusterID &&
1319
- matcher.frameType === payload.header.frameControl.frameType &&
1320
- matcher.commandIdentifier === payload.header.commandIdentifier &&
1321
- matcher.direction === payload.header.frameControl.direction,
1322
- );
1323
- }
1324
-
1325
1278
  private checkInterpanLock(): void {
1326
1279
  if (this.interpanLock) {
1327
1280
  throw new Error("Cannot execute command, in Inter-PAN mode");
@@ -8,4 +8,4 @@ import UTIL from "./util";
8
8
  import * as Utils from "./utils";
9
9
  import ZDO from "./zdo";
10
10
 
11
- export {AF, common as COMMON, DBG, MAC, SAPI, SYS, UTIL, ZDO, Utils};
11
+ export {AF, common as COMMON, DBG, MAC, SAPI, SYS, UTIL, Utils, ZDO};
@@ -30,4 +30,4 @@ const PositionCmd1 = 3;
30
30
  const MinMessageLength = 5;
31
31
  const MaxDataSize = 250;
32
32
 
33
- export {Type, Subsystem, DataStart, SOF, PositionDataLength, MinMessageLength, PositionCmd0, PositionCmd1, MaxDataSize};
33
+ export {DataStart, MaxDataSize, MinMessageLength, PositionCmd0, PositionCmd1, PositionDataLength, SOF, Subsystem, Type};
@@ -8,35 +8,23 @@ import * as ZSpec from "../../../zspec";
8
8
  import * as Zcl from "../../../zspec/zcl";
9
9
  import * as Zdo from "../../../zspec/zdo";
10
10
  import type * as ZdoTypes from "../../../zspec/zdo/definition/tstypes";
11
- import {Adapter, type TsType} from "../..";
11
+ import {Adapter, type ClusterWaitressMatcher, type ZclWaitressPayload} from "../../adapter";
12
12
  import {WORKAROUND_JOIN_MANUF_IEEE_PREFIX_TO_CODE} from "../../const";
13
13
  import type {ZclPayload} from "../../events";
14
+ import type {AdapterOptions, CoordinatorVersion, NetworkOptions, NetworkParameters, SerialPortOptions, StartResult} from "../../tstype";
14
15
  import {ZBOSSDriver} from "../driver";
15
16
  import {CommandId, DeviceUpdateStatus} from "../enums";
16
17
  import {FrameType, type ZBOSSFrame} from "../frame";
17
18
 
18
19
  const NS = "zh:zboss";
19
20
 
20
- interface WaitressMatcher {
21
- address: number | string;
22
- endpoint: number;
23
- transactionSequenceNumber?: number;
24
- clusterID: number;
25
- commandIdentifier: number;
26
- }
27
-
28
21
  export class ZBOSSAdapter extends Adapter {
29
22
  private queue: Queue;
30
23
  private readonly driver: ZBOSSDriver;
31
- private waitress: Waitress<ZclPayload, WaitressMatcher>;
24
+ private waitress: Waitress<ZclWaitressPayload, ClusterWaitressMatcher>;
32
25
  private currentManufacturerCode: Zcl.ManufacturerCode;
33
26
 
34
- constructor(
35
- networkOptions: TsType.NetworkOptions,
36
- serialPortOptions: TsType.SerialPortOptions,
37
- backupPath: string,
38
- adapterOptions: TsType.AdapterOptions,
39
- ) {
27
+ constructor(networkOptions: NetworkOptions, serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions) {
40
28
  super(networkOptions, serialPortOptions, backupPath, adapterOptions);
41
29
  this.hasZdoMessageOverhead = false;
42
30
  this.manufacturerID = Zcl.ManufacturerCode.NORDIC_SEMICONDUCTOR_ASA;
@@ -45,7 +33,7 @@ export class ZBOSSAdapter extends Adapter {
45
33
  logger.debug(`Adapter concurrent: ${concurrent}`, NS);
46
34
  this.queue = new Queue(concurrent);
47
35
 
48
- this.waitress = new Waitress<ZclPayload, WaitressMatcher>(this.waitressValidator, this.waitressTimeoutFormatter);
36
+ this.waitress = new Waitress(Adapter.zclWaitressValidator, Adapter.clusterWaitressTimeoutFormatter);
49
37
  this.driver = new ZBOSSDriver(serialPortOptions, networkOptions);
50
38
  this.driver.on("frame", this.processMessage.bind(this));
51
39
  }
@@ -107,7 +95,10 @@ export class ZBOSSAdapter extends Adapter {
107
95
  destinationEndpoint: frame.payload.dstEndpoint,
108
96
  };
109
97
 
110
- this.waitress.resolve(payload);
98
+ if (payload.header !== undefined) {
99
+ this.waitress.resolve(payload as ZclWaitressPayload);
100
+ }
101
+
111
102
  this.emit("zclPayload", payload);
112
103
  break;
113
104
  }
@@ -115,7 +106,7 @@ export class ZBOSSAdapter extends Adapter {
115
106
  }
116
107
  }
117
108
 
118
- public async start(): Promise<TsType.StartResult> {
109
+ public async start(): Promise<StartResult> {
119
110
  logger.info("ZBOSS Adapter starting", NS);
120
111
 
121
112
  await this.driver.connect();
@@ -133,8 +124,8 @@ export class ZBOSSAdapter extends Adapter {
133
124
  return await Promise.resolve(this.driver.netInfo.ieeeAddr);
134
125
  }
135
126
 
136
- public async getCoordinatorVersion(): Promise<TsType.CoordinatorVersion> {
137
- return await this.queue.execute<TsType.CoordinatorVersion>(async () => {
127
+ public async getCoordinatorVersion(): Promise<CoordinatorVersion> {
128
+ return await this.queue.execute<CoordinatorVersion>(async () => {
138
129
  const ver = await this.driver.execCommand(CommandId.GET_MODULE_VERSION, {});
139
130
  const cver = await this.driver.execCommand(CommandId.GET_COORDINATOR_VERSION, {});
140
131
  const ver2str = (version: number): string => {
@@ -169,7 +160,7 @@ export class ZBOSSAdapter extends Adapter {
169
160
  return await Promise.reject(new Error("This adapter does not support backup"));
170
161
  }
171
162
 
172
- public async getNetworkParameters(): Promise<TsType.NetworkParameters> {
163
+ public async getNetworkParameters(): Promise<NetworkParameters> {
173
164
  return await this.queue.execute(async () => {
174
165
  // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
175
166
  const channel = this.driver.netInfo!.network.channel;
@@ -362,6 +353,7 @@ export class ZBOSSAdapter extends Adapter {
362
353
  zclFrame.cluster.ID,
363
354
  // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
364
355
  command.response!,
356
+ undefined,
365
357
  timeout,
366
358
  );
367
359
  } else if (!zclFrame.header.frameControl.disableDefaultResponse) {
@@ -371,6 +363,7 @@ export class ZBOSSAdapter extends Adapter {
371
363
  zclFrame.header.transactionSequenceNumber,
372
364
  zclFrame.cluster.ID,
373
365
  Zcl.Foundation.defaultRsp.ID,
366
+ undefined,
374
367
  timeout,
375
368
  );
376
369
  }
@@ -480,18 +473,13 @@ export class ZBOSSAdapter extends Adapter {
480
473
  networkAddress: number,
481
474
  endpoint: number,
482
475
  transactionSequenceNumber: number | undefined,
483
- clusterID: number,
484
- commandIdentifier: number,
476
+ clusterId: number,
477
+ commandId: number,
478
+ defaultRspCommandId: number | undefined,
485
479
  timeout: number,
486
480
  ): {start: () => {promise: Promise<ZclPayload>}; cancel: () => void} {
487
481
  const waiter = this.waitress.waitFor(
488
- {
489
- address: networkAddress,
490
- endpoint,
491
- clusterID,
492
- commandIdentifier,
493
- transactionSequenceNumber,
494
- },
482
+ {address: networkAddress, endpoint, clusterId, commandId, defaultRspCommandId, transactionSequenceNumber},
495
483
  timeout,
496
484
  );
497
485
  const cancel = (): void => this.waitress.remove(waiter.ID);
@@ -504,32 +492,13 @@ export class ZBOSSAdapter extends Adapter {
504
492
  _frameType: Zcl.FrameType,
505
493
  _direction: Zcl.Direction,
506
494
  transactionSequenceNumber: number | undefined,
507
- clusterID: number,
508
- commandIdentifier: number,
495
+ clusterId: number,
496
+ commandId: number,
497
+ defaultRspCommandId: number | undefined,
509
498
  timeout: number,
510
499
  ): {promise: Promise<ZclPayload>; cancel: () => void} {
511
- const waiter = this.waitForInternal(networkAddress, endpoint, transactionSequenceNumber, clusterID, commandIdentifier, timeout);
500
+ const waiter = this.waitForInternal(networkAddress, endpoint, transactionSequenceNumber, clusterId, commandId, defaultRspCommandId, timeout);
512
501
 
513
502
  return {cancel: waiter.cancel, promise: waiter.start().promise};
514
503
  }
515
-
516
- private waitressTimeoutFormatter(matcher: WaitressMatcher, timeout: number): string {
517
- return (
518
- `Timeout - ${matcher.address} - ${matcher.endpoint}` +
519
- ` - ${matcher.transactionSequenceNumber} - ${matcher.clusterID}` +
520
- ` - ${matcher.commandIdentifier} after ${timeout}ms`
521
- );
522
- }
523
-
524
- private waitressValidator(payload: ZclPayload, matcher: WaitressMatcher): boolean {
525
- return (
526
- (payload.header &&
527
- (!matcher.address || payload.address === matcher.address) &&
528
- payload.endpoint === matcher.endpoint &&
529
- (matcher.transactionSequenceNumber === undefined || payload.header.transactionSequenceNumber === matcher.transactionSequenceNumber) &&
530
- payload.clusterID === matcher.clusterID &&
531
- matcher.commandIdentifier === payload.header.commandIdentifier) ||
532
- false
533
- );
534
- }
535
504
  }
@@ -8,6 +8,7 @@ import {Waitress} from "../../utils";
8
8
  import {AsyncMutex} from "../../utils/async-mutex";
9
9
  import {logger} from "../../utils/logger";
10
10
  import type * as ZSpec from "../../zspec";
11
+ import * as Zcl from "../../zspec/zcl";
11
12
  import * as Zdo from "../../zspec/zdo";
12
13
  import type {TsType} from "..";
13
14
  import {ZDO_REQ_CLUSTER_ID_TO_ZBOSS_COMMAND_ID} from "./commands";
@@ -307,6 +308,12 @@ export class ZBOSSDriver extends EventEmitter {
307
308
  }
308
309
 
309
310
  public async request(ieee: string, profileID: number, clusterID: number, dstEp: number, srcEp: number, data: Buffer): Promise<ZBOSSFrame> {
311
+ // Default APS options
312
+ let txOptions = 0x02; // ROUTE DISCOVERY
313
+ // Zigbee Direct cluster, enable APS layer encryption
314
+ if (clusterID === Zcl.Clusters.zigbeeDirectConfiguration.ID) {
315
+ txOptions |= 0x01;
316
+ }
310
317
  const payload = {
311
318
  paramLength: 21,
312
319
  dataLength: data.length,
@@ -317,7 +324,7 @@ export class ZBOSSDriver extends EventEmitter {
317
324
  srcEndpoint: srcEp,
318
325
  radius: 3,
319
326
  dstAddrMode: 3, // ADDRESS MODE ieee
320
- txOptions: 2, // ROUTE DISCOVERY
327
+ txOptions,
321
328
  useAlias: 0,
322
329
  aliasAddr: 0,
323
330
  aliasSequence: 0,
@@ -208,7 +208,20 @@ export class ZBOSSUart extends EventEmitter {
208
208
  }
209
209
 
210
210
  private async onPackage(data: Buffer): Promise<void> {
211
- if (this.inReset) return;
211
+ // Do not drop frames while `inReset` is set.
212
+ //
213
+ // `inReset` is set by `reset()` and only cleared by `onPortClose`
214
+ // (after a 3s wait + reopen). On some NCP transports the underlying
215
+ // port does not reliably close around `esp_restart()` (e.g. ESP32-C6
216
+ // USB-Serial-JTAG re-attaches the same CDC descriptor essentially
217
+ // instantly), so `onPortClose` never fires, `inReset` never clears,
218
+ // and the device's tsn-matching NCP_RESET response plus the
219
+ // post-reboot boot-ready frame both get silently dropped here.
220
+ // `Driver.execCommand(NCP_RESET, ...)` already uses an undefined-tsn
221
+ // waitress matcher, so either frame would resolve the pending
222
+ // promise if it reached the `"frame"` emitter. The CRC8/CRC16 checks below
223
+ // reject any garbage (ROM banner ASCII, partial frames, electrical
224
+ // noise) that legitimately arrives during the reset window.
212
225
  const len = data.readUInt16LE(0);
213
226
  const pType = data.readUInt8(2);
214
227
  const pFlags = data.readUInt8(3);
@@ -8,7 +8,7 @@ import type {BroadcastAddress} from "../../../zspec/enums";
8
8
  import * as Zcl from "../../../zspec/zcl";
9
9
  import * as Zdo from "../../../zspec/zdo";
10
10
  import type * as ZdoTypes from "../../../zspec/zdo/definition/tstypes";
11
- import Adapter from "../../adapter";
11
+ import Adapter, {type ClusterWaitressMatcher, type ZclWaitressPayload} from "../../adapter";
12
12
  import type * as Events from "../../events";
13
13
  import type * as TsType from "../../tstype";
14
14
  import type {RawAPSDataRequestPayload} from "../driver/commandType";
@@ -19,20 +19,11 @@ import {patchZdoBuffaloBE} from "./patchZdoBuffaloBE";
19
19
 
20
20
  const NS = "zh:zigate";
21
21
  const default_bind_group = 901; // https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/lib/constants.js#L3
22
- interface WaitressMatcher {
23
- address?: number | string;
24
- endpoint: number;
25
- transactionSequenceNumber?: number;
26
- frameType: Zcl.FrameType;
27
- clusterID: number;
28
- commandIdentifier: number;
29
- direction: number;
30
- }
31
22
 
32
23
  export class ZiGateAdapter extends Adapter {
33
24
  private driver: Driver;
34
25
  private joinPermitted: boolean;
35
- private waitress: Waitress<Events.ZclPayload, WaitressMatcher>;
26
+ private waitress: Waitress<ZclWaitressPayload, ClusterWaitressMatcher>;
36
27
  private closing: boolean;
37
28
  private queue: Queue;
38
29
 
@@ -54,7 +45,7 @@ export class ZiGateAdapter extends Adapter {
54
45
  this.queue = new Queue(concurrent);
55
46
  // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
56
47
  this.driver = new Driver(serialPortOptions.path!, serialPortOptions);
57
- this.waitress = new Waitress<Events.ZclPayload, WaitressMatcher>(this.waitressValidator, this.waitressTimeoutFormatter);
48
+ this.waitress = new Waitress(Adapter.zclWaitressValidator, Adapter.clusterWaitressTimeoutFormatter);
58
49
 
59
50
  this.driver.on("received", this.dataListener.bind(this));
60
51
  this.driver.on("LeaveIndication", this.leaveIndicationListener.bind(this));
@@ -357,6 +348,7 @@ export class ZiGateAdapter extends Adapter {
357
348
  zclFrame.header.transactionSequenceNumber,
358
349
  zclFrame.cluster.ID,
359
350
  command.response,
351
+ undefined,
360
352
  timeout,
361
353
  );
362
354
  } else if (!zclFrame.header.frameControl.disableDefaultResponse) {
@@ -368,6 +360,7 @@ export class ZiGateAdapter extends Adapter {
368
360
  zclFrame.header.transactionSequenceNumber,
369
361
  zclFrame.cluster.ID,
370
362
  Zcl.Foundation.defaultRsp.ID,
363
+ undefined,
371
364
  timeout,
372
365
  );
373
366
  }
@@ -518,24 +511,17 @@ export class ZiGateAdapter extends Adapter {
518
511
  }
519
512
 
520
513
  public waitFor(
521
- networkAddress: number | undefined,
514
+ networkAddress: number,
522
515
  endpoint: number,
523
- frameType: Zcl.FrameType,
524
- direction: Zcl.Direction,
516
+ _frameType: Zcl.FrameType,
517
+ _direction: Zcl.Direction,
525
518
  transactionSequenceNumber: number | undefined,
526
- clusterID: number,
527
- commandIdentifier: number,
519
+ clusterId: number,
520
+ commandId: number,
521
+ defaultRspCommandId: number | undefined,
528
522
  timeout: number,
529
523
  ): {promise: Promise<Events.ZclPayload>; cancel: () => void} {
530
- const payload = {
531
- address: networkAddress,
532
- endpoint,
533
- clusterID,
534
- commandIdentifier,
535
- frameType,
536
- direction,
537
- transactionSequenceNumber,
538
- };
524
+ const payload = {address: networkAddress, endpoint, clusterId, commandId, defaultRspCommandId, transactionSequenceNumber};
539
525
  const waiter = this.waitress.waitFor(payload, timeout);
540
526
  const cancel = (): void => this.waitress.remove(waiter.ID);
541
527
  return {promise: waiter.start().promise, cancel};
@@ -591,7 +577,11 @@ export class ZiGateAdapter extends Adapter {
591
577
  wasBroadcast: false, // TODO
592
578
  destinationEndpoint: <number>ziGateObject.payload.destinationEndpoint,
593
579
  };
594
- this.waitress.resolve(payload);
580
+
581
+ if (payload.header !== undefined) {
582
+ this.waitress.resolve(payload as ZclWaitressPayload);
583
+ }
584
+
595
585
  this.emit("zclPayload", payload);
596
586
  }
597
587
 
@@ -604,27 +594,6 @@ export class ZiGateAdapter extends Adapter {
604
594
  this.emit("deviceLeave", payload);
605
595
  }
606
596
 
607
- private waitressTimeoutFormatter(matcher: WaitressMatcher, timeout: number): string {
608
- return (
609
- `Timeout - ${matcher.address} - ${matcher.endpoint}` +
610
- ` - ${matcher.transactionSequenceNumber} - ${matcher.clusterID}` +
611
- ` - ${matcher.commandIdentifier} after ${timeout}ms`
612
- );
613
- }
614
-
615
- private waitressValidator(payload: Events.ZclPayload, matcher: WaitressMatcher): boolean {
616
- return Boolean(
617
- payload.header &&
618
- (!matcher.address || payload.address === matcher.address) &&
619
- matcher.endpoint === payload.endpoint &&
620
- (matcher.transactionSequenceNumber === undefined || payload.header.transactionSequenceNumber === matcher.transactionSequenceNumber) &&
621
- matcher.clusterID === payload.clusterID &&
622
- matcher.frameType === payload.header.frameControl.frameType &&
623
- matcher.commandIdentifier === payload.header.commandIdentifier &&
624
- matcher.direction === payload.header.frameControl.direction,
625
- );
626
- }
627
-
628
597
  private onZiGateClose(): void {
629
598
  if (!this.closing) {
630
599
  this.emit("disconnected");