zigbee-herdsman 6.0.2 → 6.0.3

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 (252) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +12 -3
  3. package/.github/ISSUE_TEMPLATE/config.yml +0 -5
  4. package/.github/dependabot.yml +0 -22
  5. package/.github/workflows/ci.yml +0 -69
  6. package/.github/workflows/release-please.yml +0 -18
  7. package/.github/workflows/stale.yml +0 -20
  8. package/.github/workflows/typedoc.yaml +0 -47
  9. package/.release-please-manifest.json +0 -3
  10. package/.vscode/extensions.json +0 -3
  11. package/.vscode/settings.json +0 -11
  12. package/biome.json +0 -98
  13. package/examples/join-and-log.js +0 -24
  14. package/release-please-config.json +0 -9
  15. package/src/adapter/adapter.ts +0 -189
  16. package/src/adapter/adapterDiscovery.ts +0 -666
  17. package/src/adapter/const.ts +0 -12
  18. package/src/adapter/deconz/adapter/deconzAdapter.ts +0 -877
  19. package/src/adapter/deconz/driver/constants.ts +0 -246
  20. package/src/adapter/deconz/driver/driver.ts +0 -1540
  21. package/src/adapter/deconz/driver/frame.ts +0 -11
  22. package/src/adapter/deconz/driver/frameParser.ts +0 -753
  23. package/src/adapter/deconz/driver/parser.ts +0 -45
  24. package/src/adapter/deconz/driver/writer.ts +0 -22
  25. package/src/adapter/deconz/types.d.ts +0 -13
  26. package/src/adapter/ember/adapter/emberAdapter.ts +0 -2265
  27. package/src/adapter/ember/adapter/endpoints.ts +0 -86
  28. package/src/adapter/ember/adapter/oneWaitress.ts +0 -324
  29. package/src/adapter/ember/adapter/tokensManager.ts +0 -782
  30. package/src/adapter/ember/consts.ts +0 -178
  31. package/src/adapter/ember/enums.ts +0 -1746
  32. package/src/adapter/ember/ezsp/buffalo.ts +0 -1392
  33. package/src/adapter/ember/ezsp/consts.ts +0 -148
  34. package/src/adapter/ember/ezsp/enums.ts +0 -1114
  35. package/src/adapter/ember/ezsp/ezsp.ts +0 -9061
  36. package/src/adapter/ember/ezspError.ts +0 -10
  37. package/src/adapter/ember/types.ts +0 -866
  38. package/src/adapter/ember/uart/ash.ts +0 -1960
  39. package/src/adapter/ember/uart/consts.ts +0 -109
  40. package/src/adapter/ember/uart/enums.ts +0 -192
  41. package/src/adapter/ember/uart/parser.ts +0 -48
  42. package/src/adapter/ember/uart/queues.ts +0 -247
  43. package/src/adapter/ember/uart/writer.ts +0 -53
  44. package/src/adapter/ember/utils/initters.ts +0 -58
  45. package/src/adapter/ember/utils/math.ts +0 -73
  46. package/src/adapter/events.ts +0 -21
  47. package/src/adapter/ezsp/adapter/backup.ts +0 -109
  48. package/src/adapter/ezsp/adapter/ezspAdapter.ts +0 -614
  49. package/src/adapter/ezsp/driver/commands.ts +0 -2497
  50. package/src/adapter/ezsp/driver/consts.ts +0 -11
  51. package/src/adapter/ezsp/driver/driver.ts +0 -1002
  52. package/src/adapter/ezsp/driver/ezsp.ts +0 -802
  53. package/src/adapter/ezsp/driver/frame.ts +0 -101
  54. package/src/adapter/ezsp/driver/index.ts +0 -4
  55. package/src/adapter/ezsp/driver/multicast.ts +0 -78
  56. package/src/adapter/ezsp/driver/parser.ts +0 -81
  57. package/src/adapter/ezsp/driver/types/basic.ts +0 -201
  58. package/src/adapter/ezsp/driver/types/index.ts +0 -239
  59. package/src/adapter/ezsp/driver/types/named.ts +0 -2330
  60. package/src/adapter/ezsp/driver/types/struct.ts +0 -844
  61. package/src/adapter/ezsp/driver/uart.ts +0 -460
  62. package/src/adapter/ezsp/driver/utils/crc16ccitt.ts +0 -44
  63. package/src/adapter/ezsp/driver/utils/index.ts +0 -32
  64. package/src/adapter/ezsp/driver/writer.ts +0 -64
  65. package/src/adapter/index.ts +0 -3
  66. package/src/adapter/serialPort.ts +0 -58
  67. package/src/adapter/socketPortUtils.ts +0 -16
  68. package/src/adapter/tstype.ts +0 -78
  69. package/src/adapter/z-stack/adapter/adapter-backup.ts +0 -519
  70. package/src/adapter/z-stack/adapter/adapter-nv-memory.ts +0 -457
  71. package/src/adapter/z-stack/adapter/endpoints.ts +0 -57
  72. package/src/adapter/z-stack/adapter/manager.ts +0 -543
  73. package/src/adapter/z-stack/adapter/tstype.ts +0 -6
  74. package/src/adapter/z-stack/adapter/zStackAdapter.ts +0 -1190
  75. package/src/adapter/z-stack/constants/af.ts +0 -27
  76. package/src/adapter/z-stack/constants/common.ts +0 -285
  77. package/src/adapter/z-stack/constants/dbg.ts +0 -23
  78. package/src/adapter/z-stack/constants/index.ts +0 -11
  79. package/src/adapter/z-stack/constants/mac.ts +0 -128
  80. package/src/adapter/z-stack/constants/sapi.ts +0 -25
  81. package/src/adapter/z-stack/constants/sys.ts +0 -72
  82. package/src/adapter/z-stack/constants/util.ts +0 -82
  83. package/src/adapter/z-stack/constants/utils.ts +0 -14
  84. package/src/adapter/z-stack/constants/zdo.ts +0 -103
  85. package/src/adapter/z-stack/models/startup-options.ts +0 -13
  86. package/src/adapter/z-stack/structs/entries/address-manager-entry.ts +0 -44
  87. package/src/adapter/z-stack/structs/entries/address-manager-table.ts +0 -19
  88. package/src/adapter/z-stack/structs/entries/aps-link-key-data-entry.ts +0 -12
  89. package/src/adapter/z-stack/structs/entries/aps-link-key-data-table.ts +0 -21
  90. package/src/adapter/z-stack/structs/entries/aps-tc-link-key-entry.ts +0 -19
  91. package/src/adapter/z-stack/structs/entries/aps-tc-link-key-table.ts +0 -21
  92. package/src/adapter/z-stack/structs/entries/channel-list.ts +0 -8
  93. package/src/adapter/z-stack/structs/entries/has-configured.ts +0 -16
  94. package/src/adapter/z-stack/structs/entries/index.ts +0 -16
  95. package/src/adapter/z-stack/structs/entries/nib.ts +0 -66
  96. package/src/adapter/z-stack/structs/entries/nwk-key-descriptor.ts +0 -15
  97. package/src/adapter/z-stack/structs/entries/nwk-key.ts +0 -13
  98. package/src/adapter/z-stack/structs/entries/nwk-pan-id.ts +0 -8
  99. package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.ts +0 -20
  100. package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.ts +0 -19
  101. package/src/adapter/z-stack/structs/entries/security-manager-entry.ts +0 -33
  102. package/src/adapter/z-stack/structs/entries/security-manager-table.ts +0 -22
  103. package/src/adapter/z-stack/structs/index.ts +0 -4
  104. package/src/adapter/z-stack/structs/serializable-memory-object.ts +0 -14
  105. package/src/adapter/z-stack/structs/struct.ts +0 -367
  106. package/src/adapter/z-stack/structs/table.ts +0 -198
  107. package/src/adapter/z-stack/unpi/constants.ts +0 -33
  108. package/src/adapter/z-stack/unpi/frame.ts +0 -62
  109. package/src/adapter/z-stack/unpi/index.ts +0 -4
  110. package/src/adapter/z-stack/unpi/parser.ts +0 -56
  111. package/src/adapter/z-stack/unpi/writer.ts +0 -21
  112. package/src/adapter/z-stack/utils/channel-list.ts +0 -40
  113. package/src/adapter/z-stack/utils/index.ts +0 -2
  114. package/src/adapter/z-stack/utils/network-options.ts +0 -26
  115. package/src/adapter/z-stack/znp/buffaloZnp.ts +0 -175
  116. package/src/adapter/z-stack/znp/definition.ts +0 -2713
  117. package/src/adapter/z-stack/znp/index.ts +0 -2
  118. package/src/adapter/z-stack/znp/parameterType.ts +0 -22
  119. package/src/adapter/z-stack/znp/tstype.ts +0 -44
  120. package/src/adapter/z-stack/znp/utils.ts +0 -10
  121. package/src/adapter/z-stack/znp/znp.ts +0 -342
  122. package/src/adapter/z-stack/znp/zpiObject.ts +0 -148
  123. package/src/adapter/zboss/adapter/zbossAdapter.ts +0 -526
  124. package/src/adapter/zboss/commands.ts +0 -1184
  125. package/src/adapter/zboss/consts.ts +0 -9
  126. package/src/adapter/zboss/driver.ts +0 -422
  127. package/src/adapter/zboss/enums.ts +0 -360
  128. package/src/adapter/zboss/frame.ts +0 -227
  129. package/src/adapter/zboss/reader.ts +0 -65
  130. package/src/adapter/zboss/types.ts +0 -0
  131. package/src/adapter/zboss/uart.ts +0 -428
  132. package/src/adapter/zboss/utils.ts +0 -58
  133. package/src/adapter/zboss/writer.ts +0 -49
  134. package/src/adapter/zigate/adapter/patchZdoBuffaloBE.ts +0 -27
  135. package/src/adapter/zigate/adapter/zigateAdapter.ts +0 -618
  136. package/src/adapter/zigate/driver/LICENSE +0 -17
  137. package/src/adapter/zigate/driver/buffaloZiGate.ts +0 -212
  138. package/src/adapter/zigate/driver/commandType.ts +0 -418
  139. package/src/adapter/zigate/driver/constants.ts +0 -150
  140. package/src/adapter/zigate/driver/frame.ts +0 -197
  141. package/src/adapter/zigate/driver/messageType.ts +0 -287
  142. package/src/adapter/zigate/driver/parameterType.ts +0 -32
  143. package/src/adapter/zigate/driver/ziGateObject.ts +0 -146
  144. package/src/adapter/zigate/driver/zigate.ts +0 -423
  145. package/src/adapter/zoh/adapter/utils.ts +0 -27
  146. package/src/adapter/zoh/adapter/zohAdapter.ts +0 -838
  147. package/src/buffalo/buffalo.ts +0 -342
  148. package/src/buffalo/index.ts +0 -1
  149. package/src/controller/controller.ts +0 -1022
  150. package/src/controller/database.ts +0 -124
  151. package/src/controller/events.ts +0 -52
  152. package/src/controller/greenPower.ts +0 -603
  153. package/src/controller/helpers/index.ts +0 -1
  154. package/src/controller/helpers/installCodes.ts +0 -107
  155. package/src/controller/helpers/request.ts +0 -96
  156. package/src/controller/helpers/requestQueue.ts +0 -125
  157. package/src/controller/helpers/zclFrameConverter.ts +0 -47
  158. package/src/controller/helpers/zclTransactionSequenceNumber.ts +0 -19
  159. package/src/controller/index.ts +0 -6
  160. package/src/controller/model/device.ts +0 -1249
  161. package/src/controller/model/endpoint.ts +0 -1105
  162. package/src/controller/model/entity.ts +0 -23
  163. package/src/controller/model/group.ts +0 -424
  164. package/src/controller/model/index.ts +0 -5
  165. package/src/controller/model/zigbeeEntity.ts +0 -30
  166. package/src/controller/touchlink.ts +0 -189
  167. package/src/controller/tstype.ts +0 -274
  168. package/src/index.ts +0 -12
  169. package/src/models/backup-storage-legacy.ts +0 -48
  170. package/src/models/backup-storage-unified.ts +0 -47
  171. package/src/models/backup.ts +0 -37
  172. package/src/models/index.ts +0 -5
  173. package/src/models/network-options.ts +0 -11
  174. package/src/utils/backup.ts +0 -152
  175. package/src/utils/index.ts +0 -5
  176. package/src/utils/logger.ts +0 -20
  177. package/src/utils/patchBigIntSerialization.ts +0 -8
  178. package/src/utils/queue.ts +0 -76
  179. package/src/utils/types.d.ts +0 -3
  180. package/src/utils/utils.ts +0 -19
  181. package/src/utils/wait.ts +0 -5
  182. package/src/utils/waitress.ts +0 -96
  183. package/src/zspec/consts.ts +0 -84
  184. package/src/zspec/enums.ts +0 -22
  185. package/src/zspec/index.ts +0 -3
  186. package/src/zspec/tstypes.ts +0 -18
  187. package/src/zspec/utils.ts +0 -247
  188. package/src/zspec/zcl/buffaloZcl.ts +0 -1220
  189. package/src/zspec/zcl/definition/cluster.ts +0 -5915
  190. package/src/zspec/zcl/definition/clusters-typegen.ts +0 -588
  191. package/src/zspec/zcl/definition/clusters-types.ts +0 -7331
  192. package/src/zspec/zcl/definition/consts.ts +0 -24
  193. package/src/zspec/zcl/definition/enums.ts +0 -203
  194. package/src/zspec/zcl/definition/foundation.ts +0 -329
  195. package/src/zspec/zcl/definition/manufacturerCode.ts +0 -729
  196. package/src/zspec/zcl/definition/status.ts +0 -69
  197. package/src/zspec/zcl/definition/tstype.ts +0 -377
  198. package/src/zspec/zcl/index.ts +0 -11
  199. package/src/zspec/zcl/utils.ts +0 -321
  200. package/src/zspec/zcl/zclFrame.ts +0 -356
  201. package/src/zspec/zcl/zclHeader.ts +0 -102
  202. package/src/zspec/zcl/zclStatusError.ts +0 -10
  203. package/src/zspec/zdo/buffaloZdo.ts +0 -2336
  204. package/src/zspec/zdo/definition/clusters.ts +0 -722
  205. package/src/zspec/zdo/definition/consts.ts +0 -16
  206. package/src/zspec/zdo/definition/enums.ts +0 -99
  207. package/src/zspec/zdo/definition/status.ts +0 -105
  208. package/src/zspec/zdo/definition/tstypes.ts +0 -1062
  209. package/src/zspec/zdo/index.ts +0 -7
  210. package/src/zspec/zdo/utils.ts +0 -76
  211. package/src/zspec/zdo/zdoStatusError.ts +0 -10
  212. package/test/adapter/adapter.test.ts +0 -1062
  213. package/test/adapter/ember/ash.test.ts +0 -337
  214. package/test/adapter/ember/consts.ts +0 -131
  215. package/test/adapter/ember/emberAdapter.test.ts +0 -3449
  216. package/test/adapter/ember/ezsp.test.ts +0 -385
  217. package/test/adapter/ember/ezspBuffalo.test.ts +0 -93
  218. package/test/adapter/ember/ezspError.test.ts +0 -12
  219. package/test/adapter/ember/math.test.ts +0 -206
  220. package/test/adapter/ezsp/frame.test.ts +0 -30
  221. package/test/adapter/ezsp/uart.test.ts +0 -181
  222. package/test/adapter/z-stack/adapter.test.ts +0 -3984
  223. package/test/adapter/z-stack/constants.test.ts +0 -33
  224. package/test/adapter/z-stack/structs.test.ts +0 -115
  225. package/test/adapter/z-stack/unpi.test.ts +0 -213
  226. package/test/adapter/z-stack/znp.test.ts +0 -1284
  227. package/test/adapter/zboss/fixZdoResponse.test.ts +0 -179
  228. package/test/adapter/zigate/patchZdoBuffaloBE.test.ts +0 -81
  229. package/test/adapter/zigate/zdo.test.ts +0 -187
  230. package/test/adapter/zoh/utils.test.ts +0 -36
  231. package/test/adapter/zoh/zohAdapter.test.ts +0 -1307
  232. package/test/benchOptions.ts +0 -14
  233. package/test/buffalo.test.ts +0 -431
  234. package/test/controller.bench.ts +0 -214
  235. package/test/controller.test.ts +0 -8702
  236. package/test/greenpower.test.ts +0 -1408
  237. package/test/mockAdapters.ts +0 -65
  238. package/test/mockDevices.ts +0 -598
  239. package/test/requests.bench.ts +0 -229
  240. package/test/testUtils.ts +0 -20
  241. package/test/tsconfig.json +0 -9
  242. package/test/utils/math.ts +0 -19
  243. package/test/utils.test.ts +0 -279
  244. package/test/vitest.config.mts +0 -26
  245. package/test/zcl.test.ts +0 -2831
  246. package/test/zspec/utils.test.ts +0 -68
  247. package/test/zspec/zcl/buffalo.test.ts +0 -1374
  248. package/test/zspec/zcl/frame.test.ts +0 -960
  249. package/test/zspec/zcl/utils.test.ts +0 -273
  250. package/test/zspec/zdo/buffalo.test.ts +0 -1850
  251. package/test/zspec/zdo/utils.test.ts +0 -241
  252. package/tsconfig.json +0 -24
@@ -1,3449 +0,0 @@
1
- import {existsSync, mkdirSync, unlinkSync, writeFileSync} from "node:fs";
2
- import path from "node:path";
3
- import {EventEmitter} from "node:stream";
4
- import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
5
- import type {TsType} from "../../../src/adapter";
6
- import {
7
- DEFAULT_APS_OPTIONS,
8
- DEFAULT_STACK_CONFIG,
9
- EmberAdapter,
10
- type LinkKeyBackupData,
11
- type NetworkCache,
12
- } from "../../../src/adapter/ember/adapter/emberAdapter";
13
- import {FIXED_ENDPOINTS} from "../../../src/adapter/ember/adapter/endpoints";
14
- import {OneWaitressEvents} from "../../../src/adapter/ember/adapter/oneWaitress";
15
- import {EMBER_LOW_RAM_CONCENTRATOR, SECURITY_LEVEL_Z3} from "../../../src/adapter/ember/consts";
16
- import {
17
- EmberApsOption,
18
- EmberDeviceUpdate,
19
- EmberIncomingMessageType,
20
- EmberJoinDecision,
21
- EmberJoinMethod,
22
- EmberKeyStructBitmask,
23
- EmberNetworkStatus,
24
- EmberNodeType,
25
- EmberOutgoingMessageType,
26
- EmberVersionType,
27
- EzspStatus,
28
- IEEE802154CcaMode,
29
- SecManDerivedKeyType,
30
- SecManFlag,
31
- SecManKeyType,
32
- SLStatus,
33
- } from "../../../src/adapter/ember/enums";
34
- import {EZSP_MIN_PROTOCOL_VERSION, EZSP_PROTOCOL_VERSION, EZSP_STACK_TYPE_MESH} from "../../../src/adapter/ember/ezsp/consts";
35
- import {EzspConfigId, EzspDecisionBitmask, EzspEndpointFlag, EzspPolicyId, EzspValueId} from "../../../src/adapter/ember/ezsp/enums";
36
- import type {EmberEzspEventMap} from "../../../src/adapter/ember/ezsp/ezsp";
37
- import {EzspError} from "../../../src/adapter/ember/ezspError";
38
- import type {
39
- EmberApsFrame,
40
- EmberMulticastTableEntry,
41
- EmberNetworkInitStruct,
42
- EmberNetworkParameters,
43
- EmberVersion,
44
- SecManAPSKeyMetadata,
45
- SecManContext,
46
- SecManKey,
47
- SecManNetworkKeyInfo,
48
- } from "../../../src/adapter/ember/types";
49
- import {lowHighBytes} from "../../../src/adapter/ember/utils/math";
50
- import type {DeviceJoinedPayload, DeviceLeavePayload, ZclPayload} from "../../../src/adapter/events";
51
- import type {AdapterOptions, NetworkOptions, SerialPortOptions} from "../../../src/adapter/tstype";
52
- import type {Backup} from "../../../src/models/backup";
53
- import type {UnifiedBackupStorage} from "../../../src/models/backup-storage-unified";
54
- import {logger} from "../../../src/utils/logger";
55
- import * as ZSpec from "../../../src/zspec";
56
- import type {Eui64, NodeId, PanId} from "../../../src/zspec/tstypes";
57
- import * as Zcl from "../../../src/zspec/zcl";
58
- import * as Zdo from "../../../src/zspec/zdo";
59
- import type * as ZdoTypes from "../../../src/zspec/zdo/definition/tstypes";
60
-
61
- // https://github.com/jestjs/jest/issues/6028#issuecomment-567669082
62
- function defuseRejection<T>(promise: Promise<T>) {
63
- promise.catch(() => {});
64
-
65
- return promise;
66
- }
67
-
68
- function deepClone<T>(obj: T): T {
69
- return JSON.parse(JSON.stringify(obj));
70
- }
71
-
72
- function reverseApsFrame(apsFrame: EmberApsFrame): EmberApsFrame {
73
- return Object.assign({}, apsFrame, {sourceEndpoint: apsFrame.destinationEndpoint, destinationEndpoint: apsFrame.sourceEndpoint});
74
- }
75
-
76
- async function flushPromises(): Promise<void> {
77
- const {setImmediate} = await vi.importActual<typeof import("node:timers")>("node:timers");
78
- return new Promise(setImmediate);
79
- }
80
-
81
- const TEMP_PATH = path.resolve("temp");
82
- const STACK_CONFIG_PATH = path.join(TEMP_PATH, "stack_config.json");
83
- const DEFAULT_NETWORK_OPTIONS: Readonly<NetworkOptions> = {
84
- panID: 24404,
85
- extendedPanID: [118, 185, 136, 236, 199, 244, 246, 85],
86
- channelList: [20],
87
- networkKey: [72, 97, 39, 230, 92, 72, 101, 148, 64, 225, 250, 214, 195, 31, 105, 71],
88
- networkKeyDistribute: false,
89
- };
90
- const DEFAULT_SERIAL_PORT_OPTIONS: Readonly<SerialPortOptions> = {
91
- baudRate: 115200,
92
- rtscts: false,
93
- path: "MOCK",
94
- adapter: "ember",
95
- };
96
- const DEFAULT_ADAPTER_OPTIONS: Readonly<AdapterOptions> = {
97
- concurrent: 16,
98
- disableLED: false,
99
- };
100
- const DEFAULT_BACKUP: Readonly<UnifiedBackupStorage> = {
101
- metadata: {
102
- format: "zigpy/open-coordinator-backup",
103
- version: 1,
104
- source: "zigbee-herdsman@0.55.0",
105
- internal: {
106
- date: "2024-07-19T15:57:15.163Z",
107
- ezspVersion: 13,
108
- },
109
- },
110
- stack_specific: {
111
- ezsp: {
112
- hashed_tclk: "da85e5bac80c8a958b14d44f14c2ba16",
113
- },
114
- },
115
- coordinator_ieee: "1122334455667788",
116
- pan_id: "5f54",
117
- extended_pan_id: "76b988ecc7f4f655",
118
- nwk_update_id: 0,
119
- security_level: 5,
120
- channel: 20,
121
- channel_mask: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26],
122
- network_key: {
123
- key: "486127e65c48659440e1fad6c31f6947",
124
- sequence_number: 0,
125
- frame_counter: 16434,
126
- },
127
- devices: [],
128
- };
129
- const DEFAULT_COORDINATOR_IEEE: Eui64 = ZSpec.Utils.eui64LEBufferToHex(Buffer.from(DEFAULT_BACKUP.coordinator_ieee, "hex"));
130
- const DEFAULT_ADAPTER_NETWORK_PARAMETERS: EmberNetworkParameters = {
131
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
132
- panId: DEFAULT_NETWORK_OPTIONS.panID,
133
- radioTxPower: 5,
134
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
135
- joinMethod: 0,
136
- nwkManagerId: 0,
137
- nwkUpdateId: 0,
138
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
139
- };
140
-
141
- let mockManufCode = Zcl.ManufacturerCode.SILICON_LABORATORIES;
142
- let mockAPSSequence = -1; // start at 0
143
- let mockMessageTag = -1; // start at 0
144
- let mockEzspEmitter = new EventEmitter<EmberEzspEventMap>();
145
- const mockEzspRemoveAllListeners = vi.fn().mockImplementation((e) => {
146
- mockEzspEmitter.removeAllListeners(e);
147
- });
148
- const mockEzspOn = vi.fn().mockImplementation((e, l) => {
149
- mockEzspEmitter.on(e, l);
150
- });
151
- const mockEzspOnce = vi.fn().mockImplementation((e, l) => {
152
- mockEzspEmitter.once(e, l);
153
- });
154
- const mockEzspStart = vi.fn().mockResolvedValue(EzspStatus.SUCCESS);
155
- const mockEzspStop = vi.fn();
156
-
157
- const mockEzspSend = vi.fn().mockResolvedValue([SLStatus.OK, ++mockMessageTag]);
158
- const mockEzspSetMulticastTableEntry = vi.fn().mockResolvedValue(SLStatus.OK);
159
- const mockEzspSetManufacturerCode = vi.fn().mockImplementation((code) => {
160
- mockManufCode = code;
161
- });
162
- const mockEzspReadAndClearCounters = vi.fn().mockResolvedValue([1, 2, 3, 4]); // not matching EmberCounterType, but doesn't matter here
163
- const mockEzspGetNetworkParameters = vi
164
- .fn()
165
- .mockResolvedValue([SLStatus.OK, EmberNodeType.COORDINATOR, deepClone(DEFAULT_ADAPTER_NETWORK_PARAMETERS)]);
166
- const mockEzspNetworkState = vi.fn().mockResolvedValue(EmberNetworkStatus.JOINED_NETWORK);
167
- const mockEzspGetEui64 = vi.fn().mockResolvedValue(DEFAULT_COORDINATOR_IEEE);
168
- const mockEzspSetConcentrator = vi.fn().mockResolvedValue(SLStatus.OK);
169
- const mockEzspSetSourceRouteDiscoveryMode = vi.fn().mockResolvedValue(1240 /* ms */);
170
- const mockEzspSetRadioIeee802154CcaMode = vi.fn().mockResolvedValue(SLStatus.OK);
171
- // not OK by default since used to detected unreged EP
172
- const mockEzspGetEndpointFlags = vi.fn().mockResolvedValue([SLStatus.NOT_FOUND, EzspEndpointFlag.DISABLED]);
173
- const mockEzspAddEndpoint = vi.fn().mockResolvedValue(SLStatus.OK);
174
- const mockEzspNetworkInit = vi.fn().mockImplementation((_networkInitStruct: EmberNetworkInitStruct) => {
175
- setTimeout(async () => {
176
- mockEzspEmitter.emit("stackStatus", SLStatus.NETWORK_UP);
177
- await flushPromises();
178
- }, 300);
179
-
180
- return SLStatus.OK;
181
- });
182
- const mockEzspExportKey = vi.fn().mockImplementation((context: SecManContext) => {
183
- switch (context.coreKeyType) {
184
- case SecManKeyType.NETWORK: {
185
- return [SLStatus.OK, {contents: Buffer.from(DEFAULT_BACKUP.network_key.key, "hex")} as SecManKey];
186
- }
187
- case SecManKeyType.TC_LINK: {
188
- return [SLStatus.OK, {contents: Buffer.from(DEFAULT_BACKUP.stack_specific!.ezsp!.hashed_tclk!, "hex")} as SecManKey];
189
- }
190
- }
191
- });
192
- const mockEzspLeaveNetwork = vi.fn().mockImplementation(() => {
193
- setTimeout(async () => {
194
- mockEzspEmitter.emit("stackStatus", SLStatus.NETWORK_DOWN);
195
- await flushPromises();
196
- }, 300);
197
-
198
- return SLStatus.OK;
199
- });
200
- const mockEzspSetInitialSecurityState = vi.fn().mockResolvedValue(SLStatus.OK);
201
- const mockEzspSetExtendedSecurityBitmask = vi.fn().mockResolvedValue(SLStatus.OK);
202
- const mockEzspClearKeyTable = vi.fn().mockResolvedValue(SLStatus.OK);
203
- const mockEzspFormNetwork = vi.fn().mockImplementation((_parameters: EmberNetworkParameters) => {
204
- setTimeout(async () => {
205
- mockEzspEmitter.emit("stackStatus", SLStatus.NETWORK_UP);
206
- await flushPromises();
207
- }, 300);
208
-
209
- return SLStatus.OK;
210
- });
211
- const mockEzspStartWritingStackTokens = vi.fn().mockResolvedValue(SLStatus.OK);
212
- const mockEzspGetConfigurationValue = vi.fn().mockImplementation((config: EzspConfigId) => {
213
- switch (config) {
214
- case EzspConfigId.KEY_TABLE_SIZE: {
215
- return [SLStatus.OK, 0];
216
- }
217
- }
218
- });
219
- const mockEzspExportLinkKeyByIndex = vi.fn();
220
- const mockEzspEraseKeyTableEntry = vi.fn().mockResolvedValue(SLStatus.OK);
221
- const mockEzspImportLinkKey = vi.fn().mockResolvedValue(SLStatus.OK);
222
- const mockEzspBroadcastNextNetworkKey = vi.fn().mockResolvedValue(SLStatus.OK);
223
- const mockEzspBroadcastNetworkKeySwitch = vi.fn().mockResolvedValue(SLStatus.OK);
224
- const mockEzspStartScan = vi.fn().mockResolvedValue(SLStatus.OK);
225
- const mockEzspVersion = vi.fn().mockImplementation((version: number) => [version, EZSP_STACK_TYPE_MESH, 0]);
226
- const mockEzspSetProtocolVersion = vi.fn();
227
- const mockEzspGetVersionStruct = vi.fn().mockResolvedValue([
228
- SLStatus.OK,
229
- {
230
- build: 135,
231
- major: 8,
232
- minor: 0,
233
- patch: 0,
234
- special: 0,
235
- type: EmberVersionType.GA,
236
- } as EmberVersion,
237
- ]);
238
- const mockEzspSetConfigurationValue = vi.fn().mockResolvedValue(SLStatus.OK);
239
- const mockEzspSetValue = vi.fn().mockResolvedValue(SLStatus.OK);
240
- const mockEzspSetPolicy = vi.fn().mockResolvedValue(SLStatus.OK);
241
- const mockEzspPermitJoining = vi.fn().mockImplementation((duration: number) => {
242
- setTimeout(async () => {
243
- mockEzspEmitter.emit("stackStatus", duration > 0 ? SLStatus.ZIGBEE_NETWORK_OPENED : SLStatus.ZIGBEE_NETWORK_CLOSED);
244
- await flushPromises();
245
- }, 300);
246
-
247
- return SLStatus.OK;
248
- });
249
- const mockEzspSendBroadcast = vi.fn().mockResolvedValue([SLStatus.OK, ++mockAPSSequence]);
250
- const mockEzspSendUnicast = vi.fn().mockResolvedValue([SLStatus.OK, ++mockAPSSequence]);
251
- const mockEzspGetNetworkKeyInfo = vi.fn().mockResolvedValue([
252
- SLStatus.OK,
253
- {
254
- networkKeySet: true,
255
- alternateNetworkKeySet: false,
256
- networkKeySequenceNumber: DEFAULT_BACKUP.network_key.sequence_number,
257
- altNetworkKeySequenceNumber: 0,
258
- networkKeyFrameCounter: DEFAULT_BACKUP.network_key.frame_counter,
259
- } as SecManNetworkKeyInfo,
260
- ]);
261
- const mockEzspGetApsKeyInfo = vi.fn().mockResolvedValue([
262
- SLStatus.OK,
263
- {
264
- bitmask: EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
265
- outgoingFrameCounter: 456,
266
- incomingFrameCounter: 0,
267
- ttlInSeconds: 0,
268
- } as SecManAPSKeyMetadata,
269
- ]);
270
- const mockEzspSetRadioPower = vi.fn().mockResolvedValue(SLStatus.OK);
271
- const mockEzspImportTransientKey = vi.fn().mockResolvedValue(SLStatus.OK);
272
- const mockEzspClearTransientLinkKeys = vi.fn().mockResolvedValue(SLStatus.OK);
273
- const mockEzspSetLogicalAndRadioChannel = vi.fn().mockResolvedValue(SLStatus.OK);
274
- const mockEzspSendRawMessage = vi.fn().mockResolvedValue(SLStatus.OK);
275
- const mockEzspSetNWKFrameCounter = vi.fn().mockResolvedValue(SLStatus.OK);
276
- const mockEzspSetAPSFrameCounter = vi.fn().mockResolvedValue(SLStatus.OK);
277
-
278
- vi.mock("../../../src/adapter/ember/uart/ash");
279
-
280
- vi.mock("../../../src/adapter/ember/ezsp/ezsp", async (importOriginal) => ({
281
- ...(await importOriginal()),
282
- Ezsp: vi.fn(() => ({
283
- removeAllListeners: mockEzspRemoveAllListeners,
284
- on: mockEzspOn,
285
- once: mockEzspOnce,
286
-
287
- // only functions called from adapter
288
- ash: {readAndClearCounters: vi.fn().mockReturnValue([9, 8, 7])},
289
-
290
- start: mockEzspStart,
291
- stop: mockEzspStop,
292
- send: mockEzspSend,
293
- ezspSetMulticastTableEntry: mockEzspSetMulticastTableEntry,
294
- ezspSetManufacturerCode: mockEzspSetManufacturerCode,
295
- ezspReadAndClearCounters: mockEzspReadAndClearCounters,
296
- ezspGetNetworkParameters: mockEzspGetNetworkParameters,
297
- ezspNetworkState: mockEzspNetworkState,
298
- ezspGetEui64: mockEzspGetEui64,
299
- ezspSetConcentrator: mockEzspSetConcentrator,
300
- ezspSetSourceRouteDiscoveryMode: mockEzspSetSourceRouteDiscoveryMode,
301
- ezspSetRadioIeee802154CcaMode: mockEzspSetRadioIeee802154CcaMode,
302
- ezspGetEndpointFlags: mockEzspGetEndpointFlags,
303
- ezspAddEndpoint: mockEzspAddEndpoint,
304
- ezspNetworkInit: mockEzspNetworkInit,
305
- ezspExportKey: mockEzspExportKey,
306
- ezspLeaveNetwork: mockEzspLeaveNetwork,
307
- ezspSetInitialSecurityState: mockEzspSetInitialSecurityState,
308
- ezspSetExtendedSecurityBitmask: mockEzspSetExtendedSecurityBitmask,
309
- ezspClearKeyTable: mockEzspClearKeyTable,
310
- ezspFormNetwork: mockEzspFormNetwork,
311
- ezspStartWritingStackTokens: mockEzspStartWritingStackTokens,
312
- ezspGetConfigurationValue: mockEzspGetConfigurationValue,
313
- ezspExportLinkKeyByIndex: mockEzspExportLinkKeyByIndex,
314
- ezspEraseKeyTableEntry: mockEzspEraseKeyTableEntry,
315
- ezspImportLinkKey: mockEzspImportLinkKey,
316
- ezspBroadcastNextNetworkKey: mockEzspBroadcastNextNetworkKey,
317
- ezspBroadcastNetworkKeySwitch: mockEzspBroadcastNetworkKeySwitch,
318
- ezspStartScan: mockEzspStartScan,
319
- ezspVersion: mockEzspVersion,
320
- setProtocolVersion: mockEzspSetProtocolVersion,
321
- ezspGetVersionStruct: mockEzspGetVersionStruct,
322
- ezspSetConfigurationValue: mockEzspSetConfigurationValue,
323
- ezspSetValue: mockEzspSetValue,
324
- ezspSetPolicy: mockEzspSetPolicy,
325
- ezspPermitJoining: mockEzspPermitJoining,
326
- ezspSendBroadcast: mockEzspSendBroadcast,
327
- ezspSendUnicast: mockEzspSendUnicast,
328
- ezspGetNetworkKeyInfo: mockEzspGetNetworkKeyInfo,
329
- ezspGetApsKeyInfo: mockEzspGetApsKeyInfo,
330
- ezspSetRadioPower: mockEzspSetRadioPower,
331
- ezspImportTransientKey: mockEzspImportTransientKey,
332
- ezspClearTransientLinkKeys: mockEzspClearTransientLinkKeys,
333
- ezspSetLogicalAndRadioChannel: mockEzspSetLogicalAndRadioChannel,
334
- ezspSendRawMessage: mockEzspSendRawMessage,
335
- ezspSetNWKFrameCounter: mockEzspSetNWKFrameCounter,
336
- ezspSetAPSFrameCounter: mockEzspSetAPSFrameCounter,
337
- })),
338
- }));
339
-
340
- const ezspMocks = [
341
- mockEzspRemoveAllListeners,
342
- mockEzspOn,
343
- mockEzspOnce,
344
- mockEzspStart,
345
- mockEzspStop,
346
- mockEzspSend,
347
- mockEzspSetMulticastTableEntry,
348
- mockEzspSetManufacturerCode,
349
- mockEzspReadAndClearCounters,
350
- mockEzspGetNetworkParameters,
351
- mockEzspNetworkState,
352
- mockEzspGetEui64,
353
- mockEzspSetConcentrator,
354
- mockEzspSetSourceRouteDiscoveryMode,
355
- mockEzspSetRadioIeee802154CcaMode,
356
- mockEzspGetEndpointFlags,
357
- mockEzspAddEndpoint,
358
- mockEzspNetworkInit,
359
- mockEzspExportKey,
360
- mockEzspLeaveNetwork,
361
- mockEzspSetInitialSecurityState,
362
- mockEzspSetExtendedSecurityBitmask,
363
- mockEzspClearKeyTable,
364
- mockEzspFormNetwork,
365
- mockEzspStartWritingStackTokens,
366
- mockEzspGetConfigurationValue,
367
- mockEzspExportLinkKeyByIndex,
368
- mockEzspEraseKeyTableEntry,
369
- mockEzspImportLinkKey,
370
- mockEzspBroadcastNextNetworkKey,
371
- mockEzspBroadcastNetworkKeySwitch,
372
- mockEzspStartScan,
373
- mockEzspVersion,
374
- mockEzspSetProtocolVersion,
375
- mockEzspGetVersionStruct,
376
- mockEzspSetConfigurationValue,
377
- mockEzspSetValue,
378
- mockEzspSetPolicy,
379
- mockEzspPermitJoining,
380
- mockEzspSendBroadcast,
381
- mockEzspSendUnicast,
382
- mockEzspGetNetworkKeyInfo,
383
- mockEzspGetApsKeyInfo,
384
- mockEzspSetRadioPower,
385
- mockEzspImportTransientKey,
386
- mockEzspClearTransientLinkKeys,
387
- mockEzspSetLogicalAndRadioChannel,
388
- mockEzspSendRawMessage,
389
- mockEzspSetNWKFrameCounter,
390
- mockEzspSetAPSFrameCounter,
391
- ];
392
-
393
- describe("Ember Adapter Layer", () => {
394
- let adapter: EmberAdapter;
395
- let backupPath: string;
396
- const loggerSpies = {
397
- debug: vi.spyOn(logger, "debug"),
398
- info: vi.spyOn(logger, "info"),
399
- warning: vi.spyOn(logger, "warning"),
400
- error: vi.spyOn(logger, "error"),
401
- };
402
-
403
- const deleteCoordinatorBackup = () => {
404
- if (existsSync(backupPath)) {
405
- unlinkSync(backupPath);
406
- }
407
- };
408
-
409
- const deleteStackConfig = () => {
410
- if (existsSync(STACK_CONFIG_PATH)) {
411
- unlinkSync(STACK_CONFIG_PATH);
412
- }
413
- };
414
-
415
- const takeResetCodePath = () => {
416
- deleteCoordinatorBackup();
417
- mockEzspGetNetworkParameters.mockResolvedValueOnce([
418
- SLStatus.OK,
419
- EmberNodeType.COORDINATOR,
420
- {
421
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
422
- panId: 1234,
423
- radioTxPower: 5,
424
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
425
- joinMethod: 0,
426
- nwkManagerId: 0,
427
- nwkUpdateId: 0,
428
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
429
- } as EmberNetworkParameters,
430
- ]);
431
- };
432
-
433
- const takeRestoredCodePath = () => {
434
- mockEzspGetNetworkParameters.mockResolvedValueOnce([
435
- SLStatus.OK,
436
- EmberNodeType.COORDINATOR,
437
- {
438
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
439
- panId: 1234,
440
- radioTxPower: 5,
441
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
442
- joinMethod: 0,
443
- nwkManagerId: 0,
444
- nwkUpdateId: 0,
445
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
446
- } as EmberNetworkParameters,
447
- ]);
448
- };
449
-
450
- const clearMocks = () => {
451
- for (const mock of ezspMocks) {
452
- mock.mockClear();
453
- }
454
-
455
- loggerSpies.debug.mockClear();
456
- loggerSpies.info.mockClear();
457
- loggerSpies.warning.mockClear();
458
- loggerSpies.error.mockClear();
459
- };
460
-
461
- beforeAll(() => {
462
- if (!existsSync(TEMP_PATH)) {
463
- mkdirSync(TEMP_PATH);
464
- } else {
465
- // just in case, remove previous remnants
466
- deleteCoordinatorBackup();
467
- deleteStackConfig();
468
- }
469
- });
470
-
471
- afterAll(() => {
472
- deleteCoordinatorBackup();
473
- deleteStackConfig();
474
- });
475
-
476
- beforeEach(() => {
477
- vi.useFakeTimers();
478
-
479
- backupPath = path.join(TEMP_PATH, "ember_coordinator_backup.json");
480
-
481
- writeFileSync(backupPath, JSON.stringify(DEFAULT_BACKUP, undefined, 2));
482
-
483
- mockManufCode = Zcl.ManufacturerCode.SILICON_LABORATORIES;
484
- mockAPSSequence = -1;
485
- mockMessageTag = -1;
486
- // make sure emitter is reset too
487
- mockEzspEmitter = new EventEmitter();
488
-
489
- clearMocks();
490
- });
491
-
492
- afterEach(() => {
493
- vi.useRealTimers();
494
- });
495
-
496
- it("Creates default instance", () => {
497
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
498
-
499
- expect(adapter).toBeInstanceOf(EmberAdapter);
500
- expect(adapter.stackConfig).toStrictEqual(DEFAULT_STACK_CONFIG);
501
- });
502
-
503
- it("Loads custom stack config", () => {
504
- const config = {
505
- CONCENTRATOR_RAM_TYPE: "low",
506
- CONCENTRATOR_MIN_TIME: 1,
507
- CONCENTRATOR_MAX_TIME: 31,
508
- CONCENTRATOR_ROUTE_ERROR_THRESHOLD: 5,
509
- CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD: 2,
510
- CONCENTRATOR_MAX_HOPS: 5,
511
- MAX_END_DEVICE_CHILDREN: 16,
512
- TRANSIENT_DEVICE_TIMEOUT: 1000,
513
- END_DEVICE_POLL_TIMEOUT: 12,
514
- TRANSIENT_KEY_TIMEOUT_S: 500,
515
- CCA_MODE: "SIGNAL_AND_RSSI",
516
- };
517
-
518
- writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));
519
-
520
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
521
-
522
- expect(adapter.stackConfig).toStrictEqual(config);
523
-
524
- // cleanup
525
- unlinkSync(STACK_CONFIG_PATH);
526
- });
527
-
528
- it("Loads only valid custom stack config", () => {
529
- const config = {
530
- CONCENTRATOR_RAM_TYPE: "bad",
531
- CONCENTRATOR_MIN_TIME: -1,
532
- CONCENTRATOR_MAX_TIME: 15,
533
- CONCENTRATOR_ROUTE_ERROR_THRESHOLD: 500,
534
- CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD: 200,
535
- CONCENTRATOR_MAX_HOPS: 35,
536
- MAX_END_DEVICE_CHILDREN: 65,
537
- TRANSIENT_DEVICE_TIMEOUT: 65536,
538
- END_DEVICE_POLL_TIMEOUT: 15,
539
- TRANSIENT_KEY_TIMEOUT_S: 65536,
540
- CCA_MODE: "abcd",
541
- };
542
-
543
- writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));
544
-
545
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
546
-
547
- expect(adapter.stackConfig).toStrictEqual(DEFAULT_STACK_CONFIG);
548
-
549
- // cleanup
550
- unlinkSync(STACK_CONFIG_PATH);
551
- });
552
-
553
- it("Loads only valid custom stack config - null CCA_MODE", () => {
554
- const config = {
555
- CCA_MODE: null,
556
- };
557
-
558
- writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));
559
-
560
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
561
-
562
- expect(adapter.stackConfig).toStrictEqual(DEFAULT_STACK_CONFIG);
563
-
564
- // cleanup
565
- unlinkSync(STACK_CONFIG_PATH);
566
- });
567
-
568
- it("Uses default concurrency for queue if not supplied/valid", () => {
569
- adapter = new EmberAdapter(
570
- DEFAULT_NETWORK_OPTIONS,
571
- DEFAULT_SERIAL_PORT_OPTIONS,
572
- backupPath,
573
- Object.assign({}, DEFAULT_ADAPTER_OPTIONS, {concurrent: undefined}),
574
- );
575
- // @ts-expect-error private
576
- expect(adapter.queue.concurrent).toStrictEqual(16);
577
- });
578
-
579
- it("Starts with resumed when everything matches", async () => {
580
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
581
- const result = adapter.start();
582
-
583
- await vi.advanceTimersByTimeAsync(5000);
584
- await expect(result).resolves.toStrictEqual("resumed");
585
- expect(mockEzspSetProtocolVersion).toHaveBeenCalledWith(EZSP_PROTOCOL_VERSION);
586
- expect(
587
- // @ts-expect-error private
588
- adapter.networkCache,
589
- ).toStrictEqual({
590
- eui64: DEFAULT_COORDINATOR_IEEE,
591
- parameters: {
592
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
593
- panId: DEFAULT_NETWORK_OPTIONS.panID,
594
- radioTxPower: 5,
595
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
596
- joinMethod: 0,
597
- nwkManagerId: 0,
598
- nwkUpdateId: 0,
599
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
600
- } as EmberNetworkParameters,
601
- } as NetworkCache);
602
- });
603
-
604
- it("Starts with custom stack config", async () => {
605
- const config = {
606
- CONCENTRATOR_RAM_TYPE: "low",
607
- CONCENTRATOR_MIN_TIME: 1,
608
- CONCENTRATOR_MAX_TIME: 31,
609
- CONCENTRATOR_ROUTE_ERROR_THRESHOLD: 5,
610
- CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD: 2,
611
- CONCENTRATOR_MAX_HOPS: 5,
612
- MAX_END_DEVICE_CHILDREN: 16,
613
- TRANSIENT_DEVICE_TIMEOUT: 1000,
614
- END_DEVICE_POLL_TIMEOUT: 12,
615
- TRANSIENT_KEY_TIMEOUT_S: 500,
616
- CCA_MODE: "SIGNAL_AND_RSSI",
617
- };
618
-
619
- writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));
620
-
621
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
622
- const result = adapter.start();
623
-
624
- await vi.advanceTimersByTimeAsync(5000);
625
- await expect(result).resolves.toStrictEqual("resumed");
626
- expect(mockEzspSetValue).toHaveBeenCalledWith(EzspValueId.TRANSIENT_DEVICE_TIMEOUT, 2, lowHighBytes(config.TRANSIENT_DEVICE_TIMEOUT));
627
- expect(mockEzspSetConfigurationValue).toHaveBeenCalledWith(EzspConfigId.MAX_END_DEVICE_CHILDREN, config.MAX_END_DEVICE_CHILDREN);
628
- expect(mockEzspSetConfigurationValue).toHaveBeenCalledWith(EzspConfigId.END_DEVICE_POLL_TIMEOUT, config.END_DEVICE_POLL_TIMEOUT);
629
- expect(mockEzspSetConfigurationValue).toHaveBeenCalledWith(EzspConfigId.TRANSIENT_KEY_TIMEOUT_S, config.TRANSIENT_KEY_TIMEOUT_S);
630
- expect(mockEzspSetConcentrator).toHaveBeenCalledWith(
631
- true,
632
- EMBER_LOW_RAM_CONCENTRATOR,
633
- config.CONCENTRATOR_MIN_TIME,
634
- config.CONCENTRATOR_MAX_TIME,
635
- config.CONCENTRATOR_ROUTE_ERROR_THRESHOLD,
636
- config.CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD,
637
- config.CONCENTRATOR_MAX_HOPS,
638
- );
639
- expect(mockEzspSetRadioIeee802154CcaMode).toHaveBeenCalledWith(IEEE802154CcaMode.SIGNAL_AND_RSSI);
640
-
641
- // cleanup
642
- unlinkSync(STACK_CONFIG_PATH);
643
- });
644
-
645
- it("Starts with custom stack config invalid CCA_MODE", async () => {
646
- const config = {
647
- CCA_MODE: "abcd",
648
- };
649
-
650
- writeFileSync(STACK_CONFIG_PATH, JSON.stringify(config, undefined, 2));
651
-
652
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
653
- const result = adapter.start();
654
-
655
- await vi.advanceTimersByTimeAsync(5000);
656
- await expect(result).resolves.toStrictEqual("resumed");
657
- expect(mockEzspSetRadioIeee802154CcaMode).toHaveBeenCalledTimes(0);
658
-
659
- // cleanup
660
- unlinkSync(STACK_CONFIG_PATH);
661
- });
662
-
663
- it("Starts with restored when no network in adapter", async () => {
664
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
665
- const expectedNetParams: EmberNetworkParameters = {
666
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
667
- panId: DEFAULT_NETWORK_OPTIONS.panID,
668
- radioTxPower: 5,
669
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
670
- joinMethod: 0,
671
- nwkManagerId: 0,
672
- nwkUpdateId: 0,
673
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
674
- };
675
-
676
- mockEzspNetworkInit.mockResolvedValueOnce(SLStatus.NOT_JOINED);
677
-
678
- const result = adapter.start();
679
-
680
- await vi.advanceTimersByTimeAsync(5000);
681
- expect(mockEzspSetNWKFrameCounter).toHaveBeenCalledWith(DEFAULT_BACKUP.network_key.frame_counter);
682
- // expect(mockEzspSetAPSFrameCounter).toHaveBeenCalledWith(DEFAULT_BACKUP.???.???);
683
- expect(mockEzspFormNetwork).toHaveBeenCalledWith(expectedNetParams);
684
- await expect(result).resolves.toStrictEqual("restored");
685
- });
686
-
687
- it("Starts with restored when network param mismatch but backup available", async () => {
688
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
689
- const expectedNetParams: EmberNetworkParameters = {
690
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
691
- panId: DEFAULT_NETWORK_OPTIONS.panID,
692
- radioTxPower: 5,
693
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
694
- joinMethod: 0,
695
- nwkManagerId: 0,
696
- nwkUpdateId: 0,
697
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
698
- };
699
-
700
- mockEzspGetNetworkParameters.mockResolvedValueOnce([
701
- SLStatus.OK,
702
- EmberNodeType.COORDINATOR,
703
- {
704
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
705
- panId: 1234,
706
- radioTxPower: 5,
707
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
708
- joinMethod: 0,
709
- nwkManagerId: 0,
710
- nwkUpdateId: 0,
711
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
712
- } as EmberNetworkParameters,
713
- ]);
714
-
715
- const result = adapter.start();
716
-
717
- await vi.advanceTimersByTimeAsync(5000);
718
- expect(mockEzspSetNWKFrameCounter).toHaveBeenCalledWith(DEFAULT_BACKUP.network_key.frame_counter);
719
- // expect(mockEzspSetAPSFrameCounter).toHaveBeenCalledWith(DEFAULT_BACKUP.???.???);
720
- expect(mockEzspFormNetwork).toHaveBeenCalledWith(expectedNetParams);
721
- await expect(result).resolves.toStrictEqual("restored");
722
- });
723
-
724
- it("Starts with restored when network key mismatch but backup available", async () => {
725
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
726
- const expectedNetParams: EmberNetworkParameters = {
727
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
728
- panId: DEFAULT_NETWORK_OPTIONS.panID,
729
- radioTxPower: 5,
730
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
731
- joinMethod: 0,
732
- nwkManagerId: 0,
733
- nwkUpdateId: 0,
734
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
735
- };
736
-
737
- mockEzspGetNetworkParameters.mockResolvedValueOnce([SLStatus.OK, EmberNodeType.COORDINATOR, expectedNetParams]);
738
-
739
- const contents = Buffer.from(DEFAULT_BACKUP.network_key.key, "hex").fill(0xff);
740
-
741
- mockEzspExportKey.mockResolvedValueOnce([SLStatus.OK, {contents} as SecManKey]);
742
-
743
- const result = adapter.start();
744
-
745
- await vi.advanceTimersByTimeAsync(5000);
746
- await expect(result).resolves.toStrictEqual("restored");
747
- expect(mockEzspSetNWKFrameCounter).toHaveBeenCalledWith(DEFAULT_BACKUP.network_key.frame_counter);
748
- // expect(mockEzspSetAPSFrameCounter).toHaveBeenCalledWith(DEFAULT_BACKUP.???.???);
749
- expect(mockEzspFormNetwork).toHaveBeenCalledWith(expectedNetParams);
750
- });
751
-
752
- it("Starts with reset when networks mismatch but no backup available", async () => {
753
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
754
-
755
- deleteCoordinatorBackup();
756
- mockEzspGetNetworkParameters.mockResolvedValueOnce([
757
- SLStatus.OK,
758
- EmberNodeType.COORDINATOR,
759
- {
760
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
761
- panId: 1234,
762
- radioTxPower: 5,
763
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
764
- joinMethod: 0,
765
- nwkManagerId: 0,
766
- nwkUpdateId: 0,
767
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
768
- } as EmberNetworkParameters,
769
- ]);
770
-
771
- const result = adapter.start();
772
-
773
- await vi.advanceTimersByTimeAsync(5000);
774
- await expect(result).resolves.toStrictEqual("reset");
775
- });
776
-
777
- it("Starts with reset when backup/config mismatch", async () => {
778
- adapter = new EmberAdapter(
779
- Object.assign({}, DEFAULT_NETWORK_OPTIONS, {panID: 1234}),
780
- DEFAULT_SERIAL_PORT_OPTIONS,
781
- backupPath,
782
- DEFAULT_ADAPTER_OPTIONS,
783
- );
784
-
785
- const result = adapter.start();
786
-
787
- await vi.advanceTimersByTimeAsync(5000);
788
- await expect(result).resolves.toStrictEqual("reset");
789
- expect(mockEzspSetNWKFrameCounter).toHaveBeenCalledTimes(0);
790
- // expect(mockEzspSetAPSFrameCounter).toHaveBeenCalledTimes(0);
791
- expect(mockEzspFormNetwork).toHaveBeenCalledWith({
792
- panId: 1234,
793
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
794
- radioTxPower: 5, // default when setting `transmitPower` is null/zero
795
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
796
- joinMethod: EmberJoinMethod.MAC_ASSOCIATION,
797
- nwkManagerId: ZSpec.COORDINATOR_ADDRESS,
798
- nwkUpdateId: 0,
799
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
800
- } as EmberNetworkParameters);
801
- });
802
-
803
- it("Starts with reset and forms with given transmit power", async () => {
804
- adapter = new EmberAdapter(
805
- Object.assign({}, DEFAULT_NETWORK_OPTIONS, {panID: 1234}),
806
- DEFAULT_SERIAL_PORT_OPTIONS,
807
- backupPath,
808
- Object.assign({}, DEFAULT_ADAPTER_OPTIONS, {transmitPower: 10}),
809
- );
810
-
811
- const result = adapter.start();
812
-
813
- await vi.advanceTimersByTimeAsync(5000);
814
- await expect(result).resolves.toStrictEqual("reset");
815
- expect(mockEzspSetNWKFrameCounter).toHaveBeenCalledTimes(0);
816
- // expect(mockEzspSetAPSFrameCounter).toHaveBeenCalledTimes(0);
817
- expect(mockEzspFormNetwork).toHaveBeenCalledWith({
818
- panId: 1234,
819
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
820
- radioTxPower: 10,
821
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
822
- joinMethod: EmberJoinMethod.MAC_ASSOCIATION,
823
- nwkManagerId: ZSpec.COORDINATOR_ADDRESS,
824
- nwkUpdateId: 0,
825
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
826
- } as EmberNetworkParameters);
827
- });
828
-
829
- it("Starts with mismatching transmit power", async () => {
830
- adapter = new EmberAdapter(
831
- DEFAULT_NETWORK_OPTIONS,
832
- DEFAULT_SERIAL_PORT_OPTIONS,
833
- backupPath,
834
- Object.assign({}, DEFAULT_ADAPTER_OPTIONS, {transmitPower: 10}),
835
- );
836
-
837
- const result = adapter.start();
838
-
839
- await vi.advanceTimersByTimeAsync(5000);
840
- await expect(result).resolves.toStrictEqual("resumed");
841
- expect(mockEzspSetRadioPower).toHaveBeenCalledTimes(1);
842
- expect(mockEzspSetRadioPower).toHaveBeenCalledWith(10);
843
- });
844
-
845
- it("Starts with matching transmit power after form", async () => {
846
- adapter = new EmberAdapter(
847
- DEFAULT_NETWORK_OPTIONS,
848
- DEFAULT_SERIAL_PORT_OPTIONS,
849
- backupPath,
850
- Object.assign({}, DEFAULT_ADAPTER_OPTIONS, {transmitPower: 10}),
851
- );
852
- mockEzspNetworkInit.mockResolvedValueOnce(SLStatus.NOT_JOINED);
853
- mockEzspGetNetworkParameters.mockResolvedValueOnce([
854
- SLStatus.OK,
855
- EmberNodeType.COORDINATOR,
856
- {
857
- extendedPanId: DEFAULT_NETWORK_OPTIONS.extendedPanID!,
858
- panId: DEFAULT_NETWORK_OPTIONS.panID,
859
- radioTxPower: 10,
860
- radioChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
861
- joinMethod: 0,
862
- nwkManagerId: 0,
863
- nwkUpdateId: 0,
864
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
865
- } as EmberNetworkParameters,
866
- ]);
867
-
868
- const result = adapter.start();
869
-
870
- await vi.advanceTimersByTimeAsync(5000);
871
- await expect(result).resolves.toStrictEqual("restored");
872
- expect(mockEzspSetRadioPower).toHaveBeenCalledTimes(0);
873
- });
874
-
875
- it("Starts with mismatching transmit power, failure does not present start", async () => {
876
- adapter = new EmberAdapter(
877
- DEFAULT_NETWORK_OPTIONS,
878
- DEFAULT_SERIAL_PORT_OPTIONS,
879
- backupPath,
880
- Object.assign({}, DEFAULT_ADAPTER_OPTIONS, {transmitPower: 12}),
881
- );
882
- mockEzspSetRadioPower.mockResolvedValueOnce(SLStatus.FAIL);
883
-
884
- const result = adapter.start();
885
-
886
- await vi.advanceTimersByTimeAsync(5000);
887
- await expect(result).resolves.toStrictEqual("resumed");
888
- expect(mockEzspSetRadioPower).toHaveBeenCalledTimes(1);
889
- expect(mockEzspSetRadioPower).toHaveBeenCalledWith(12);
890
- expect(loggerSpies.error).toHaveBeenCalledWith("Failed to set transmit power to 12 status=FAIL.", "zh:ember");
891
- });
892
-
893
- it("Fails to start when EZSP layer fails to start", async () => {
894
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
895
-
896
- mockEzspStart.mockResolvedValueOnce(EzspStatus.HOST_FATAL_ERROR);
897
-
898
- const result = adapter.start();
899
-
900
- await expect(result).rejects.toThrow(`Failed to start EZSP layer with status=${EzspStatus[EzspStatus.HOST_FATAL_ERROR]}.`);
901
- });
902
-
903
- it.each([
904
- [
905
- "if NCP has improper stack type",
906
- () => {
907
- mockEzspVersion.mockResolvedValueOnce([14, 1, 123]);
908
- },
909
- "Stack type 1 is not expected!",
910
- ],
911
- [
912
- "if NCP version unsupported",
913
- () => {
914
- mockEzspVersion.mockResolvedValueOnce([12, EZSP_STACK_TYPE_MESH, 123]);
915
- },
916
- `Adapter EZSP protocol version (12) is not supported by Host [${EZSP_MIN_PROTOCOL_VERSION}-${EZSP_PROTOCOL_VERSION}].`,
917
- ],
918
- [
919
- "if NCP has old style version number",
920
- () => {
921
- mockEzspGetVersionStruct.mockResolvedValueOnce([SLStatus.INVALID_PARAMETER, 0]);
922
- },
923
- "NCP has old-style version number. Not supported.",
924
- ],
925
- [
926
- "if network is not valid by end of init sequence",
927
- () => {
928
- mockEzspGetNetworkParameters
929
- .mockResolvedValueOnce([SLStatus.OK, EmberNodeType.COORDINATOR, deepClone(DEFAULT_ADAPTER_NETWORK_PARAMETERS)])
930
- .mockResolvedValueOnce([SLStatus.FAIL, 0, {}]);
931
- },
932
- "Failed to get network parameters with status=FAIL.",
933
- ],
934
- [
935
- "if could not set concentrator",
936
- () => {
937
- mockEzspSetConcentrator.mockResolvedValueOnce(SLStatus.FAIL);
938
- },
939
- "[CONCENTRATOR] Failed to set concentrator with status=FAIL.",
940
- ],
941
- [
942
- "if could not add endpoint",
943
- () => {
944
- mockEzspAddEndpoint.mockResolvedValueOnce(SLStatus.FAIL);
945
- },
946
- `Failed to register endpoint '1' with status=FAIL.`,
947
- ],
948
- [
949
- "if could not set multicast table entry",
950
- () => {
951
- mockEzspSetMulticastTableEntry.mockResolvedValueOnce(SLStatus.FAIL);
952
- },
953
- `Failed to register group '0' in multicast table with status=FAIL.`,
954
- ],
955
- [
956
- "if could not set TC key request policy",
957
- () => {
958
- mockEzspSetPolicy
959
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.BINDING_MODIFICATION_POLICY
960
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.MESSAGE_CONTENTS_IN_CALLBACK_POLICY
961
- .mockResolvedValueOnce(SLStatus.FAIL); // EzspPolicyId.TC_KEY_REQUEST_POLICY
962
- },
963
- "[INIT TC] Failed to set EzspPolicyId TC_KEY_REQUEST_POLICY to ALLOW_TC_KEY_REQUESTS_AND_SEND_CURRENT_KEY with status=FAIL.",
964
- ],
965
- [
966
- "if could not set app key request policy",
967
- () => {
968
- mockEzspSetPolicy
969
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.BINDING_MODIFICATION_POLICY
970
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.MESSAGE_CONTENTS_IN_CALLBACK_POLICY
971
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.TC_KEY_REQUEST_POLICY
972
- .mockResolvedValueOnce(SLStatus.FAIL); // EzspPolicyId.APP_KEY_REQUEST_POLICY
973
- },
974
- "[INIT TC] Failed to set EzspPolicyId APP_KEY_REQUEST_POLICY to DENY_APP_KEY_REQUESTS with status=FAIL.",
975
- ],
976
- [
977
- "if could not set app key request policy",
978
- () => {
979
- mockEzspSetPolicy
980
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.BINDING_MODIFICATION_POLICY
981
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.MESSAGE_CONTENTS_IN_CALLBACK_POLICY
982
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.TC_KEY_REQUEST_POLICY
983
- .mockResolvedValueOnce(SLStatus.OK) // EzspPolicyId.APP_KEY_REQUEST_POLICY
984
- .mockResolvedValueOnce(SLStatus.FAIL); // EzspPolicyId.TRUST_CENTER_POLICY
985
- },
986
- "[INIT TC] Failed to set join policy to USE_PRECONFIGURED_KEY with status=FAIL.",
987
- ],
988
- [
989
- "if could not init network",
990
- () => {
991
- mockEzspNetworkInit.mockResolvedValueOnce(SLStatus.FAIL);
992
- },
993
- "[INIT TC] Failed network init request with status=FAIL.",
994
- ],
995
- [
996
- "if could not export network key",
997
- () => {
998
- mockEzspExportKey.mockResolvedValueOnce([SLStatus.FAIL, Buffer.alloc(16)]);
999
- },
1000
- "[INIT TC] Failed to export Network Key with status=FAIL.",
1001
- ],
1002
- [
1003
- "if could not leave network",
1004
- () => {
1005
- // force leave code path
1006
- mockEzspGetNetworkParameters.mockResolvedValueOnce([SLStatus.FAIL, 0, {}]);
1007
- mockEzspLeaveNetwork.mockResolvedValueOnce(SLStatus.FAIL);
1008
- },
1009
- "[INIT TC] Failed leave network request with status=FAIL.",
1010
- ],
1011
- [
1012
- "if form could not set NWK frame counter",
1013
- () => {
1014
- takeRestoredCodePath();
1015
- mockEzspSetNWKFrameCounter.mockResolvedValueOnce(SLStatus.FAIL);
1016
- },
1017
- "[INIT FORM] Failed to set NWK frame counter with status=FAIL.",
1018
- ],
1019
- // [
1020
- // 'if form could not set TC APS frame counter',
1021
- // () => {
1022
- // takeRestoredCodePath();
1023
- // mockEzspSetAPSFrameCounter.mockResolvedValueOnce(SLStatus.FAIL);
1024
- // },
1025
- // `[INIT FORM] Failed to set TC APS frame counter with status=FAIL.`,
1026
- // ],
1027
- [
1028
- "if form could not set initial security state",
1029
- () => {
1030
- takeResetCodePath();
1031
- mockEzspSetInitialSecurityState.mockResolvedValueOnce(SLStatus.FAIL);
1032
- },
1033
- "[INIT FORM] Failed to set initial security state with status=FAIL.",
1034
- ],
1035
- [
1036
- "if form could not set extended security bitmask",
1037
- () => {
1038
- takeResetCodePath();
1039
- mockEzspSetExtendedSecurityBitmask.mockResolvedValueOnce(SLStatus.FAIL);
1040
- },
1041
- "[INIT FORM] Failed to set extended security bitmask to 272 with status=FAIL.",
1042
- ],
1043
- [
1044
- "if could not form network",
1045
- () => {
1046
- takeResetCodePath();
1047
- mockEzspFormNetwork.mockResolvedValueOnce(SLStatus.FAIL);
1048
- },
1049
- "[INIT FORM] Failed form network request with status=FAIL.",
1050
- ],
1051
- [
1052
- "if backup corrupted",
1053
- () => {
1054
- writeFileSync(backupPath, "abcd");
1055
- },
1056
- "[BACKUP] Coordinator backup is corrupted.",
1057
- ],
1058
- [
1059
- "if backup unsupported",
1060
- () => {
1061
- const customBackup = deepClone(DEFAULT_BACKUP);
1062
- // @ts-expect-error mock override
1063
- customBackup.metadata.version = 2;
1064
-
1065
- writeFileSync(backupPath, JSON.stringify(customBackup, undefined, 2));
1066
- },
1067
- "[BACKUP] Unsupported open coordinator backup version (version=2).",
1068
- ],
1069
- [
1070
- "if backup not EmberZNet stack specific",
1071
- () => {
1072
- const customBackup = deepClone(DEFAULT_BACKUP);
1073
- customBackup.stack_specific!.ezsp = undefined;
1074
-
1075
- writeFileSync(backupPath, JSON.stringify(customBackup, undefined, 2));
1076
- },
1077
- "[BACKUP] Current backup file is not for EmberZNet stack.",
1078
- ],
1079
- [
1080
- "if backup not EmberZNet EZSP version",
1081
- () => {
1082
- const customBackup = deepClone(DEFAULT_BACKUP);
1083
- customBackup.metadata.internal.ezspVersion = undefined;
1084
-
1085
- writeFileSync(backupPath, JSON.stringify(customBackup, undefined, 2));
1086
- },
1087
- "[BACKUP] Current backup file is not for EmberZNet stack.",
1088
- ],
1089
- [
1090
- "if backup unknown format",
1091
- () => {
1092
- const customBackup = deepClone(DEFAULT_BACKUP);
1093
- // @ts-expect-error mock override
1094
- customBackup.metadata.format = "unknown";
1095
-
1096
- writeFileSync(backupPath, JSON.stringify(customBackup, undefined, 2));
1097
- },
1098
- "[BACKUP] Unknown backup format.",
1099
- ],
1100
- ])("Fails to start %s", async (_reason, setup, error) => {
1101
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1102
-
1103
- setup();
1104
-
1105
- const result = defuseRejection(adapter.start());
1106
-
1107
- await vi.advanceTimersByTimeAsync(5000);
1108
- await expect(result).rejects.toThrow(error);
1109
- });
1110
-
1111
- it("Warns if NCP has non-GA firmware", async () => {
1112
- const type: EmberVersionType = EmberVersionType.ALPHA_1;
1113
-
1114
- mockEzspGetVersionStruct.mockResolvedValueOnce([
1115
- SLStatus.OK,
1116
- {
1117
- build: 135,
1118
- major: 8,
1119
- minor: 0,
1120
- patch: 0,
1121
- special: 0,
1122
- type,
1123
- } as EmberVersion,
1124
- ]);
1125
-
1126
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1127
-
1128
- const result = adapter.start();
1129
-
1130
- await vi.advanceTimersByTimeAsync(5000);
1131
- await expect(result).resolves.toStrictEqual("resumed");
1132
-
1133
- expect(loggerSpies.warning).toHaveBeenCalledWith(`Adapter is running a non-GA version (${EmberVersionType[type]}).`, "zh:ember");
1134
- });
1135
-
1136
- it("Switches EZSP protocol when supported", async () => {
1137
- mockEzspVersion.mockResolvedValueOnce([EZSP_MIN_PROTOCOL_VERSION, EZSP_STACK_TYPE_MESH, 123]);
1138
-
1139
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1140
-
1141
- const result = adapter.start();
1142
-
1143
- await vi.advanceTimersByTimeAsync(5000);
1144
- await expect(result).resolves.toStrictEqual("resumed");
1145
- expect(mockEzspVersion).toHaveBeenNthCalledWith(1, EZSP_PROTOCOL_VERSION);
1146
- expect(mockEzspVersion).toHaveBeenNthCalledWith(2, EZSP_MIN_PROTOCOL_VERSION);
1147
- expect(mockEzspSetProtocolVersion).toHaveBeenCalledWith(EZSP_MIN_PROTOCOL_VERSION);
1148
- });
1149
-
1150
- it("Logs failed set config value on start", async () => {
1151
- mockEzspSetConfigurationValue.mockResolvedValueOnce(SLStatus.ALLOCATION_FAILED);
1152
-
1153
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1154
-
1155
- const result = adapter.start();
1156
-
1157
- await vi.advanceTimersByTimeAsync(5000);
1158
- await expect(result).resolves.toStrictEqual("resumed");
1159
-
1160
- expect(loggerSpies.info).toHaveBeenCalledWith(
1161
- `[EzspConfigId] Failed to SET '${EzspConfigId[EzspConfigId.TRUST_CENTER_ADDRESS_CACHE_SIZE]}' TO '2' with status=${SLStatus[SLStatus.ALLOCATION_FAILED]}. Firmware value will be used instead.`,
1162
- "zh:ember",
1163
- );
1164
- });
1165
-
1166
- it("Starts and skips adding endpoint if already present", async () => {
1167
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1168
-
1169
- mockEzspGetEndpointFlags
1170
- .mockResolvedValueOnce([SLStatus.NOT_FOUND, EzspEndpointFlag.DISABLED])
1171
- .mockResolvedValueOnce([SLStatus.OK, EzspEndpointFlag.ENABLED]); // mock GP already registered
1172
-
1173
- const result = adapter.start();
1174
-
1175
- await vi.advanceTimersByTimeAsync(5000);
1176
- await expect(result).resolves.toStrictEqual("resumed");
1177
- expect(mockEzspAddEndpoint).toHaveBeenCalledTimes(1);
1178
- const ep = FIXED_ENDPOINTS[0];
1179
- expect(mockEzspAddEndpoint).toHaveBeenCalledWith(
1180
- ep.endpoint,
1181
- ep.profileId,
1182
- ep.deviceId,
1183
- ep.deviceVersion,
1184
- ep.inClusterList.slice(), // copy
1185
- ep.outClusterList.slice(), // copy
1186
- );
1187
- });
1188
-
1189
- it("Starts and detects when network key frame counter will soon wrap to 0", async () => {
1190
- const customBackup = deepClone(DEFAULT_BACKUP);
1191
- customBackup.network_key.frame_counter = 0xfeeeeeef;
1192
-
1193
- writeFileSync(backupPath, JSON.stringify(customBackup, undefined, 2));
1194
-
1195
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1196
- const result = adapter.start();
1197
-
1198
- await vi.advanceTimersByTimeAsync(5000);
1199
- await expect(result).resolves.toStrictEqual("resumed");
1200
- expect(logger.warning).toHaveBeenCalledWith(
1201
- "[INIT TC] Network key frame counter is reaching its limit. A new network key will have to be instaured soon.",
1202
- "zh:ember",
1203
- );
1204
- });
1205
-
1206
- it("Starts and soft-fails if unable to clear key table", async () => {
1207
- takeResetCodePath();
1208
- mockEzspClearKeyTable.mockResolvedValueOnce(SLStatus.FAIL);
1209
-
1210
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1211
- const result = adapter.start();
1212
-
1213
- await vi.advanceTimersByTimeAsync(5000);
1214
- await expect(result).resolves.toStrictEqual("reset");
1215
- expect(loggerSpies.error).toHaveBeenCalledWith("[INIT FORM] Failed to clear key table with status=FAIL.", "zh:ember");
1216
- });
1217
-
1218
- it("Starts but ignores backup if unsupported version", async () => {
1219
- const customBackup = deepClone(DEFAULT_BACKUP);
1220
- customBackup.metadata.internal.ezspVersion = 11;
1221
-
1222
- writeFileSync(backupPath, JSON.stringify(customBackup, undefined, 2));
1223
-
1224
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1225
- const result = adapter.start();
1226
- const old = `${backupPath}.old`;
1227
-
1228
- await vi.advanceTimersByTimeAsync(5000);
1229
- await expect(result).resolves.toStrictEqual("resumed");
1230
- expect(existsSync(old)).toBeTruthy();
1231
- expect(loggerSpies.warning).toHaveBeenCalledWith(
1232
- "[BACKUP] Current backup file is from an unsupported EZSP version. Renaming and ignoring.",
1233
- "zh:ember",
1234
- );
1235
-
1236
- // cleanup
1237
- unlinkSync(old);
1238
- });
1239
-
1240
- describe("When started", () => {
1241
- beforeEach(async () => {
1242
- adapter = new EmberAdapter(DEFAULT_NETWORK_OPTIONS, DEFAULT_SERIAL_PORT_OPTIONS, backupPath, DEFAULT_ADAPTER_OPTIONS);
1243
-
1244
- const result = adapter.start();
1245
-
1246
- await vi.advanceTimersByTimeAsync(5000);
1247
- await result;
1248
-
1249
- // clean slate "post-start"
1250
- clearMocks();
1251
- });
1252
-
1253
- it("Stops Ezsp layer on stop", async () => {
1254
- // @ts-expect-error private
1255
- const ezspStopSpy = vi.spyOn(adapter.ezsp, "stop");
1256
- // @ts-expect-error private
1257
- const ezspRemoveAllListenersSpy = vi.spyOn(adapter.ezsp, "removeAllListeners");
1258
-
1259
- await adapter.stop();
1260
-
1261
- expect(ezspStopSpy).toHaveBeenCalledTimes(1);
1262
- expect(ezspRemoveAllListenersSpy).toHaveBeenCalledTimes(1);
1263
- });
1264
-
1265
- it("Retrieves parameters from cache when cache valid", async () => {
1266
- await expect(adapter.emberGetEui64()).resolves.toStrictEqual(DEFAULT_COORDINATOR_IEEE);
1267
- expect(mockEzspGetEui64).toHaveBeenCalledTimes(0);
1268
-
1269
- await expect(adapter.emberGetPanId()).resolves.toStrictEqual(DEFAULT_NETWORK_OPTIONS.panID);
1270
- expect(mockEzspGetNetworkParameters).toHaveBeenCalledTimes(0);
1271
-
1272
- await expect(adapter.emberGetExtendedPanId()).resolves.toStrictEqual(DEFAULT_NETWORK_OPTIONS.extendedPanID!);
1273
- expect(mockEzspGetNetworkParameters).toHaveBeenCalledTimes(0);
1274
-
1275
- await expect(adapter.emberGetRadioChannel()).resolves.toStrictEqual(DEFAULT_NETWORK_OPTIONS.channelList[0]);
1276
- expect(mockEzspGetNetworkParameters).toHaveBeenCalledTimes(0);
1277
- });
1278
-
1279
- it("Retrieves parameters from NCP when cache invalid", async () => {
1280
- adapter.clearNetworkCache();
1281
- await expect(adapter.emberGetEui64()).resolves.toStrictEqual(DEFAULT_COORDINATOR_IEEE);
1282
- expect(mockEzspGetEui64).toHaveBeenCalledTimes(1);
1283
-
1284
- adapter.clearNetworkCache();
1285
- await expect(adapter.emberGetPanId()).resolves.toStrictEqual(DEFAULT_NETWORK_OPTIONS.panID);
1286
- expect(mockEzspGetNetworkParameters).toHaveBeenCalledTimes(1);
1287
-
1288
- adapter.clearNetworkCache();
1289
- await expect(adapter.emberGetExtendedPanId()).resolves.toStrictEqual(DEFAULT_NETWORK_OPTIONS.extendedPanID!);
1290
- expect(mockEzspGetNetworkParameters).toHaveBeenCalledTimes(2);
1291
-
1292
- adapter.clearNetworkCache();
1293
- await expect(adapter.emberGetRadioChannel()).resolves.toStrictEqual(DEFAULT_NETWORK_OPTIONS.channelList[0]);
1294
- expect(mockEzspGetNetworkParameters).toHaveBeenCalledTimes(3);
1295
- });
1296
-
1297
- it("Throws when failed to retrieve parameter from NCP", async () => {
1298
- mockEzspGetNetworkParameters
1299
- .mockResolvedValueOnce([SLStatus.FAIL, 0, {}])
1300
- .mockResolvedValueOnce([SLStatus.FAIL, 0, {}])
1301
- .mockResolvedValueOnce([SLStatus.FAIL, 0, {}]);
1302
-
1303
- adapter.clearNetworkCache();
1304
-
1305
- const p1 = defuseRejection(adapter.emberGetPanId());
1306
-
1307
- await vi.advanceTimersByTimeAsync(5000);
1308
- await expect(p1).rejects.toThrow("Failed to get PAN ID (via network parameters) with status=FAIL.");
1309
-
1310
- adapter.clearNetworkCache();
1311
-
1312
- const p2 = defuseRejection(adapter.emberGetExtendedPanId());
1313
-
1314
- await vi.advanceTimersByTimeAsync(5000);
1315
- await expect(p2).rejects.toThrow("Failed to get Extended PAN ID (via network parameters) with status=FAIL.");
1316
-
1317
- adapter.clearNetworkCache();
1318
-
1319
- const p3 = defuseRejection(adapter.emberGetRadioChannel());
1320
-
1321
- await vi.advanceTimersByTimeAsync(5000);
1322
- await expect(p3).rejects.toThrow("Failed to get radio channel (via network parameters) with status=FAIL.");
1323
- });
1324
-
1325
- it("Logs stack status change", async () => {
1326
- mockEzspEmitter.emit("stackStatus", SLStatus.ZIGBEE_TRUST_CENTER_SWAP_EUI_HAS_CHANGED);
1327
- await flushPromises();
1328
-
1329
- expect(loggerSpies.debug).toHaveBeenCalledWith(
1330
- `[STACK STATUS] ${SLStatus[SLStatus.ZIGBEE_TRUST_CENTER_SWAP_EUI_HAS_CHANGED]}.`,
1331
- "zh:ember",
1332
- );
1333
- });
1334
-
1335
- it("Handles message delivery failure", async () => {
1336
- let apsFrame: EmberApsFrame = {
1337
- profileId: ZSpec.HA_PROFILE_ID,
1338
- clusterId: Zcl.Clusters.genBasic.ID,
1339
- sourceEndpoint: 1,
1340
- destinationEndpoint: 1,
1341
- options: 0,
1342
- groupId: 0,
1343
- sequence: 0,
1344
- };
1345
-
1346
- mockEzspEmitter.emit("messageSent", SLStatus.ZIGBEE_DELIVERY_FAILED, EmberOutgoingMessageType.BROADCAST, 1234, apsFrame, 1);
1347
- await flushPromises();
1348
-
1349
- expect(loggerSpies.error).toHaveBeenCalledWith(`Delivery of BROADCAST failed for '1234'.`, "zh:ember");
1350
-
1351
- const spyDeliveryFailedFor = vi.spyOn(
1352
- // @ts-expect-error private
1353
- adapter.oneWaitress,
1354
- "deliveryFailedFor",
1355
- );
1356
-
1357
- apsFrame = {
1358
- profileId: ZSpec.HA_PROFILE_ID,
1359
- clusterId: Zcl.Clusters.genBasic.ID,
1360
- sourceEndpoint: 1,
1361
- destinationEndpoint: 1,
1362
- options: 0,
1363
- groupId: 0,
1364
- sequence: 0,
1365
- };
1366
-
1367
- mockEzspEmitter.emit("messageSent", SLStatus.ZIGBEE_DELIVERY_FAILED, EmberOutgoingMessageType.DIRECT, 1234, apsFrame, 1);
1368
- await flushPromises();
1369
-
1370
- expect(spyDeliveryFailedFor).toHaveBeenCalledTimes(1);
1371
- expect(spyDeliveryFailedFor).toHaveBeenCalledWith(1234, apsFrame);
1372
- });
1373
-
1374
- it("Registers message unknown group in multicast table", async () => {
1375
- // @ts-expect-error private
1376
- const tableIdx = adapter.multicastTable.length;
1377
- const apsFrame = {
1378
- profileId: ZSpec.HA_PROFILE_ID,
1379
- clusterId: Zcl.Clusters.genBasic.ID,
1380
- sourceEndpoint: 1,
1381
- destinationEndpoint: 0xff,
1382
- options: 0,
1383
- groupId: 123,
1384
- sequence: 0,
1385
- };
1386
-
1387
- mockEzspEmitter.emit("messageSent", SLStatus.OK, EmberOutgoingMessageType.MULTICAST, 1234, apsFrame, 1);
1388
- await flushPromises();
1389
-
1390
- expect(mockEzspSetMulticastTableEntry).toHaveBeenCalledTimes(1);
1391
- expect(mockEzspSetMulticastTableEntry).toHaveBeenCalledWith(tableIdx, {
1392
- multicastId: 123,
1393
- endpoint: FIXED_ENDPOINTS[0].endpoint,
1394
- networkIndex: FIXED_ENDPOINTS[0].networkIndex,
1395
- } as EmberMulticastTableEntry);
1396
- expect(
1397
- // @ts-expect-error private
1398
- adapter.multicastTable.length,
1399
- ).toStrictEqual(tableIdx + 1);
1400
- });
1401
-
1402
- it("Fails to register message unknown group in multicast table", async () => {
1403
- mockEzspSetMulticastTableEntry.mockResolvedValueOnce(SLStatus.FAIL);
1404
-
1405
- // @ts-expect-error private
1406
- const tableIdx = adapter.multicastTable.length;
1407
- const apsFrame = {
1408
- profileId: ZSpec.HA_PROFILE_ID,
1409
- clusterId: Zcl.Clusters.genBasic.ID,
1410
- sourceEndpoint: 1,
1411
- destinationEndpoint: 0xff,
1412
- options: 0,
1413
- groupId: 123,
1414
- sequence: 0,
1415
- };
1416
-
1417
- mockEzspEmitter.emit("messageSent", SLStatus.OK, EmberOutgoingMessageType.MULTICAST, 1234, apsFrame, 1);
1418
- await flushPromises();
1419
-
1420
- expect(mockEzspSetMulticastTableEntry).toHaveBeenCalledTimes(1);
1421
- expect(mockEzspSetMulticastTableEntry).toHaveBeenCalledWith(tableIdx, {
1422
- multicastId: 123,
1423
- endpoint: FIXED_ENDPOINTS[0].endpoint,
1424
- networkIndex: FIXED_ENDPOINTS[0].networkIndex,
1425
- } as EmberMulticastTableEntry);
1426
- expect(
1427
- // @ts-expect-error private
1428
- adapter.multicastTable.length,
1429
- ).toStrictEqual(tableIdx); // not increased, entry was removed
1430
- });
1431
-
1432
- it("Emits network address event on ZDO NETWORK_ADDRESS_RESPONSE", async () => {
1433
- const spyResolveZDO = vi.spyOn(
1434
- // @ts-expect-error private
1435
- adapter.oneWaitress,
1436
- "resolveZDO",
1437
- );
1438
- const spyEmit = vi.spyOn(adapter, "emit");
1439
- const sender = 1234;
1440
- const apsFrame: EmberApsFrame = {
1441
- profileId: Zdo.ZDO_PROFILE_ID,
1442
- clusterId: Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE,
1443
- sourceEndpoint: Zdo.ZDO_ENDPOINT,
1444
- destinationEndpoint: Zdo.ZDO_ENDPOINT,
1445
- options: 0,
1446
- groupId: 0,
1447
- sequence: 0,
1448
- };
1449
-
1450
- mockEzspEmitter.emit(
1451
- "zdoResponse",
1452
- apsFrame,
1453
- sender,
1454
- Buffer.from([1, Zdo.Status.SUCCESS, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x11, 0x22, 0x33, 0xd2, 0x04]),
1455
- );
1456
- await flushPromises();
1457
-
1458
- const zdoResponse = [
1459
- Zdo.Status.SUCCESS,
1460
- {
1461
- eui64: "0x332211eeddccbbaa",
1462
- nwkAddress: sender,
1463
- startIndex: 0,
1464
- assocDevList: [],
1465
- } as ZdoTypes.NetworkAddressResponse,
1466
- ];
1467
-
1468
- expect(spyResolveZDO).toHaveBeenCalledTimes(1);
1469
- expect(spyResolveZDO).toHaveBeenCalledWith("0x332211eeddccbbaa", apsFrame, zdoResponse);
1470
- expect(spyEmit).toHaveBeenCalledWith("zdoResponse", Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE, zdoResponse);
1471
- });
1472
-
1473
- it("Emits device announce event on ZDO END_DEVICE_ANNOUNCE", async () => {
1474
- const spyResolveZDO = vi.spyOn(
1475
- // @ts-expect-error private
1476
- adapter.oneWaitress,
1477
- "resolveZDO",
1478
- );
1479
- const spyEmit = vi.spyOn(adapter, "emit");
1480
- const sender = 1234;
1481
- const apsFrame: EmberApsFrame = {
1482
- profileId: Zdo.ZDO_PROFILE_ID,
1483
- clusterId: Zdo.ClusterId.END_DEVICE_ANNOUNCE,
1484
- sourceEndpoint: Zdo.ZDO_ENDPOINT,
1485
- destinationEndpoint: Zdo.ZDO_ENDPOINT,
1486
- options: 0,
1487
- groupId: 0,
1488
- sequence: 0,
1489
- };
1490
-
1491
- mockEzspEmitter.emit("zdoResponse", apsFrame, sender, Buffer.from([1, 0xd2, 0x04, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x11, 0x22, 0x33, 6]));
1492
-
1493
- await flushPromises();
1494
-
1495
- const zdoResponse = [
1496
- Zdo.Status.SUCCESS,
1497
- {
1498
- nwkAddress: sender,
1499
- eui64: "0x332211eeddccbbaa",
1500
- capabilities: {
1501
- alternatePANCoordinator: 0,
1502
- deviceType: 1,
1503
- powerSource: 1,
1504
- rxOnWhenIdle: 0,
1505
- reserved1: 0,
1506
- reserved2: 0,
1507
- securityCapability: 0,
1508
- allocateAddress: 0,
1509
- },
1510
- } as ZdoTypes.EndDeviceAnnounce,
1511
- ];
1512
- expect(spyResolveZDO).toHaveBeenCalledTimes(1);
1513
- expect(spyResolveZDO).toHaveBeenCalledWith(sender, apsFrame, zdoResponse);
1514
- expect(spyEmit).toHaveBeenCalledWith("zdoResponse", Zdo.ClusterId.END_DEVICE_ANNOUNCE, zdoResponse);
1515
- });
1516
-
1517
- it("Emits ZCL payload on incoming message", async () => {
1518
- const spyResolveZCL = vi.spyOn(
1519
- // @ts-expect-error private
1520
- adapter.oneWaitress,
1521
- "resolveZCL",
1522
- );
1523
- const spyEmit = vi.spyOn(adapter, "emit");
1524
- const sender = 1234;
1525
- const apsFrame: EmberApsFrame = {
1526
- profileId: ZSpec.HA_PROFILE_ID,
1527
- clusterId: Zcl.Clusters.genBasic.ID,
1528
- sourceEndpoint: 2,
1529
- destinationEndpoint: 1,
1530
- options: 0,
1531
- groupId: 0,
1532
- sequence: 0,
1533
- };
1534
- const lastHopLqi = 252;
1535
- // Received Zigbee message from '0x', type 'readResponse', cluster 'genBasic', data '{"zclVersion":3}' from endpoint 1 with groupID 0
1536
- const messageContents = Buffer.from("1803010000002003", "hex");
1537
-
1538
- mockEzspEmitter.emit("incomingMessage", EmberIncomingMessageType.UNICAST, apsFrame, lastHopLqi, sender, messageContents);
1539
- await flushPromises();
1540
-
1541
- const payload: ZclPayload = {
1542
- clusterID: apsFrame.clusterId,
1543
- header: Zcl.Header.fromBuffer(messageContents),
1544
- address: sender,
1545
- data: messageContents,
1546
- endpoint: apsFrame.sourceEndpoint,
1547
- linkquality: lastHopLqi,
1548
- groupID: apsFrame.groupId,
1549
- wasBroadcast: false,
1550
- destinationEndpoint: apsFrame.destinationEndpoint,
1551
- };
1552
-
1553
- expect(spyResolveZCL).toHaveBeenCalledTimes(1);
1554
- expect(spyResolveZCL).toHaveBeenCalledWith(payload);
1555
- expect(spyEmit).toHaveBeenCalledWith("zclPayload", payload);
1556
- });
1557
-
1558
- it("Emits ZCL payload on touchlink message", async () => {
1559
- const spyResolveZCL = vi.spyOn(
1560
- // @ts-expect-error private
1561
- adapter.oneWaitress,
1562
- "resolveZCL",
1563
- );
1564
- const spyEmit = vi.spyOn(adapter, "emit");
1565
- const sourcePanId: PanId = 0x1234;
1566
- const sourceAddress: Eui64 = "0x1122334455aabbcc";
1567
- const lastHopLqi = 252;
1568
- const groupId: number = 0;
1569
- const messageContents = Buffer.from("1803010000002003", "hex");
1570
-
1571
- mockEzspEmitter.emit("touchlinkMessage", sourcePanId, sourceAddress, groupId, lastHopLqi, messageContents);
1572
- await flushPromises();
1573
-
1574
- const payload: ZclPayload = {
1575
- clusterID: Zcl.Clusters.touchlink.ID,
1576
- header: Zcl.Header.fromBuffer(messageContents),
1577
- address: sourceAddress,
1578
- data: messageContents,
1579
- endpoint: 1,
1580
- linkquality: lastHopLqi,
1581
- groupID: groupId,
1582
- wasBroadcast: true,
1583
- destinationEndpoint: FIXED_ENDPOINTS[0].endpoint,
1584
- };
1585
-
1586
- expect(spyResolveZCL).toHaveBeenCalledTimes(1);
1587
- expect(spyResolveZCL).toHaveBeenCalledWith(payload);
1588
- expect(spyEmit).toHaveBeenCalledWith("zclPayload", payload);
1589
- });
1590
-
1591
- it("Emits ZCL payload on greenpower message", async () => {
1592
- const spyResolveZCL = vi.spyOn(
1593
- // @ts-expect-error private
1594
- adapter.oneWaitress,
1595
- "resolveZCL",
1596
- );
1597
- const spyEmit = vi.spyOn(adapter, "emit");
1598
- const sourceId: number = 1234;
1599
- const nwkAddress: NodeId = sourceId & 0xffff;
1600
- const gpdLink: number = 123;
1601
- const sequenceNumber: number = 1;
1602
- const commandIdentifier: number = Zcl.Clusters.greenPower.commands.commissioningNotification.ID;
1603
- const frameCounter: number = 102;
1604
- const gpdCommandId: number = 0xe0;
1605
- const gpdCommandPayload = Buffer.from([
1606
- 0x02 /* deviceID */,
1607
- 0x83 /* options */,
1608
- 0xf2 /* extendedOptions */,
1609
- ...[0xf1, 0xec, 0x92, 0xab, 0xff, 0x8f, 0x13, 0x63, 0xe1, 0x46, 0xbe, 0xb5, 0x18, 0xc9, 0x0c, 0xab] /* securityKey */,
1610
- 0xa4,
1611
- 0x46,
1612
- 0xd4,
1613
- 0xd5 /* keyMic */,
1614
- 0xe4,
1615
- 0x04,
1616
- 0x00,
1617
- 0x00 /* outgoingCounter */,
1618
- ]);
1619
- const gpdHeader = Buffer.alloc(15);
1620
- gpdHeader.writeUInt8(0b00000001, 0);
1621
- gpdHeader.writeUInt8(sequenceNumber, 1);
1622
- gpdHeader.writeUInt8(commandIdentifier, 2);
1623
- gpdHeader.writeUInt16LE(0, 3);
1624
- gpdHeader.writeUInt32LE(sourceId, 5);
1625
- gpdHeader.writeUInt32LE(frameCounter, 9);
1626
- gpdHeader.writeUInt8(gpdCommandId, 13);
1627
- gpdHeader.writeUInt8(gpdCommandPayload.length, 14);
1628
- const messageContents = Buffer.concat([gpdHeader, gpdCommandPayload]);
1629
- const apsFrame: EmberApsFrame = {
1630
- profileId: ZSpec.GP_PROFILE_ID,
1631
- clusterId: Zcl.Clusters.greenPower.ID,
1632
- sourceEndpoint: ZSpec.GP_ENDPOINT,
1633
- destinationEndpoint: ZSpec.GP_ENDPOINT,
1634
- options: 0, // not used
1635
- groupId: ZSpec.GP_GROUP_ID,
1636
- sequence: 0, // not used
1637
- };
1638
-
1639
- mockEzspEmitter.emit("incomingMessage", EmberIncomingMessageType.BROADCAST, apsFrame, gpdLink, nwkAddress, messageContents);
1640
- await flushPromises();
1641
-
1642
- const payload: ZclPayload = {
1643
- header: Zcl.Header.fromBuffer(messageContents),
1644
- data: messageContents,
1645
- clusterID: apsFrame.clusterId,
1646
- address: nwkAddress,
1647
- endpoint: apsFrame.sourceEndpoint,
1648
- linkquality: gpdLink,
1649
- groupID: apsFrame.groupId,
1650
- wasBroadcast: true,
1651
- destinationEndpoint: apsFrame.destinationEndpoint,
1652
- };
1653
-
1654
- expect(spyResolveZCL).toHaveBeenCalledTimes(1);
1655
- expect(spyResolveZCL).toHaveBeenCalledWith(payload);
1656
- expect(spyEmit).toHaveBeenCalledWith("zclPayload", payload);
1657
- });
1658
-
1659
- it("Emits device joined on trust center join", async () => {
1660
- const spyEmit = vi.spyOn(adapter, "emit");
1661
- const newNodeId: NodeId = 1234;
1662
- const newNodeEui64: Eui64 = "0x11223344eebbccaa";
1663
- const status: EmberDeviceUpdate = EmberDeviceUpdate.STANDARD_SECURITY_UNSECURED_JOIN;
1664
- const policyDecision: EmberJoinDecision = EmberJoinDecision.USE_PRECONFIGURED_KEY;
1665
- const parentOfNewNodeId: NodeId = 4321;
1666
-
1667
- mockEzspEmitter.emit("trustCenterJoin", newNodeId, newNodeEui64, status, policyDecision, parentOfNewNodeId);
1668
- await flushPromises();
1669
-
1670
- expect(spyEmit).toHaveBeenCalledWith("deviceJoined", {
1671
- networkAddress: newNodeId,
1672
- ieeeAddr: newNodeEui64,
1673
- } as DeviceJoinedPayload);
1674
- });
1675
-
1676
- it("Emits device leave on trust center join", async () => {
1677
- const spyEmit = vi.spyOn(adapter, "emit");
1678
- const newNodeId: NodeId = 1234;
1679
- const newNodeEui64: Eui64 = "0x11223344eebbccaa";
1680
- const status: EmberDeviceUpdate = EmberDeviceUpdate.DEVICE_LEFT;
1681
- const policyDecision: EmberJoinDecision = EmberJoinDecision.NO_ACTION;
1682
- const parentOfNewNodeId: NodeId = 0xffff;
1683
-
1684
- mockEzspEmitter.emit("trustCenterJoin", newNodeId, newNodeEui64, status, policyDecision, parentOfNewNodeId);
1685
- await flushPromises();
1686
-
1687
- expect(spyEmit).toHaveBeenCalledWith("deviceLeave", {
1688
- networkAddress: newNodeId,
1689
- ieeeAddr: newNodeEui64,
1690
- } as DeviceLeavePayload);
1691
- });
1692
-
1693
- it("Handles DENY_JOIN on trust center join", async () => {
1694
- const newNodeId: NodeId = 1234;
1695
- const newNodeEui64: Eui64 = "0x11223344eebbccaa";
1696
- const status: EmberDeviceUpdate = EmberDeviceUpdate.STANDARD_SECURITY_UNSECURED_JOIN;
1697
- const policyDecision: EmberJoinDecision = EmberJoinDecision.DENY_JOIN;
1698
- const parentOfNewNodeId: NodeId = 4321;
1699
-
1700
- mockEzspEmitter.emit("trustCenterJoin", newNodeId, newNodeEui64, status, policyDecision, parentOfNewNodeId);
1701
- await flushPromises();
1702
-
1703
- expect(loggerSpies.warning).toHaveBeenCalledWith(
1704
- `[TRUST CENTER] Device ${newNodeId}:${newNodeEui64} was denied joining via ${parentOfNewNodeId}.`,
1705
- "zh:ember",
1706
- );
1707
- });
1708
-
1709
- it("Handles device join workaround requiring specific manufacturer code", async () => {
1710
- const spyEmit = vi.spyOn(adapter, "emit");
1711
- const newNodeId: NodeId = 1234;
1712
- const newNodeEui64: Eui64 = "0x54ef44ffeebbccaa";
1713
- const status: EmberDeviceUpdate = EmberDeviceUpdate.STANDARD_SECURITY_UNSECURED_JOIN;
1714
- const policyDecision: EmberJoinDecision = EmberJoinDecision.USE_PRECONFIGURED_KEY;
1715
- const parentOfNewNodeId: NodeId = 4321;
1716
-
1717
- mockEzspEmitter.emit("trustCenterJoin", newNodeId, newNodeEui64, status, policyDecision, parentOfNewNodeId);
1718
- await flushPromises();
1719
-
1720
- expect(spyEmit).toHaveBeenCalledWith("deviceJoined", {
1721
- networkAddress: newNodeId,
1722
- ieeeAddr: newNodeEui64,
1723
- } as DeviceJoinedPayload);
1724
- expect(mockEzspSetManufacturerCode).toHaveBeenCalledWith(Zcl.ManufacturerCode.LUMI_UNITED_TECHOLOGY_LTD_SHENZHEN);
1725
- expect(mockManufCode).toStrictEqual(Zcl.ManufacturerCode.LUMI_UNITED_TECHOLOGY_LTD_SHENZHEN);
1726
- });
1727
-
1728
- it("Triggers watchdog counters", async () => {
1729
- await vi.advanceTimersByTimeAsync(3610000);
1730
- expect(mockEzspReadAndClearCounters).toHaveBeenCalledTimes(1);
1731
- expect(loggerSpies.info).toHaveBeenCalledTimes(2);
1732
- expect(loggerSpies.info.mock.calls[0][0]).toMatch(/[NCP COUNTERS]/);
1733
- expect(loggerSpies.info.mock.calls[1][0]).toMatch(/[ASH COUNTERS]/);
1734
- });
1735
-
1736
- it("Exports link keys", async () => {
1737
- const k1Context: SecManContext = {
1738
- coreKeyType: SecManKeyType.APP_LINK,
1739
- keyIndex: 0,
1740
- derivedType: SecManDerivedKeyType.NONE,
1741
- eui64: "0x1122334455667788",
1742
- multiNetworkIndex: 0,
1743
- flags: SecManFlag.EUI_IS_VALID | SecManFlag.KEY_INDEX_IS_VALID,
1744
- psaKeyAlgPermission: 0,
1745
- };
1746
- const k1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
1747
- const k1Hashed = ZSpec.Utils.aes128MmoHash(k1);
1748
- const k1Metadata: SecManAPSKeyMetadata = {
1749
- bitmask: EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER | EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
1750
- outgoingFrameCounter: 1,
1751
- incomingFrameCounter: 2,
1752
- ttlInSeconds: 0,
1753
- };
1754
- const k2Context: SecManContext = {
1755
- coreKeyType: SecManKeyType.APP_LINK,
1756
- keyIndex: 1,
1757
- derivedType: SecManDerivedKeyType.NONE,
1758
- eui64: "0x2233445566778899",
1759
- multiNetworkIndex: 0,
1760
- flags: SecManFlag.EUI_IS_VALID | SecManFlag.KEY_INDEX_IS_VALID,
1761
- psaKeyAlgPermission: 0,
1762
- };
1763
- const k2 = Buffer.from([2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]);
1764
- const k2Hashed = ZSpec.Utils.aes128MmoHash(k2);
1765
- const k2Metadata: SecManAPSKeyMetadata = {
1766
- bitmask: EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER | EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
1767
- outgoingFrameCounter: 10,
1768
- incomingFrameCounter: 20,
1769
- ttlInSeconds: 0,
1770
- };
1771
- const k3Context: SecManContext = {
1772
- coreKeyType: SecManKeyType.APP_LINK,
1773
- keyIndex: 2,
1774
- derivedType: SecManDerivedKeyType.NONE,
1775
- eui64: "0x3344556677889900",
1776
- multiNetworkIndex: 0,
1777
- flags: SecManFlag.EUI_IS_VALID | SecManFlag.KEY_INDEX_IS_VALID,
1778
- psaKeyAlgPermission: 0,
1779
- };
1780
- const k3 = Buffer.from([3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
1781
- const k3Hashed = ZSpec.Utils.aes128MmoHash(k3);
1782
- const k3Metadata: SecManAPSKeyMetadata = {
1783
- bitmask: EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER | EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
1784
- outgoingFrameCounter: 100,
1785
- incomingFrameCounter: 200,
1786
- ttlInSeconds: 0,
1787
- };
1788
-
1789
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.OK, 3]);
1790
- mockEzspExportLinkKeyByIndex
1791
- .mockResolvedValueOnce([SLStatus.OK, k1Context, {contents: k1} as SecManKey, k1Metadata])
1792
- .mockResolvedValueOnce([SLStatus.OK, k2Context, {contents: k2} as SecManKey, k2Metadata])
1793
- .mockResolvedValueOnce([SLStatus.OK, k3Context, {contents: k3} as SecManKey, k3Metadata]);
1794
-
1795
- const keys = await adapter.exportLinkKeys();
1796
-
1797
- expect(mockEzspExportLinkKeyByIndex).toHaveBeenCalledTimes(3);
1798
- expect(keys).toStrictEqual([
1799
- {
1800
- deviceEui64: k1Context.eui64,
1801
- key: {contents: k1Hashed},
1802
- outgoingFrameCounter: k1Metadata.outgoingFrameCounter,
1803
- incomingFrameCounter: k1Metadata.incomingFrameCounter,
1804
- } as LinkKeyBackupData,
1805
- {
1806
- deviceEui64: k2Context.eui64,
1807
- key: {contents: k2Hashed},
1808
- outgoingFrameCounter: k2Metadata.outgoingFrameCounter,
1809
- incomingFrameCounter: k2Metadata.incomingFrameCounter,
1810
- } as LinkKeyBackupData,
1811
- {
1812
- deviceEui64: k3Context.eui64,
1813
- key: {contents: k3Hashed},
1814
- outgoingFrameCounter: k3Metadata.outgoingFrameCounter,
1815
- incomingFrameCounter: k3Metadata.incomingFrameCounter,
1816
- } as LinkKeyBackupData,
1817
- ]);
1818
- });
1819
-
1820
- it("Exports zero link keys", async () => {
1821
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.OK, 0]);
1822
- const keys = await adapter.exportLinkKeys();
1823
-
1824
- expect(keys).toStrictEqual([]);
1825
- });
1826
-
1827
- it("Fails to export link keys due to failed table size retrieval", async () => {
1828
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.FAIL, 0]);
1829
-
1830
- await expect(adapter.exportLinkKeys()).rejects.toThrow("[BACKUP] Failed to retrieve key table size from NCP with status=FAIL.");
1831
- });
1832
-
1833
- it("Imports link keys", async () => {
1834
- const k1Context: SecManContext = {
1835
- coreKeyType: SecManKeyType.APP_LINK,
1836
- keyIndex: 0,
1837
- derivedType: SecManDerivedKeyType.NONE,
1838
- eui64: "0x1122334455667788",
1839
- multiNetworkIndex: 0,
1840
- flags: SecManFlag.EUI_IS_VALID | SecManFlag.KEY_INDEX_IS_VALID,
1841
- psaKeyAlgPermission: 0,
1842
- };
1843
- const k1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
1844
- const k1Metadata: SecManAPSKeyMetadata = {
1845
- bitmask: EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER | EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
1846
- outgoingFrameCounter: 1,
1847
- incomingFrameCounter: 2,
1848
- ttlInSeconds: 0,
1849
- };
1850
- const k2Context: SecManContext = {
1851
- coreKeyType: SecManKeyType.APP_LINK,
1852
- keyIndex: 1,
1853
- derivedType: SecManDerivedKeyType.NONE,
1854
- eui64: "0x2233445566778899",
1855
- multiNetworkIndex: 0,
1856
- flags: SecManFlag.EUI_IS_VALID | SecManFlag.KEY_INDEX_IS_VALID,
1857
- psaKeyAlgPermission: 0,
1858
- };
1859
- const k2 = Buffer.from([2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]);
1860
- const k2Metadata: SecManAPSKeyMetadata = {
1861
- bitmask: EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER | EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
1862
- outgoingFrameCounter: 10,
1863
- incomingFrameCounter: 20,
1864
- ttlInSeconds: 0,
1865
- };
1866
- const k3Context: SecManContext = {
1867
- coreKeyType: SecManKeyType.APP_LINK,
1868
- keyIndex: 2,
1869
- derivedType: SecManDerivedKeyType.NONE,
1870
- eui64: "0x3344556677889900",
1871
- multiNetworkIndex: 0,
1872
- flags: SecManFlag.EUI_IS_VALID | SecManFlag.KEY_INDEX_IS_VALID,
1873
- psaKeyAlgPermission: 0,
1874
- };
1875
- const k3 = Buffer.from([3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
1876
- const k3Metadata: SecManAPSKeyMetadata = {
1877
- bitmask: EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER | EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
1878
- outgoingFrameCounter: 100,
1879
- incomingFrameCounter: 200,
1880
- ttlInSeconds: 0,
1881
- };
1882
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.OK, 4]);
1883
- mockEzspNetworkState.mockResolvedValueOnce(EmberNetworkStatus.NO_NETWORK);
1884
-
1885
- await adapter.importLinkKeys([
1886
- {
1887
- deviceEui64: k1Context.eui64,
1888
- key: {contents: k1},
1889
- outgoingFrameCounter: k1Metadata.outgoingFrameCounter,
1890
- incomingFrameCounter: k1Metadata.incomingFrameCounter,
1891
- },
1892
- {
1893
- deviceEui64: k2Context.eui64,
1894
- key: {contents: k2},
1895
- outgoingFrameCounter: k2Metadata.outgoingFrameCounter,
1896
- incomingFrameCounter: k2Metadata.incomingFrameCounter,
1897
- },
1898
- {
1899
- deviceEui64: k3Context.eui64,
1900
- key: {contents: k3},
1901
- outgoingFrameCounter: k3Metadata.outgoingFrameCounter,
1902
- incomingFrameCounter: k3Metadata.incomingFrameCounter,
1903
- },
1904
- ]);
1905
-
1906
- expect(mockEzspImportLinkKey).toHaveBeenCalledTimes(3);
1907
- expect(mockEzspEraseKeyTableEntry).toHaveBeenCalledTimes(1);
1908
- });
1909
-
1910
- it("Imports zero link keys", async () => {
1911
- await expect(adapter.importLinkKeys([])).resolves.toStrictEqual(undefined);
1912
- });
1913
-
1914
- it("Failed to import link keys due to failed table size retrieval", async () => {
1915
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.FAIL, 0]);
1916
-
1917
- await expect(
1918
- adapter.importLinkKeys([
1919
- // @ts-expect-error mock, unnecessary
1920
- {},
1921
- ]),
1922
- ).rejects.toThrow("[BACKUP] Failed to retrieve key table size from NCP with status=FAIL.");
1923
- });
1924
-
1925
- it("Failed to import link keys due to insufficient table size", async () => {
1926
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.OK, 0]);
1927
-
1928
- await expect(
1929
- adapter.importLinkKeys([
1930
- // @ts-expect-error mock, unnecessary
1931
- {},
1932
- ]),
1933
- ).rejects.toThrow("[BACKUP] Current key table of 0 is too small to import backup of 1!");
1934
- });
1935
-
1936
- it("Failed to import link keys due to improper network state", async () => {
1937
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.OK, 3]);
1938
- mockEzspNetworkState.mockResolvedValueOnce(EmberNetworkStatus.JOINED_NETWORK);
1939
-
1940
- await expect(
1941
- adapter.importLinkKeys([
1942
- // @ts-expect-error mock, unnecessary
1943
- {},
1944
- ]),
1945
- ).rejects.toThrow(
1946
- `[BACKUP] Cannot import TC data while network is up, networkStatus=${EmberNetworkStatus[EmberNetworkStatus.JOINED_NETWORK]}.`,
1947
- );
1948
- });
1949
-
1950
- it("Failed to import link keys due to failed key set", async () => {
1951
- const k1Context: SecManContext = {
1952
- coreKeyType: SecManKeyType.APP_LINK,
1953
- keyIndex: 0,
1954
- derivedType: SecManDerivedKeyType.NONE,
1955
- eui64: "0x1122334455667788",
1956
- multiNetworkIndex: 0,
1957
- flags: SecManFlag.EUI_IS_VALID | SecManFlag.KEY_INDEX_IS_VALID,
1958
- psaKeyAlgPermission: 0,
1959
- };
1960
- const k1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
1961
- const k1Metadata: SecManAPSKeyMetadata = {
1962
- bitmask: EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER | EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
1963
- outgoingFrameCounter: 1,
1964
- incomingFrameCounter: 2,
1965
- ttlInSeconds: 0,
1966
- };
1967
-
1968
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.OK, 3]);
1969
- mockEzspNetworkState.mockResolvedValueOnce(EmberNetworkStatus.NO_NETWORK);
1970
- mockEzspImportLinkKey.mockResolvedValueOnce(SLStatus.FAIL);
1971
-
1972
- await expect(
1973
- adapter.importLinkKeys([
1974
- {
1975
- deviceEui64: k1Context.eui64,
1976
- key: {contents: k1},
1977
- outgoingFrameCounter: k1Metadata.outgoingFrameCounter,
1978
- incomingFrameCounter: k1Metadata.incomingFrameCounter,
1979
- },
1980
- ]),
1981
- ).rejects.toThrow("[BACKUP] Failed to set key table entry at index 0 with status=FAIL.");
1982
- });
1983
-
1984
- it("Failed to import link keys due to failed key erase", async () => {
1985
- const k1Context: SecManContext = {
1986
- coreKeyType: SecManKeyType.APP_LINK,
1987
- keyIndex: 0,
1988
- derivedType: SecManDerivedKeyType.NONE,
1989
- eui64: "0x1122334455667788",
1990
- multiNetworkIndex: 0,
1991
- flags: SecManFlag.EUI_IS_VALID | SecManFlag.KEY_INDEX_IS_VALID,
1992
- psaKeyAlgPermission: 0,
1993
- };
1994
- const k1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
1995
- const k1Metadata: SecManAPSKeyMetadata = {
1996
- bitmask: EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER | EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER,
1997
- outgoingFrameCounter: 1,
1998
- incomingFrameCounter: 2,
1999
- ttlInSeconds: 0,
2000
- };
2001
-
2002
- mockEzspGetConfigurationValue.mockResolvedValueOnce([SLStatus.OK, 3]);
2003
- mockEzspNetworkState.mockResolvedValueOnce(EmberNetworkStatus.NO_NETWORK);
2004
- mockEzspEraseKeyTableEntry.mockResolvedValueOnce(SLStatus.FAIL);
2005
-
2006
- await expect(
2007
- adapter.importLinkKeys([
2008
- {
2009
- deviceEui64: k1Context.eui64,
2010
- key: {contents: k1},
2011
- outgoingFrameCounter: k1Metadata.outgoingFrameCounter,
2012
- incomingFrameCounter: k1Metadata.incomingFrameCounter,
2013
- },
2014
- ]),
2015
- ).rejects.toThrow("[BACKUP] Failed to erase key table entry at index 1 with status=FAIL.");
2016
- });
2017
-
2018
- it("Broadcasts network key update", async () => {
2019
- const p = adapter.broadcastNetworkKeyUpdate();
2020
-
2021
- await vi.advanceTimersByTimeAsync(100000);
2022
- await expect(p).resolves.toStrictEqual(undefined);
2023
- expect(mockEzspBroadcastNextNetworkKey).toHaveBeenCalledTimes(1);
2024
- expect(mockEzspBroadcastNetworkKeySwitch).toHaveBeenCalledTimes(1);
2025
- });
2026
-
2027
- it("Fails to broadcast network key update due to failed next key broadcast", async () => {
2028
- mockEzspBroadcastNextNetworkKey.mockResolvedValueOnce(SLStatus.FAIL);
2029
-
2030
- const p = defuseRejection(adapter.broadcastNetworkKeyUpdate());
2031
-
2032
- await vi.advanceTimersByTimeAsync(100000);
2033
- await expect(p).rejects.toThrow("[TRUST CENTER] Failed to broadcast next network key with status=FAIL.");
2034
- expect(mockEzspBroadcastNextNetworkKey).toHaveBeenCalledTimes(1);
2035
- expect(mockEzspBroadcastNetworkKeySwitch).toHaveBeenCalledTimes(0);
2036
- });
2037
-
2038
- it("Fails to broadcast network key update due to failed switch broadcast", async () => {
2039
- mockEzspBroadcastNetworkKeySwitch.mockResolvedValueOnce(SLStatus.FAIL);
2040
-
2041
- const p = defuseRejection(adapter.broadcastNetworkKeyUpdate());
2042
-
2043
- await vi.advanceTimersByTimeAsync(100000);
2044
- await expect(p).rejects.toThrow("[TRUST CENTER] Failed to broadcast network key switch with status=FAIL.");
2045
- expect(mockEzspBroadcastNextNetworkKey).toHaveBeenCalledTimes(1);
2046
- expect(mockEzspBroadcastNetworkKeySwitch).toHaveBeenCalledTimes(1);
2047
- });
2048
-
2049
- it("Handles NCP needing reset & init", async () => {
2050
- const spyEmit = vi.spyOn(adapter, "emit");
2051
-
2052
- mockEzspEmitter.emit("ncpNeedsResetAndInit", EzspStatus.ERROR_SERIAL_INIT);
2053
- await vi.advanceTimersByTimeAsync(5000);
2054
-
2055
- expect(spyEmit).toHaveBeenCalledTimes(1);
2056
- expect(spyEmit).toHaveBeenCalledWith("disconnected");
2057
- });
2058
-
2059
- it("Emits adapter disconnected when NCP needs reset & init but queue is too high", async () => {
2060
- vi.spyOn(
2061
- // @ts-expect-error private
2062
- adapter.queue,
2063
- "count",
2064
- ).mockReturnValueOnce(999);
2065
- const spyEmit = vi.spyOn(adapter, "emit");
2066
-
2067
- mockEzspEmitter.emit("ncpNeedsResetAndInit", EzspStatus.ERROR_SERIAL_INIT);
2068
- await flushPromises();
2069
-
2070
- expect(spyEmit).toHaveBeenCalledWith("disconnected");
2071
- });
2072
-
2073
- it("Emits adapter disconnected when failed to reset & init NCP", async () => {
2074
- vi.spyOn(adapter, "stop").mockRejectedValueOnce(new Error("mock error"));
2075
- const spyEmit = vi.spyOn(adapter, "emit");
2076
-
2077
- mockEzspEmitter.emit("ncpNeedsResetAndInit", EzspStatus.ERROR_SERIAL_INIT);
2078
- await flushPromises();
2079
-
2080
- expect(spyEmit).toHaveBeenCalledWith("disconnected");
2081
- });
2082
-
2083
- it("Handles channel changed stack status", async () => {
2084
- mockEzspEmitter.emit("stackStatus", SLStatus.ZIGBEE_CHANNEL_CHANGED);
2085
- await flushPromises();
2086
- expect(loggerSpies.info).toHaveBeenCalledWith("[STACK STATUS] Channel changed.", "zh:ember");
2087
- });
2088
-
2089
- it.each([
2090
- ["getCoordinatorIEEE", []],
2091
- ["getNetworkParameters", []],
2092
- ["permitJoin", [250, 1234]],
2093
- ["permitJoin", [250]],
2094
- [
2095
- "sendZclFrameToEndpoint",
2096
- [
2097
- "0x1122334455667788",
2098
- 1234,
2099
- 1,
2100
- Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {}),
2101
- 10000,
2102
- true,
2103
- false,
2104
- 1,
2105
- ],
2106
- ],
2107
- [
2108
- "sendZclFrameToGroup",
2109
- [32, Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {}), 1],
2110
- ],
2111
- [
2112
- "sendZclFrameToAll",
2113
- [
2114
- 1,
2115
- Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {}),
2116
- 1,
2117
- ZSpec.BroadcastAddress.DEFAULT,
2118
- ],
2119
- ],
2120
- ])("Adapter impl: throws when using non-InterPAN function %s while in InterPAN mode", async (funcName, args) => {
2121
- await adapter.setChannelInterPAN(15);
2122
-
2123
- await expect(
2124
- // @ts-expect-error mock
2125
- adapter[funcName](...args),
2126
- ).rejects.toThrow("[INTERPAN MODE] Cannot execute non-InterPAN commands.");
2127
- });
2128
-
2129
- it("Adapter impl: getCoordinatorIEEE", async () => {
2130
- await expect(adapter.getCoordinatorIEEE()).resolves.toStrictEqual(DEFAULT_COORDINATOR_IEEE);
2131
- });
2132
-
2133
- it("Adapter impl: getCoordinatorVersion", async () => {
2134
- await expect(adapter.getCoordinatorVersion()).resolves.toStrictEqual({
2135
- type: "EmberZNet",
2136
- meta: {
2137
- ezsp: EZSP_PROTOCOL_VERSION,
2138
- revision: `8.0.0 [${EmberVersionType[EmberVersionType.GA]}]`,
2139
- build: 135,
2140
- major: 8,
2141
- minor: 0,
2142
- patch: 0,
2143
- special: 0,
2144
- type: EmberVersionType.GA,
2145
- },
2146
- } as TsType.CoordinatorVersion);
2147
- });
2148
-
2149
- it("Adapter impl: reset soft", async () => {
2150
- await expect(adapter.reset("soft")).rejects.toThrow(`Not supported 'soft'.`);
2151
- });
2152
-
2153
- it("Adapter impl: reset hard", async () => {
2154
- await expect(adapter.reset("hard")).rejects.toThrow(`Not supported 'hard'.`);
2155
- });
2156
-
2157
- it("Adapter impl: supportsBackup", async () => {
2158
- await expect(adapter.supportsBackup()).resolves.toStrictEqual(true);
2159
- });
2160
-
2161
- it("Adapter impl: backup", async () => {
2162
- await expect(adapter.backup([])).resolves.toStrictEqual({
2163
- networkOptions: {
2164
- panId: DEFAULT_NETWORK_OPTIONS.panID, // uint16_t
2165
- extendedPanId: Buffer.from(DEFAULT_NETWORK_OPTIONS.extendedPanID!),
2166
- channelList: ZSpec.ALL_802_15_4_CHANNELS.slice(),
2167
- networkKey: Buffer.from(DEFAULT_BACKUP.network_key.key, "hex"),
2168
- networkKeyDistribute: false,
2169
- },
2170
- logicalChannel: DEFAULT_NETWORK_OPTIONS.channelList[0],
2171
- networkKeyInfo: {
2172
- sequenceNumber: DEFAULT_BACKUP.network_key.sequence_number,
2173
- frameCounter: DEFAULT_BACKUP.network_key.frame_counter,
2174
- },
2175
- securityLevel: SECURITY_LEVEL_Z3,
2176
- networkUpdateId: 0,
2177
- coordinatorIeeeAddress: Buffer.from(DEFAULT_BACKUP.coordinator_ieee, "hex"),
2178
- devices: [],
2179
- ezsp: {
2180
- version: EZSP_PROTOCOL_VERSION,
2181
- hashed_tclk: Buffer.from(DEFAULT_BACKUP.stack_specific!.ezsp!.hashed_tclk!, "hex"),
2182
- },
2183
- } as Backup);
2184
- });
2185
-
2186
- it.each([
2187
- [
2188
- "failed get network parameters",
2189
- () => {
2190
- mockEzspGetNetworkParameters.mockResolvedValueOnce([SLStatus.FAIL, 0, {}]);
2191
- },
2192
- "[BACKUP] Failed to get network parameters with status=FAIL.",
2193
- ],
2194
- [
2195
- "failed get network key info",
2196
- () => {
2197
- mockEzspGetNetworkKeyInfo.mockResolvedValueOnce([SLStatus.FAIL, {}]);
2198
- },
2199
- "[BACKUP] Failed to get network keys info with status=FAIL.",
2200
- ],
2201
- // [
2202
- // 'failed get TC APS key info',
2203
- // () => {
2204
- // mockEzspGetNetworkKeyInfo.mockResolvedValueOnce([SLStatus.FAIL, {}]);
2205
- // },
2206
- // `[BACKUP] Failed to get TC APS key info with status=FAIL.`,
2207
- // ],
2208
- [
2209
- "no network key set",
2210
- () => {
2211
- mockEzspGetNetworkKeyInfo.mockResolvedValueOnce([
2212
- SLStatus.OK,
2213
- {
2214
- networkKeySet: false,
2215
- alternateNetworkKeySet: false,
2216
- networkKeySequenceNumber: 123,
2217
- altNetworkKeySequenceNumber: 0,
2218
- networkKeyFrameCounter: 456,
2219
- } as SecManNetworkKeyInfo,
2220
- ]);
2221
- },
2222
- "[BACKUP] No network key set.",
2223
- ],
2224
- [
2225
- "failed export TC link key",
2226
- () => {
2227
- mockEzspExportKey.mockResolvedValueOnce([SLStatus.FAIL, {}]);
2228
- },
2229
- "[BACKUP] Failed to export TC Link Key with status=FAIL.",
2230
- ],
2231
- [
2232
- "failed export network key",
2233
- () => {
2234
- mockEzspExportKey
2235
- .mockResolvedValueOnce([
2236
- SLStatus.OK,
2237
- {contents: Buffer.from(DEFAULT_BACKUP.stack_specific!.ezsp!.hashed_tclk!, "hex")} as SecManKey,
2238
- ])
2239
- .mockResolvedValueOnce([SLStatus.FAIL, {}]);
2240
- },
2241
- "[BACKUP] Failed to export Network Key with status=FAIL.",
2242
- ],
2243
- ])("Adapter impl: throws when backup fails due to %s", async (_command, setup, error) => {
2244
- setup();
2245
-
2246
- await expect(adapter.backup([])).rejects.toThrow(error);
2247
- });
2248
-
2249
- it("Adapter impl: getNetworkParameters from cache", async () => {
2250
- await expect(adapter.getNetworkParameters()).resolves.toStrictEqual({
2251
- panID: DEFAULT_NETWORK_OPTIONS.panID,
2252
- extendedPanID: ZSpec.Utils.eui64LEBufferToHex(Buffer.from(DEFAULT_NETWORK_OPTIONS.extendedPanID!)),
2253
- channel: DEFAULT_NETWORK_OPTIONS.channelList[0],
2254
- nwkUpdateID: 0,
2255
- } as TsType.NetworkParameters);
2256
- expect(mockEzspGetNetworkParameters).toHaveBeenCalledTimes(0);
2257
- });
2258
-
2259
- it("Adapter impl: getNetworkParameters from NCP", async () => {
2260
- adapter.clearNetworkCache();
2261
-
2262
- await expect(adapter.getNetworkParameters()).resolves.toStrictEqual({
2263
- panID: DEFAULT_NETWORK_OPTIONS.panID,
2264
- extendedPanID: ZSpec.Utils.eui64LEBufferToHex(Buffer.from(DEFAULT_NETWORK_OPTIONS.extendedPanID!)),
2265
- channel: DEFAULT_NETWORK_OPTIONS.channelList[0],
2266
- nwkUpdateID: 0,
2267
- } as TsType.NetworkParameters);
2268
- expect(mockEzspGetNetworkParameters).toHaveBeenCalledTimes(1);
2269
- });
2270
-
2271
- it("Adapter impl: addInstallCode", async () => {
2272
- await expect(
2273
- adapter.addInstallCode("0x1122334455667788", Buffer.from("DD7ED5CDAA8E2C708B67D2B1573DB6843A5F", "hex"), false),
2274
- ).resolves.toStrictEqual(undefined);
2275
- expect(mockEzspImportTransientKey).toHaveBeenCalledTimes(1);
2276
- expect(loggerSpies.debug).toHaveBeenCalledWith(`[ADD INSTALL CODE] Success for '0x1122334455667788'.`, "zh:ember");
2277
- });
2278
-
2279
- it("Adapter impl: throw when addInstallCode fails import transient key", async () => {
2280
- mockEzspImportTransientKey.mockResolvedValueOnce(SLStatus.FAIL);
2281
-
2282
- await expect(adapter.addInstallCode("0x1122334455667788", Buffer.alloc(16), true)).rejects.toThrow(
2283
- `[ADD INSTALL CODE] Failed for '0x1122334455667788' with status=FAIL.`,
2284
- );
2285
- expect(mockEzspImportTransientKey).toHaveBeenCalledTimes(1);
2286
- });
2287
-
2288
- it("Adapter impl: waitFor", () => {
2289
- const waiter = adapter.waitFor(1234, 1, Zcl.FrameType.GLOBAL, Zcl.Direction.CLIENT_TO_SERVER, 10, 0, 1, 15000);
2290
- const spyCancel = vi.spyOn(waiter, "cancel");
2291
-
2292
- expect(typeof waiter.cancel).toStrictEqual("function");
2293
- expect(waiter.promise).toBeDefined();
2294
-
2295
- waiter.cancel();
2296
-
2297
- expect(spyCancel).toHaveReturned();
2298
- });
2299
-
2300
- it("Adapter impl: permitJoin on all", async () => {
2301
- const spyResolveEvent = vi.spyOn(
2302
- // @ts-expect-error private
2303
- adapter.oneWaitress,
2304
- "resolveEvent",
2305
- );
2306
-
2307
- await adapter.permitJoin(250);
2308
- await vi.advanceTimersByTimeAsync(1000);
2309
- expect(mockEzspPermitJoining).toHaveBeenCalledWith(250);
2310
- expect(mockEzspSendBroadcast).toHaveBeenCalledTimes(1);
2311
- expect(spyResolveEvent).toHaveBeenCalledWith(OneWaitressEvents.STACK_STATUS_NETWORK_OPENED);
2312
-
2313
- await adapter.permitJoin(0);
2314
- await vi.advanceTimersByTimeAsync(1000);
2315
- expect(mockEzspPermitJoining).toHaveBeenCalledWith(0);
2316
- expect(mockEzspSendBroadcast).toHaveBeenCalledTimes(2);
2317
- expect(spyResolveEvent).toHaveBeenCalledWith(OneWaitressEvents.STACK_STATUS_NETWORK_CLOSED);
2318
-
2319
- expect(mockEzspSetPolicy).toHaveBeenNthCalledWith(
2320
- 1,
2321
- EzspPolicyId.TRUST_CENTER_POLICY,
2322
- EzspDecisionBitmask.ALLOW_JOINS | EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS,
2323
- );
2324
- expect(mockEzspSetPolicy).toHaveBeenNthCalledWith(2, EzspPolicyId.TRUST_CENTER_POLICY, EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS);
2325
- });
2326
-
2327
- it("Adapter impl: permitJoin on coordinator", async () => {
2328
- const spyResolveEvent = vi.spyOn(
2329
- // @ts-expect-error private
2330
- adapter.oneWaitress,
2331
- "resolveEvent",
2332
- );
2333
-
2334
- await adapter.permitJoin(250, ZSpec.COORDINATOR_ADDRESS);
2335
- await vi.advanceTimersByTimeAsync(1000);
2336
- expect(mockEzspPermitJoining).toHaveBeenCalledWith(250);
2337
- expect(mockEzspSendBroadcast).toHaveBeenCalledTimes(0);
2338
- expect(spyResolveEvent).toHaveBeenCalledWith(OneWaitressEvents.STACK_STATUS_NETWORK_OPENED);
2339
-
2340
- await adapter.permitJoin(0, ZSpec.COORDINATOR_ADDRESS);
2341
- await vi.advanceTimersByTimeAsync(1000);
2342
- expect(mockEzspPermitJoining).toHaveBeenCalledWith(0);
2343
- expect(mockEzspSendBroadcast).toHaveBeenCalledTimes(0);
2344
- expect(spyResolveEvent).toHaveBeenCalledWith(OneWaitressEvents.STACK_STATUS_NETWORK_CLOSED);
2345
-
2346
- expect(mockEzspSetPolicy).toHaveBeenNthCalledWith(
2347
- 1,
2348
- EzspPolicyId.TRUST_CENTER_POLICY,
2349
- EzspDecisionBitmask.ALLOW_JOINS | EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS,
2350
- );
2351
- expect(mockEzspSetPolicy).toHaveBeenNthCalledWith(2, EzspPolicyId.TRUST_CENTER_POLICY, EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS);
2352
- });
2353
-
2354
- it("Adapter impl: permitJoin on router", async () => {
2355
- const spyResolveZDO = vi.spyOn(
2356
- // @ts-expect-error private
2357
- adapter.oneWaitress,
2358
- "resolveZDO",
2359
- );
2360
- const sender = 1234;
2361
- const apsFrame: EmberApsFrame = {
2362
- profileId: Zdo.ZDO_PROFILE_ID,
2363
- clusterId: Zdo.ClusterId.PERMIT_JOINING_RESPONSE,
2364
- sourceEndpoint: Zdo.ZDO_ENDPOINT,
2365
- destinationEndpoint: Zdo.ZDO_ENDPOINT,
2366
- options: 0,
2367
- groupId: 0,
2368
- sequence: 0,
2369
- };
2370
- const emitResponse = () => {
2371
- setTimeout(async () => {
2372
- mockEzspEmitter.emit("zdoResponse", apsFrame, sender, Buffer.from([1, Zdo.Status.SUCCESS]));
2373
- await flushPromises();
2374
- }, 300);
2375
-
2376
- return [SLStatus.OK, ++mockAPSSequence];
2377
- };
2378
-
2379
- mockEzspSendUnicast.mockImplementationOnce(emitResponse).mockImplementationOnce(emitResponse);
2380
-
2381
- const zdoResponse = [Zdo.Status.SUCCESS, undefined];
2382
- let p = adapter.permitJoin(250, sender);
2383
- await vi.advanceTimersByTimeAsync(1000);
2384
- await p;
2385
- expect(mockEzspSendUnicast).toHaveBeenCalledTimes(1);
2386
- expect(spyResolveZDO).toHaveBeenCalledTimes(1);
2387
- expect(spyResolveZDO).toHaveBeenCalledWith(sender, apsFrame, zdoResponse);
2388
-
2389
- p = adapter.permitJoin(0, sender);
2390
- await vi.advanceTimersByTimeAsync(1000);
2391
- await p;
2392
- expect(mockEzspSendUnicast).toHaveBeenCalledTimes(2);
2393
- expect(spyResolveZDO).toHaveBeenCalledTimes(2);
2394
- expect(spyResolveZDO).toHaveBeenCalledWith(sender, apsFrame, zdoResponse);
2395
-
2396
- expect(mockEzspSetPolicy).toHaveBeenNthCalledWith(
2397
- 1,
2398
- EzspPolicyId.TRUST_CENTER_POLICY,
2399
- EzspDecisionBitmask.ALLOW_JOINS | EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS,
2400
- );
2401
- expect(mockEzspSetPolicy).toHaveBeenNthCalledWith(2, EzspPolicyId.TRUST_CENTER_POLICY, EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS);
2402
- });
2403
-
2404
- it("Adapter impl: permitJoin restores temp manufacturer code", async () => {
2405
- const spyResolveEvent = vi.spyOn(
2406
- // @ts-expect-error private
2407
- adapter.oneWaitress,
2408
- "resolveEvent",
2409
- );
2410
-
2411
- const newNodeId: NodeId = 1234;
2412
- const newNodeEui64: Eui64 = "0x54ef44ffeebbccaa";
2413
- const status: EmberDeviceUpdate = EmberDeviceUpdate.STANDARD_SECURITY_UNSECURED_JOIN;
2414
- const policyDecision: EmberJoinDecision = EmberJoinDecision.USE_PRECONFIGURED_KEY;
2415
- const parentOfNewNodeId: NodeId = 4321;
2416
-
2417
- mockEzspEmitter.emit("trustCenterJoin", newNodeId, newNodeEui64, status, policyDecision, parentOfNewNodeId);
2418
- await flushPromises();
2419
-
2420
- expect(mockEzspSetManufacturerCode).toHaveBeenCalledWith(Zcl.ManufacturerCode.LUMI_UNITED_TECHOLOGY_LTD_SHENZHEN);
2421
- expect(mockManufCode).toStrictEqual(Zcl.ManufacturerCode.LUMI_UNITED_TECHOLOGY_LTD_SHENZHEN);
2422
-
2423
- await adapter.permitJoin(0, ZSpec.COORDINATOR_ADDRESS);
2424
- await vi.advanceTimersByTimeAsync(1000);
2425
- expect(mockEzspPermitJoining).toHaveBeenCalledWith(0);
2426
- expect(mockEzspSendBroadcast).toHaveBeenCalledTimes(0);
2427
- expect(spyResolveEvent).toHaveBeenCalledWith(OneWaitressEvents.STACK_STATUS_NETWORK_CLOSED);
2428
- expect(mockEzspSetManufacturerCode).toHaveBeenCalledWith(Zcl.ManufacturerCode.SILICON_LABORATORIES);
2429
- expect(mockManufCode).toStrictEqual(Zcl.ManufacturerCode.SILICON_LABORATORIES);
2430
- });
2431
-
2432
- it("Adapter impl: throws when permitJoin request on coordinator fails", async () => {
2433
- mockEzspPermitJoining.mockResolvedValueOnce(SLStatus.FAIL);
2434
-
2435
- await expect(adapter.permitJoin(250, 0)).rejects.toThrow("[ZDO] Failed coordinator permit joining request with status=FAIL.");
2436
- });
2437
-
2438
- it("Adapter impl: throws when permitJoin broadcast request fails", async () => {
2439
- mockEzspSendBroadcast.mockResolvedValueOnce([SLStatus.FAIL, 0]);
2440
-
2441
- await expect(defuseRejection(adapter.permitJoin(250, undefined))).rejects.toThrow(
2442
- "~x~> [ZDO PERMIT_JOINING_REQUEST BROADCAST to=65532 messageTag=1] Failed to send request with status=FAIL.",
2443
- );
2444
- });
2445
-
2446
- it("Adapter impl: resolves undefined when permitJoin on router fails due to failed ZDO status", async () => {
2447
- const spyResolveZDO = vi.spyOn(
2448
- // @ts-expect-error private
2449
- adapter.oneWaitress,
2450
- "resolveZDO",
2451
- );
2452
- const sender = 1234;
2453
- const apsFrame: EmberApsFrame = {
2454
- profileId: Zdo.ZDO_PROFILE_ID,
2455
- clusterId: Zdo.ClusterId.PERMIT_JOINING_RESPONSE,
2456
- sourceEndpoint: Zdo.ZDO_ENDPOINT,
2457
- destinationEndpoint: Zdo.ZDO_ENDPOINT,
2458
- options: 0,
2459
- groupId: 0,
2460
- sequence: 0,
2461
- };
2462
-
2463
- mockEzspEmitter.emit("zdoResponse", apsFrame, sender, Buffer.from([1, Zdo.Status.NOT_AUTHORIZED]));
2464
- await flushPromises();
2465
-
2466
- const zdoResponse = [Zdo.Status.NOT_AUTHORIZED, undefined];
2467
- expect(spyResolveZDO).toHaveBeenCalledTimes(1);
2468
- expect(spyResolveZDO).toHaveBeenCalledWith(sender, apsFrame, zdoResponse);
2469
- });
2470
-
2471
- it("Adapter impl: throws when permitJoin request on router fails", async () => {
2472
- mockEzspSendUnicast.mockResolvedValueOnce([SLStatus.FAIL, 0]);
2473
-
2474
- await expect(adapter.permitJoin(250, 1234)).rejects.toThrow(
2475
- "~x~> [ZDO PERMIT_JOINING_REQUEST UNICAST to=0xFFFFFFFFFFFFFFFF:1234 messageTag=1] Failed to send request with status=FAIL.",
2476
- );
2477
- });
2478
-
2479
- it("Adapter impl: throws when permitJoin fails to import ZIGBEE_PROFILE_INTEROPERABILITY_LINK_KEY", async () => {
2480
- mockEzspImportTransientKey.mockResolvedValueOnce(SLStatus.FAIL);
2481
-
2482
- await expect(adapter.permitJoin(250)).rejects.toThrow("[ZDO] Failed import transient key with status=FAIL.");
2483
- });
2484
-
2485
- it("Adapter impl: throws when permitJoin fails to set TC policy", async () => {
2486
- mockEzspSetPolicy.mockResolvedValueOnce(SLStatus.FAIL);
2487
-
2488
- await expect(adapter.permitJoin(250)).rejects.toThrow("[ZDO] Failed set join policy with status=FAIL.");
2489
- });
2490
-
2491
- it("Adapter impl: throws when stop permitJoin fails to restore TC policy", async () => {
2492
- mockEzspSetPolicy.mockResolvedValueOnce(SLStatus.FAIL);
2493
-
2494
- await expect(adapter.permitJoin(0)).rejects.toThrow("[ZDO] Failed set join policy with status=FAIL.");
2495
- });
2496
-
2497
- it("Adapter impl: sendZclFrameToEndpoint with command response with fixed source endpoint", async () => {
2498
- const networkAddress: NodeId = 1234;
2499
- const endpoint: number = 1;
2500
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
2501
- const zclFrame = Zcl.Frame.create(
2502
- Zcl.FrameType.GLOBAL,
2503
- Zcl.Direction.CLIENT_TO_SERVER,
2504
- true,
2505
- undefined,
2506
- 3,
2507
- "read",
2508
- "genBasic",
2509
- [{attrId: 0}],
2510
- {},
2511
- );
2512
- const apsFrame: EmberApsFrame = {
2513
- profileId: FIXED_ENDPOINTS[0].profileId,
2514
- clusterId: zclFrame.cluster.ID,
2515
- sourceEndpoint,
2516
- destinationEndpoint: endpoint,
2517
- options: DEFAULT_APS_OPTIONS,
2518
- groupId: 0,
2519
- sequence: 0, // set by stack
2520
- };
2521
- const lastHopLqi: number = 234;
2522
- // Received Zigbee message from '0x', type 'readResponse', cluster 'genBasic', data '{"zclVersion":3}' from endpoint 1 with groupID 0
2523
- const messageContents = Buffer.from("1803010000002003", "hex");
2524
-
2525
- mockEzspSend.mockImplementationOnce(() => {
2526
- setTimeout(async () => {
2527
- mockEzspEmitter.emit(
2528
- "incomingMessage",
2529
- EmberIncomingMessageType.UNICAST,
2530
- reverseApsFrame(apsFrame),
2531
- lastHopLqi,
2532
- networkAddress,
2533
- messageContents,
2534
- );
2535
- await flushPromises();
2536
- }, 300);
2537
-
2538
- return [SLStatus.OK, ++mockAPSSequence];
2539
- });
2540
-
2541
- const p = adapter.sendZclFrameToEndpoint("0x1122334455667788", networkAddress, endpoint, zclFrame, 10000, false, false, sourceEndpoint);
2542
-
2543
- await vi.advanceTimersByTimeAsync(5000);
2544
- await expect(p).resolves.toStrictEqual({
2545
- clusterID: apsFrame.clusterId,
2546
- header: Zcl.Header.fromBuffer(messageContents),
2547
- address: networkAddress,
2548
- data: messageContents,
2549
- endpoint: apsFrame.destinationEndpoint,
2550
- linkquality: lastHopLqi,
2551
- groupID: apsFrame.groupId,
2552
- wasBroadcast: false,
2553
- destinationEndpoint: apsFrame.sourceEndpoint,
2554
- } as ZclPayload);
2555
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
2556
- });
2557
-
2558
- it("Adapter impl: sendZclFrameToEndpoint with command response with other source endpoint", async () => {
2559
- const networkAddress: NodeId = 1234;
2560
- const endpoint: number = 1;
2561
- const sourceEndpoint = 3;
2562
- const zclFrame = Zcl.Frame.create(
2563
- Zcl.FrameType.GLOBAL,
2564
- Zcl.Direction.CLIENT_TO_SERVER,
2565
- true,
2566
- undefined,
2567
- 3,
2568
- "read",
2569
- "genBasic",
2570
- [{attrId: 0}],
2571
- {},
2572
- );
2573
- const apsFrame: EmberApsFrame = {
2574
- profileId: FIXED_ENDPOINTS[0].profileId,
2575
- clusterId: zclFrame.cluster.ID,
2576
- sourceEndpoint,
2577
- destinationEndpoint: endpoint,
2578
- options: DEFAULT_APS_OPTIONS,
2579
- groupId: 0,
2580
- sequence: 0, // set by stack
2581
- };
2582
- const lastHopLqi: number = 234;
2583
- // Received Zigbee message from '0x', type 'readResponse', cluster 'genBasic', data '{"zclVersion":3}' from endpoint 1 with groupID 0
2584
- const messageContents = Buffer.from("1803010000002003", "hex");
2585
-
2586
- mockEzspSend.mockImplementationOnce(() => {
2587
- setTimeout(async () => {
2588
- mockEzspEmitter.emit(
2589
- "incomingMessage",
2590
- EmberIncomingMessageType.UNICAST,
2591
- reverseApsFrame(apsFrame),
2592
- lastHopLqi,
2593
- networkAddress,
2594
- messageContents,
2595
- );
2596
- await flushPromises();
2597
- }, 300);
2598
-
2599
- return [SLStatus.OK, ++mockAPSSequence];
2600
- });
2601
-
2602
- const p = adapter.sendZclFrameToEndpoint("0x1122334455667788", networkAddress, endpoint, zclFrame, 10000, false, false, sourceEndpoint);
2603
-
2604
- await vi.advanceTimersByTimeAsync(5000);
2605
- await expect(p).resolves.toStrictEqual({
2606
- clusterID: apsFrame.clusterId,
2607
- header: Zcl.Header.fromBuffer(messageContents),
2608
- address: networkAddress,
2609
- data: messageContents,
2610
- endpoint: apsFrame.destinationEndpoint,
2611
- linkquality: lastHopLqi,
2612
- groupID: apsFrame.groupId,
2613
- wasBroadcast: false,
2614
- destinationEndpoint: apsFrame.sourceEndpoint,
2615
- } as ZclPayload);
2616
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
2617
- });
2618
-
2619
- it("Adapter impl: sendZclFrameToEndpoint with command response with no source endpoint", async () => {
2620
- const networkAddress: NodeId = 1234;
2621
- const endpoint: number = 1;
2622
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
2623
- const zclFrame = Zcl.Frame.create(
2624
- Zcl.FrameType.GLOBAL,
2625
- Zcl.Direction.CLIENT_TO_SERVER,
2626
- true,
2627
- undefined,
2628
- 3,
2629
- "read",
2630
- "genBasic",
2631
- [{attrId: 0}],
2632
- {},
2633
- );
2634
- const apsFrame: EmberApsFrame = {
2635
- profileId: FIXED_ENDPOINTS[0].profileId,
2636
- clusterId: zclFrame.cluster.ID,
2637
- sourceEndpoint,
2638
- destinationEndpoint: endpoint,
2639
- options: DEFAULT_APS_OPTIONS,
2640
- groupId: 0,
2641
- sequence: 0, // set by stack
2642
- };
2643
- const lastHopLqi: number = 234;
2644
- // Received Zigbee message from '0x', type 'readResponse', cluster 'genBasic', data '{"zclVersion":3}' from endpoint 1 with groupID 0
2645
- const messageContents = Buffer.from("1803010000002003", "hex");
2646
-
2647
- mockEzspSend.mockImplementationOnce(() => {
2648
- setTimeout(async () => {
2649
- mockEzspEmitter.emit(
2650
- "incomingMessage",
2651
- EmberIncomingMessageType.UNICAST,
2652
- reverseApsFrame(apsFrame),
2653
- lastHopLqi,
2654
- networkAddress,
2655
- messageContents,
2656
- );
2657
- await flushPromises();
2658
- }, 300);
2659
-
2660
- return [SLStatus.OK, ++mockAPSSequence];
2661
- });
2662
-
2663
- const p = adapter.sendZclFrameToEndpoint("0x1122334455667788", networkAddress, endpoint, zclFrame, 10000, false, false);
2664
-
2665
- await vi.advanceTimersByTimeAsync(5000);
2666
- await expect(p).resolves.toStrictEqual({
2667
- clusterID: apsFrame.clusterId,
2668
- header: Zcl.Header.fromBuffer(messageContents),
2669
- address: networkAddress,
2670
- data: messageContents,
2671
- endpoint: apsFrame.destinationEndpoint,
2672
- linkquality: lastHopLqi,
2673
- groupID: apsFrame.groupId,
2674
- wasBroadcast: false,
2675
- destinationEndpoint: apsFrame.sourceEndpoint,
2676
- } as ZclPayload);
2677
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
2678
- });
2679
-
2680
- it.each([
2681
- ["NO_TX_SPACE", EzspStatus.NO_TX_SPACE],
2682
- ["NOT_CONNECTED", EzspStatus.NOT_CONNECTED],
2683
- ])("Adapter impl: recovers when sendZclFrameToEndpoint throws %s status", async (_statusName, status) => {
2684
- const networkAddress: NodeId = 1234;
2685
- const endpoint: number = 1;
2686
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
2687
- const zclFrame = Zcl.Frame.create(
2688
- Zcl.FrameType.GLOBAL,
2689
- Zcl.Direction.CLIENT_TO_SERVER,
2690
- true,
2691
- undefined,
2692
- 3,
2693
- "read",
2694
- "genBasic",
2695
- [{attrId: 0}],
2696
- {},
2697
- );
2698
- const apsFrame: EmberApsFrame = {
2699
- profileId: FIXED_ENDPOINTS[0].profileId,
2700
- clusterId: zclFrame.cluster.ID,
2701
- sourceEndpoint,
2702
- destinationEndpoint: endpoint,
2703
- options: DEFAULT_APS_OPTIONS,
2704
- groupId: 0,
2705
- sequence: 0, // set by stack
2706
- };
2707
- const lastHopLqi: number = 234;
2708
- // Received Zigbee message from '0x', type 'readResponse', cluster 'genBasic', data '{"zclVersion":3}' from endpoint 1 with groupID 0
2709
- const messageContents = Buffer.from("1803010000002003", "hex");
2710
-
2711
- mockEzspSend.mockRejectedValueOnce(new EzspError(status)).mockImplementationOnce(() => {
2712
- setTimeout(async () => {
2713
- mockEzspEmitter.emit(
2714
- "incomingMessage",
2715
- EmberIncomingMessageType.UNICAST,
2716
- reverseApsFrame(apsFrame),
2717
- lastHopLqi,
2718
- networkAddress,
2719
- messageContents,
2720
- );
2721
- await flushPromises();
2722
- }, 300);
2723
-
2724
- return [SLStatus.OK, ++mockAPSSequence];
2725
- });
2726
-
2727
- const p = adapter.sendZclFrameToEndpoint("0x1122334455667788", networkAddress, endpoint, zclFrame, 10000, false, false, sourceEndpoint);
2728
-
2729
- await vi.advanceTimersByTimeAsync(10000);
2730
- await expect(p).resolves.toStrictEqual({
2731
- clusterID: apsFrame.clusterId,
2732
- header: Zcl.Header.fromBuffer(messageContents),
2733
- address: networkAddress,
2734
- data: messageContents,
2735
- endpoint: apsFrame.destinationEndpoint,
2736
- linkquality: lastHopLqi,
2737
- groupID: apsFrame.groupId,
2738
- wasBroadcast: false,
2739
- destinationEndpoint: apsFrame.sourceEndpoint,
2740
- } as ZclPayload);
2741
- expect(mockEzspSend).toHaveBeenCalledTimes(2);
2742
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
2743
- });
2744
-
2745
- it.each([
2746
- ["ZIGBEE_MAX_MESSAGE_LIMIT_REACHED", SLStatus.ZIGBEE_MAX_MESSAGE_LIMIT_REACHED],
2747
- ["BUSY", SLStatus.BUSY],
2748
- ["NETWORK_DOWN", SLStatus.NETWORK_DOWN],
2749
- ])("Adapter impl: recovers when sendZclFrameToEndpoint get %s status from NCP", async (_statusName, status) => {
2750
- const networkAddress: NodeId = 1234;
2751
- const endpoint: number = 1;
2752
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
2753
- const zclFrame = Zcl.Frame.create(
2754
- Zcl.FrameType.GLOBAL,
2755
- Zcl.Direction.CLIENT_TO_SERVER,
2756
- true,
2757
- undefined,
2758
- 3,
2759
- "read",
2760
- "genBasic",
2761
- [{attrId: 0}],
2762
- {},
2763
- );
2764
- const apsFrame: EmberApsFrame = {
2765
- profileId: FIXED_ENDPOINTS[0].profileId,
2766
- clusterId: zclFrame.cluster.ID,
2767
- sourceEndpoint,
2768
- destinationEndpoint: endpoint,
2769
- options: DEFAULT_APS_OPTIONS,
2770
- groupId: 0,
2771
- sequence: 0, // set by stack
2772
- };
2773
- const lastHopLqi: number = 234;
2774
- // Received Zigbee message from '0x', type 'readResponse', cluster 'genBasic', data '{"zclVersion":3}' from endpoint 1 with groupID 0
2775
- const messageContents = Buffer.from("1803010000002003", "hex");
2776
-
2777
- mockEzspSend.mockResolvedValueOnce([status, 0]).mockImplementationOnce(() => {
2778
- setTimeout(async () => {
2779
- mockEzspEmitter.emit(
2780
- "incomingMessage",
2781
- EmberIncomingMessageType.UNICAST,
2782
- reverseApsFrame(apsFrame),
2783
- lastHopLqi,
2784
- networkAddress,
2785
- messageContents,
2786
- );
2787
- await flushPromises();
2788
- }, 300);
2789
-
2790
- return [SLStatus.OK, ++mockAPSSequence];
2791
- });
2792
-
2793
- const p = adapter.sendZclFrameToEndpoint("0x1122334455667788", networkAddress, endpoint, zclFrame, 10000, false, false, sourceEndpoint);
2794
-
2795
- await vi.advanceTimersByTimeAsync(10000);
2796
- await expect(p).resolves.toStrictEqual({
2797
- clusterID: apsFrame.clusterId,
2798
- header: Zcl.Header.fromBuffer(messageContents),
2799
- address: networkAddress,
2800
- data: messageContents,
2801
- endpoint: apsFrame.destinationEndpoint,
2802
- linkquality: lastHopLqi,
2803
- groupID: apsFrame.groupId,
2804
- wasBroadcast: false,
2805
- destinationEndpoint: apsFrame.sourceEndpoint,
2806
- } as ZclPayload);
2807
- expect(mockEzspSend).toHaveBeenCalledTimes(2);
2808
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
2809
- });
2810
-
2811
- it("Adapter impl: throws when sendZclFrameToEndpoint throws NO_TX_SPACE status and recovery disabled", async () => {
2812
- const networkAddress: NodeId = 1234;
2813
- const endpoint: number = 1;
2814
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
2815
- const zclFrame = Zcl.Frame.create(
2816
- Zcl.FrameType.GLOBAL,
2817
- Zcl.Direction.CLIENT_TO_SERVER,
2818
- true,
2819
- undefined,
2820
- 3,
2821
- "read",
2822
- "genBasic",
2823
- [{attrId: 0}],
2824
- {},
2825
- );
2826
- const apsFrame: EmberApsFrame = {
2827
- profileId: FIXED_ENDPOINTS[0].profileId,
2828
- clusterId: zclFrame.cluster.ID,
2829
- sourceEndpoint,
2830
- destinationEndpoint: endpoint,
2831
- options: DEFAULT_APS_OPTIONS,
2832
- groupId: 0,
2833
- sequence: 0, // set by stack
2834
- };
2835
-
2836
- mockEzspSend.mockRejectedValueOnce(new EzspError(EzspStatus.NO_TX_SPACE));
2837
-
2838
- const p = defuseRejection(
2839
- adapter.sendZclFrameToEndpoint(
2840
- "0x1122334455667788",
2841
- networkAddress,
2842
- endpoint,
2843
- zclFrame,
2844
- 10000,
2845
- false,
2846
- true, // disable recovery
2847
- sourceEndpoint,
2848
- ),
2849
- );
2850
-
2851
- await vi.advanceTimersByTimeAsync(10000);
2852
- await expect(p).rejects.toThrow(
2853
- `~x~> [ZCL to=0x1122334455667788:1234 apsFrame={"profileId":260,"clusterId":0,"sourceEndpoint":1,"destinationEndpoint":1,"options":4416,"groupId":0,"sequence":0}] Failed to send request with status=${SLStatus[SLStatus.BUSY]}.`,
2854
- );
2855
- expect(mockEzspSend).toHaveBeenCalledTimes(1);
2856
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
2857
- });
2858
-
2859
- it("Adapter impl: throws when sendZclFrameToEndpoint get BUSY status from NCP and recovery disabled", async () => {
2860
- const networkAddress: NodeId = 1234;
2861
- const endpoint: number = 1;
2862
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
2863
- const zclFrame = Zcl.Frame.create(
2864
- Zcl.FrameType.GLOBAL,
2865
- Zcl.Direction.CLIENT_TO_SERVER,
2866
- true,
2867
- undefined,
2868
- 3,
2869
- "read",
2870
- "genBasic",
2871
- [{attrId: 0}],
2872
- {},
2873
- );
2874
- const apsFrame: EmberApsFrame = {
2875
- profileId: FIXED_ENDPOINTS[0].profileId,
2876
- clusterId: zclFrame.cluster.ID,
2877
- sourceEndpoint,
2878
- destinationEndpoint: endpoint,
2879
- options: DEFAULT_APS_OPTIONS,
2880
- groupId: 0,
2881
- sequence: 0, // set by stack
2882
- };
2883
-
2884
- mockEzspSend.mockResolvedValueOnce([SLStatus.BUSY, 0]);
2885
-
2886
- const p = defuseRejection(
2887
- adapter.sendZclFrameToEndpoint(
2888
- "0x1122334455667788",
2889
- networkAddress,
2890
- endpoint,
2891
- zclFrame,
2892
- 10000,
2893
- false,
2894
- true, // disable recovery
2895
- sourceEndpoint,
2896
- ),
2897
- );
2898
-
2899
- await vi.advanceTimersByTimeAsync(10000);
2900
- await expect(p).rejects.toThrow(
2901
- `~x~> [ZCL to=0x1122334455667788:1234 apsFrame={"profileId":260,"clusterId":0,"sourceEndpoint":1,"destinationEndpoint":1,"options":4416,"groupId":0,"sequence":0}] Failed to send request with status=${SLStatus[SLStatus.BUSY]}.`,
2902
- );
2903
- expect(mockEzspSend).toHaveBeenCalledTimes(1);
2904
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
2905
- });
2906
-
2907
- it("Adapter impl: throws when sendZclFrameToEndpoint get BUSY status from NCP and exceeded max attempts", async () => {
2908
- const networkAddress: NodeId = 1234;
2909
- const endpoint: number = 1;
2910
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
2911
- const zclFrame = Zcl.Frame.create(
2912
- Zcl.FrameType.GLOBAL,
2913
- Zcl.Direction.CLIENT_TO_SERVER,
2914
- true,
2915
- undefined,
2916
- 3,
2917
- "read",
2918
- "genBasic",
2919
- [{attrId: 0}],
2920
- {},
2921
- );
2922
- const apsFrame: EmberApsFrame = {
2923
- profileId: FIXED_ENDPOINTS[0].profileId,
2924
- clusterId: zclFrame.cluster.ID,
2925
- sourceEndpoint,
2926
- destinationEndpoint: endpoint,
2927
- options: DEFAULT_APS_OPTIONS,
2928
- groupId: 0,
2929
- sequence: 0, // set by stack
2930
- };
2931
-
2932
- mockEzspSend
2933
- .mockResolvedValueOnce([SLStatus.BUSY, 0])
2934
- .mockResolvedValueOnce([SLStatus.BUSY, 0])
2935
- .mockResolvedValueOnce([SLStatus.BUSY, 0]);
2936
-
2937
- const p = defuseRejection(
2938
- adapter.sendZclFrameToEndpoint(
2939
- "0x1122334455667788",
2940
- networkAddress,
2941
- endpoint,
2942
- zclFrame,
2943
- 10000,
2944
- false,
2945
- false, // disable recovery
2946
- sourceEndpoint,
2947
- ),
2948
- );
2949
-
2950
- await vi.advanceTimersByTimeAsync(10000);
2951
- await expect(p).rejects.toThrow(
2952
- `~x~> [ZCL to=0x1122334455667788:1234 apsFrame={"profileId":260,"clusterId":0,"sourceEndpoint":1,"destinationEndpoint":1,"options":4416,"groupId":0,"sequence":0}] Failed to send request with status=${SLStatus[SLStatus.BUSY]}.`,
2953
- );
2954
- expect(mockEzspSend).toHaveBeenCalledTimes(3);
2955
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
2956
- });
2957
-
2958
- it("Adapter impl: throws when sendZclFrameToEndpoint request fails", async () => {
2959
- const networkAddress: NodeId = 1234;
2960
- const endpoint: number = 1;
2961
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
2962
- const zclFrame = Zcl.Frame.create(
2963
- Zcl.FrameType.GLOBAL,
2964
- Zcl.Direction.CLIENT_TO_SERVER,
2965
- true,
2966
- undefined,
2967
- 3,
2968
- "read",
2969
- "genBasic",
2970
- [{attrId: 0}],
2971
- {},
2972
- );
2973
- const apsFrame: EmberApsFrame = {
2974
- profileId: FIXED_ENDPOINTS[0].profileId,
2975
- clusterId: zclFrame.cluster.ID,
2976
- sourceEndpoint,
2977
- destinationEndpoint: endpoint,
2978
- options: DEFAULT_APS_OPTIONS,
2979
- groupId: 0,
2980
- sequence: 0, // set by stack
2981
- };
2982
-
2983
- mockEzspSend.mockResolvedValueOnce([SLStatus.FAIL, 0]);
2984
-
2985
- const p = defuseRejection(
2986
- adapter.sendZclFrameToEndpoint(
2987
- "0x1122334455667788",
2988
- networkAddress,
2989
- endpoint,
2990
- zclFrame,
2991
- 10000,
2992
- false,
2993
- false, // disable recovery
2994
- sourceEndpoint,
2995
- ),
2996
- );
2997
-
2998
- await vi.advanceTimersByTimeAsync(10000);
2999
- await expect(p).rejects.toThrow(
3000
- `~x~> [ZCL to=0x1122334455667788:1234 apsFrame={"profileId":260,"clusterId":0,"sourceEndpoint":1,"destinationEndpoint":1,"options":4416,"groupId":0,"sequence":0}] Failed to send request with status=FAIL.`,
3001
- );
3002
- expect(mockEzspSend).toHaveBeenCalledTimes(1);
3003
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
3004
- });
3005
-
3006
- it("Adapter impl: sendZdo with EUI64", async () => {
3007
- const sender: NodeId = 0x6789;
3008
- const senderEUI64: Eui64 = "0x1122334455667788";
3009
- const apsFrame: EmberApsFrame = {
3010
- profileId: Zdo.ZDO_PROFILE_ID,
3011
- clusterId: Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE,
3012
- sourceEndpoint: Zdo.ZDO_ENDPOINT,
3013
- destinationEndpoint: Zdo.ZDO_ENDPOINT,
3014
- options: 0,
3015
- groupId: 0,
3016
- sequence: 0,
3017
- };
3018
-
3019
- mockEzspSendBroadcast.mockImplementationOnce(() => {
3020
- setTimeout(async () => {
3021
- mockEzspEmitter.emit(
3022
- "zdoResponse",
3023
- apsFrame,
3024
- sender,
3025
- Buffer.from([
3026
- 1,
3027
- Zdo.Status.SUCCESS,
3028
- 0x88,
3029
- 0x77,
3030
- 0x66,
3031
- 0x55,
3032
- 0x44,
3033
- 0x33,
3034
- 0x22,
3035
- 0x11,
3036
- 0x89, // nwkAddress
3037
- 0x67, // nwkAddress
3038
- ]),
3039
- );
3040
- await flushPromises();
3041
- }, 300);
3042
-
3043
- return [SLStatus.OK, ++mockAPSSequence];
3044
- });
3045
-
3046
- const zdoPayload = Zdo.Buffalo.buildRequest(false, Zdo.ClusterId.NETWORK_ADDRESS_REQUEST, senderEUI64, false, 0);
3047
- const p = adapter.sendZdo(
3048
- senderEUI64,
3049
- ZSpec.NULL_NODE_ID /* same as broadcast SLEEPY */,
3050
- Zdo.ClusterId.NETWORK_ADDRESS_REQUEST,
3051
- zdoPayload,
3052
- false,
3053
- );
3054
-
3055
- await vi.advanceTimersByTimeAsync(1000);
3056
- await expect(p).resolves.toStrictEqual([
3057
- Zdo.Status.SUCCESS,
3058
- {
3059
- eui64: senderEUI64,
3060
- nwkAddress: sender,
3061
- startIndex: 0,
3062
- assocDevList: [],
3063
- } as ZdoTypes.NetworkAddressResponse,
3064
- ]);
3065
- });
3066
-
3067
- it("Adapter impl: sendZclFrameToEndpoint with default response", async () => {
3068
- const networkAddress: NodeId = 1234;
3069
- const endpoint: number = 3;
3070
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
3071
- const zclFrame = Zcl.Frame.create(
3072
- Zcl.FrameType.GLOBAL,
3073
- Zcl.Direction.CLIENT_TO_SERVER,
3074
- false,
3075
- undefined,
3076
- 3,
3077
- "read",
3078
- "genBasic",
3079
- [{attrId: 0}],
3080
- {},
3081
- );
3082
- const apsFrame: EmberApsFrame = {
3083
- profileId: FIXED_ENDPOINTS[0].profileId,
3084
- clusterId: zclFrame.cluster.ID,
3085
- sourceEndpoint,
3086
- destinationEndpoint: endpoint,
3087
- options: DEFAULT_APS_OPTIONS,
3088
- groupId: 0,
3089
- sequence: 0, // set by stack
3090
- };
3091
- const lastHopLqi: number = 234;
3092
- // defaultRsp with cmdId=0, status=0
3093
- const messageContents = Buffer.from("18030b0000", "hex");
3094
-
3095
- mockEzspSend.mockImplementationOnce(() => {
3096
- setTimeout(async () => {
3097
- mockEzspEmitter.emit(
3098
- "incomingMessage",
3099
- EmberIncomingMessageType.UNICAST,
3100
- reverseApsFrame(apsFrame),
3101
- lastHopLqi,
3102
- networkAddress,
3103
- messageContents,
3104
- );
3105
- await flushPromises();
3106
- }, 300);
3107
-
3108
- return [SLStatus.OK, ++mockAPSSequence];
3109
- });
3110
-
3111
- const p = adapter.sendZclFrameToEndpoint("0x1122334455667788", networkAddress, endpoint, zclFrame, 10000, true, false, sourceEndpoint);
3112
-
3113
- await vi.advanceTimersByTimeAsync(5000);
3114
- await expect(p).resolves.toStrictEqual({
3115
- clusterID: apsFrame.clusterId,
3116
- header: Zcl.Header.fromBuffer(messageContents),
3117
- address: networkAddress,
3118
- data: messageContents,
3119
- endpoint: apsFrame.destinationEndpoint,
3120
- linkquality: lastHopLqi,
3121
- groupID: apsFrame.groupId,
3122
- wasBroadcast: false,
3123
- destinationEndpoint: apsFrame.sourceEndpoint,
3124
- } as ZclPayload);
3125
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
3126
- });
3127
-
3128
- it("Adapter impl: sendZclFrameToEndpoint without response", async () => {
3129
- const networkAddress: NodeId = 1234;
3130
- const endpoint: number = 3;
3131
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
3132
- const zclFrame = Zcl.Frame.create(
3133
- Zcl.FrameType.GLOBAL,
3134
- Zcl.Direction.CLIENT_TO_SERVER,
3135
- true,
3136
- undefined,
3137
- 3,
3138
- "read",
3139
- "genBasic",
3140
- [{attrId: 0}],
3141
- {},
3142
- );
3143
-
3144
- const p = adapter.sendZclFrameToEndpoint("0x1122334455667788", networkAddress, endpoint, zclFrame, 10000, true, false, sourceEndpoint);
3145
-
3146
- await vi.advanceTimersByTimeAsync(5000);
3147
- await expect(p).resolves.toStrictEqual(undefined);
3148
-
3149
- const apsFrame: EmberApsFrame = {
3150
- profileId: FIXED_ENDPOINTS[0].profileId,
3151
- clusterId: zclFrame.cluster.ID,
3152
- sourceEndpoint,
3153
- destinationEndpoint: endpoint,
3154
- options: DEFAULT_APS_OPTIONS & ~EmberApsOption.RETRY,
3155
- groupId: 0,
3156
- sequence: 0, // set by stack
3157
- };
3158
-
3159
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.DIRECT, networkAddress, apsFrame, zclFrame.toBuffer(), 0, 0);
3160
- });
3161
-
3162
- it("Adapter impl: sendZclFrameToGroup with source endpoint", async () => {
3163
- const groupId: number = 32;
3164
- const zclFrame = Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {});
3165
- const p = adapter.sendZclFrameToGroup(groupId, zclFrame, 2);
3166
-
3167
- await vi.advanceTimersByTimeAsync(5000);
3168
- await expect(p).resolves.toStrictEqual(undefined);
3169
-
3170
- const apsFrame: EmberApsFrame = {
3171
- profileId: FIXED_ENDPOINTS[0].profileId,
3172
- clusterId: zclFrame.cluster.ID,
3173
- sourceEndpoint: 2,
3174
- destinationEndpoint: 0xff,
3175
- options: DEFAULT_APS_OPTIONS,
3176
- groupId,
3177
- sequence: 0, // set by stack
3178
- };
3179
-
3180
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.MULTICAST, groupId, apsFrame, zclFrame.toBuffer(), 0, 0);
3181
- });
3182
-
3183
- it("Adapter impl: sendZclFrameToGroup with default source endpoint", async () => {
3184
- const groupId: number = 32;
3185
- const zclFrame = Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {});
3186
- const p = adapter.sendZclFrameToGroup(groupId, zclFrame);
3187
-
3188
- await vi.advanceTimersByTimeAsync(5000);
3189
- await expect(p).resolves.toStrictEqual(undefined);
3190
-
3191
- const apsFrame: EmberApsFrame = {
3192
- profileId: FIXED_ENDPOINTS[0].profileId,
3193
- clusterId: zclFrame.cluster.ID,
3194
- sourceEndpoint: FIXED_ENDPOINTS[0].endpoint,
3195
- destinationEndpoint: 0xff,
3196
- options: DEFAULT_APS_OPTIONS,
3197
- groupId,
3198
- sequence: 0, // set by stack
3199
- };
3200
-
3201
- expect(mockEzspSend).toHaveBeenCalledWith(EmberOutgoingMessageType.MULTICAST, groupId, apsFrame, zclFrame.toBuffer(), 0, 0);
3202
- });
3203
-
3204
- it("Adapter impl: throws when sendZclFrameToGroup fails request", async () => {
3205
- mockEzspSend.mockResolvedValueOnce([SLStatus.FAIL, 0]);
3206
-
3207
- const groupId: number = 32;
3208
- const zclFrame = Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {});
3209
- const p = defuseRejection(adapter.sendZclFrameToGroup(groupId, zclFrame, 1));
3210
-
3211
- await vi.advanceTimersByTimeAsync(5000);
3212
- await expect(p).rejects.toThrow("~x~> [ZCL GROUP groupId=32] Failed to send with status=FAIL.");
3213
- expect(mockEzspSend).toHaveBeenCalledTimes(1);
3214
- });
3215
-
3216
- it("Adapter impl: sendZclFrameToAll with fixed endpoint", async () => {
3217
- const endpoint: number = 32;
3218
- const zclFrame = Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {});
3219
- const sourceEndpoint = FIXED_ENDPOINTS[0].endpoint;
3220
- const p = adapter.sendZclFrameToAll(endpoint, zclFrame, sourceEndpoint, ZSpec.BroadcastAddress.DEFAULT);
3221
-
3222
- await vi.advanceTimersByTimeAsync(5000);
3223
- await expect(p).resolves.toStrictEqual(undefined);
3224
-
3225
- const apsFrame: EmberApsFrame = {
3226
- profileId: FIXED_ENDPOINTS[0].profileId,
3227
- clusterId: zclFrame.cluster.ID,
3228
- sourceEndpoint,
3229
- destinationEndpoint: endpoint,
3230
- options: DEFAULT_APS_OPTIONS,
3231
- groupId: ZSpec.BroadcastAddress.DEFAULT,
3232
- sequence: 0, // set by stack
3233
- };
3234
-
3235
- expect(mockEzspSend).toHaveBeenCalledWith(
3236
- EmberOutgoingMessageType.BROADCAST,
3237
- ZSpec.BroadcastAddress.DEFAULT,
3238
- apsFrame,
3239
- zclFrame.toBuffer(),
3240
- 0,
3241
- 0,
3242
- );
3243
- });
3244
-
3245
- it("Adapter impl: sendZclFrameToAll with other endpoint", async () => {
3246
- const endpoint: number = 32;
3247
- const zclFrame = Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {});
3248
- const sourceEndpoint = 3;
3249
- const p = adapter.sendZclFrameToAll(endpoint, zclFrame, sourceEndpoint, ZSpec.BroadcastAddress.DEFAULT);
3250
-
3251
- await vi.advanceTimersByTimeAsync(5000);
3252
- await expect(p).resolves.toStrictEqual(undefined);
3253
-
3254
- const apsFrame: EmberApsFrame = {
3255
- profileId: FIXED_ENDPOINTS[0].profileId,
3256
- clusterId: zclFrame.cluster.ID,
3257
- sourceEndpoint,
3258
- destinationEndpoint: endpoint,
3259
- options: DEFAULT_APS_OPTIONS,
3260
- groupId: ZSpec.BroadcastAddress.DEFAULT,
3261
- sequence: 0, // set by stack
3262
- };
3263
-
3264
- expect(mockEzspSend).toHaveBeenCalledWith(
3265
- EmberOutgoingMessageType.BROADCAST,
3266
- ZSpec.BroadcastAddress.DEFAULT,
3267
- apsFrame,
3268
- zclFrame.toBuffer(),
3269
- 0,
3270
- 0,
3271
- );
3272
- });
3273
-
3274
- it("Adapter impl: throws when sendZclFrameToAll fails request", async () => {
3275
- mockEzspSend.mockResolvedValueOnce([SLStatus.FAIL, 0]);
3276
-
3277
- const endpoint: number = 32;
3278
- const zclFrame = Zcl.Frame.create(Zcl.FrameType.GLOBAL, Zcl.Direction.SERVER_TO_CLIENT, true, undefined, 1, 1, 0, [{}], {});
3279
- const p = defuseRejection(adapter.sendZclFrameToAll(endpoint, zclFrame, 1, ZSpec.BroadcastAddress.DEFAULT));
3280
-
3281
- await vi.advanceTimersByTimeAsync(5000);
3282
- await expect(p).rejects.toThrow("~x~> [ZCL BROADCAST destination=65532] Failed to send with status=FAIL.");
3283
- expect(mockEzspSend).toHaveBeenCalledTimes(1);
3284
- });
3285
-
3286
- it("Adapter impl: setChannelInterPAN", async () => {
3287
- await expect(adapter.setChannelInterPAN(15)).resolves.toStrictEqual(undefined);
3288
- expect(mockEzspSetLogicalAndRadioChannel).toHaveBeenCalledWith(15);
3289
- });
3290
-
3291
- it("Adapter impl: throws when setChannelInterPAN fails request", async () => {
3292
- mockEzspSetLogicalAndRadioChannel.mockResolvedValueOnce(SLStatus.FAIL);
3293
-
3294
- await expect(adapter.setChannelInterPAN(15)).rejects.toThrow(`Failed to set InterPAN channel to '15' with status=FAIL.`);
3295
- expect(mockEzspSetLogicalAndRadioChannel).toHaveBeenCalledWith(15);
3296
- });
3297
-
3298
- it("Adapter impl: sendZclFrameInterPANToIeeeAddr", async () => {
3299
- const ieee: Eui64 = "0x1122334455667788";
3300
- const zclFrame = Zcl.Frame.create(
3301
- Zcl.FrameType.GLOBAL,
3302
- Zcl.Direction.CLIENT_TO_SERVER,
3303
- false,
3304
- undefined,
3305
- 3,
3306
- "read",
3307
- "genBasic",
3308
- [{attrId: 0}],
3309
- {},
3310
- );
3311
-
3312
- await expect(adapter.sendZclFrameInterPANToIeeeAddr(zclFrame, ieee)).resolves.toStrictEqual(undefined);
3313
- expect(mockEzspSendRawMessage).toHaveBeenCalledTimes(1);
3314
- expect(mockEzspSendRawMessage).toHaveBeenCalledWith(expect.any(Buffer), 1, true);
3315
- });
3316
-
3317
- it("Adapter impl: throws when sendZclFrameInterPANToIeeeAddr request fails", async () => {
3318
- mockEzspSendRawMessage.mockResolvedValueOnce(SLStatus.BUSY);
3319
-
3320
- const ieee: Eui64 = "0x1122334455667788";
3321
- const zclFrame = Zcl.Frame.create(
3322
- Zcl.FrameType.GLOBAL,
3323
- Zcl.Direction.CLIENT_TO_SERVER,
3324
- false,
3325
- undefined,
3326
- 3,
3327
- "read",
3328
- "genBasic",
3329
- [{attrId: 0}],
3330
- {},
3331
- );
3332
-
3333
- await expect(adapter.sendZclFrameInterPANToIeeeAddr(zclFrame, ieee)).rejects.toThrow(
3334
- `~x~> [ZCL TOUCHLINK to=${ieee}] Failed to send with status=${SLStatus[SLStatus.BUSY]}.`,
3335
- );
3336
- expect(mockEzspSendRawMessage).toHaveBeenCalledTimes(1);
3337
- expect(mockEzspSendRawMessage).toHaveBeenCalledWith(expect.any(Buffer), 1, true);
3338
- });
3339
-
3340
- it("Adapter impl: sendZclFrameInterPANBroadcast", async () => {
3341
- const zclFrame = Zcl.Frame.create(
3342
- Zcl.FrameType.SPECIFIC,
3343
- Zcl.Direction.CLIENT_TO_SERVER,
3344
- true,
3345
- undefined,
3346
- 0,
3347
- "scanRequest",
3348
- Zcl.Clusters.touchlink.ID,
3349
- {transactionID: 1, zigbeeInformation: 4, touchlinkInformation: 18},
3350
- {},
3351
- );
3352
- const sourcePanId: PanId = 0x1234;
3353
- const sourceAddress: Eui64 = "0x1122334455aabbcc";
3354
- const groupId: number = ZSpec.BroadcastAddress.SLEEPY;
3355
- const lastHopLqi = 252;
3356
- // Received Zigbee message from '0x', type 'readResponse', cluster 'genBasic', data '{"zclVersion":3}' from endpoint 1 with groupID 0
3357
- const messageContents = Buffer.from("1800010000000100000000000000000088776655443322110154466341200", "hex");
3358
-
3359
- mockEzspSendRawMessage.mockImplementationOnce(() => {
3360
- setTimeout(async () => {
3361
- mockEzspEmitter.emit("touchlinkMessage", sourcePanId, sourceAddress, groupId, lastHopLqi, messageContents);
3362
- await flushPromises();
3363
- }, 300);
3364
-
3365
- return SLStatus.OK;
3366
- });
3367
-
3368
- const p = adapter.sendZclFrameInterPANBroadcast(zclFrame, 10000);
3369
-
3370
- await vi.advanceTimersByTimeAsync(5000);
3371
-
3372
- const payload: ZclPayload = {
3373
- clusterID: Zcl.Clusters.touchlink.ID,
3374
- header: Zcl.Header.fromBuffer(messageContents),
3375
- address: sourceAddress,
3376
- data: messageContents,
3377
- endpoint: FIXED_ENDPOINTS[0].endpoint,
3378
- linkquality: lastHopLqi,
3379
- groupID: groupId,
3380
- wasBroadcast: true,
3381
- destinationEndpoint: FIXED_ENDPOINTS[0].endpoint,
3382
- };
3383
-
3384
- await expect(p).resolves.toStrictEqual(payload);
3385
- expect(mockEzspSendRawMessage).toHaveBeenCalledTimes(1);
3386
- expect(mockEzspSendRawMessage).toHaveBeenCalledWith(expect.any(Buffer), 1, true);
3387
- });
3388
-
3389
- it("Adapter impl: throws when sendZclFrameInterPANBroadcast command has no response", async () => {
3390
- const commandName = "readRsp";
3391
- const zclFrame = Zcl.Frame.create(
3392
- Zcl.FrameType.GLOBAL,
3393
- Zcl.Direction.CLIENT_TO_SERVER,
3394
- false,
3395
- undefined,
3396
- 3,
3397
- commandName,
3398
- "genBasic",
3399
- [{attrId: 0}],
3400
- {},
3401
- );
3402
-
3403
- await expect(adapter.sendZclFrameInterPANBroadcast(zclFrame, 10000)).rejects.toThrow(
3404
- `Command '${commandName}' has no response, cannot wait for response.`,
3405
- );
3406
- expect(mockEzspSendRawMessage).toHaveBeenCalledTimes(0);
3407
- });
3408
-
3409
- it("Adapter impl: throws when sendZclFrameInterPANBroadcast request fails", async () => {
3410
- mockEzspSendRawMessage.mockResolvedValueOnce(SLStatus.BUSY);
3411
-
3412
- const zclFrame = Zcl.Frame.create(
3413
- Zcl.FrameType.GLOBAL,
3414
- Zcl.Direction.CLIENT_TO_SERVER,
3415
- false,
3416
- undefined,
3417
- 3,
3418
- "read",
3419
- "genBasic",
3420
- [{attrId: 0}],
3421
- {},
3422
- );
3423
-
3424
- await expect(adapter.sendZclFrameInterPANBroadcast(zclFrame, 10000)).rejects.toThrow(
3425
- `~x~> [ZCL TOUCHLINK BROADCAST] Failed to send with status=${SLStatus[SLStatus.BUSY]}.`,
3426
- );
3427
- expect(mockEzspSendRawMessage).toHaveBeenCalledTimes(1);
3428
- expect(mockEzspSendRawMessage).toHaveBeenCalledWith(expect.any(Buffer), 1, true);
3429
- });
3430
-
3431
- it("Adapter impl: restoreChannelInterPAN", async () => {
3432
- const p = adapter.restoreChannelInterPAN();
3433
-
3434
- await vi.advanceTimersByTimeAsync(10000);
3435
- await expect(p).resolves.toStrictEqual(undefined);
3436
- expect(mockEzspSetLogicalAndRadioChannel).toHaveBeenCalledWith(DEFAULT_NETWORK_OPTIONS.channelList[0]);
3437
- });
3438
-
3439
- it("Adapter impl: throws when restoreChannelInterPAN fails request", async () => {
3440
- mockEzspSetLogicalAndRadioChannel.mockResolvedValueOnce(SLStatus.FAIL);
3441
-
3442
- const p = defuseRejection(adapter.restoreChannelInterPAN());
3443
-
3444
- await vi.advanceTimersByTimeAsync(10000);
3445
- await expect(p).rejects.toThrow(`Failed to restore InterPAN channel to '${DEFAULT_NETWORK_OPTIONS.channelList[0]}' with status=FAIL.`);
3446
- expect(mockEzspSetLogicalAndRadioChannel).toHaveBeenCalledWith(DEFAULT_NETWORK_OPTIONS.channelList[0]);
3447
- });
3448
- });
3449
- });