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,2265 +0,0 @@
1
- import {randomBytes} from "node:crypto";
2
- import {existsSync, readFileSync, renameSync} from "node:fs";
3
- import path from "node:path";
4
-
5
- import equals from "fast-deep-equal/es6";
6
- import type {Backup, UnifiedBackupStorage} from "../../../models";
7
- import {BackupUtils, Queue, wait} from "../../../utils";
8
- import {logger} from "../../../utils/logger";
9
- import * as ZSpec from "../../../zspec";
10
- import type {Eui64, ExtendedPanId, NodeId, PanId} from "../../../zspec/tstypes";
11
- import * as Zcl from "../../../zspec/zcl";
12
- import * as Zdo from "../../../zspec/zdo";
13
- import type * as ZdoTypes from "../../../zspec/zdo/definition/tstypes";
14
- import {Adapter, type TsType} from "../..";
15
- import {WORKAROUND_JOIN_MANUF_IEEE_PREFIX_TO_CODE} from "../../const";
16
- import type {DeviceJoinedPayload, DeviceLeavePayload, ZclPayload} from "../../events";
17
- import {
18
- EMBER_HIGH_RAM_CONCENTRATOR,
19
- EMBER_LOW_RAM_CONCENTRATOR,
20
- EMBER_MIN_BROADCAST_ADDRESS,
21
- INTERPAN_APS_FRAME_TYPE,
22
- INVALID_RADIO_CHANNEL,
23
- LONG_DEST_FRAME_CONTROL,
24
- MAC_ACK_REQUIRED,
25
- MAXIMUM_INTERPAN_LENGTH,
26
- SECURITY_LEVEL_Z3,
27
- SHORT_DEST_FRAME_CONTROL,
28
- STACK_PROFILE_ZIGBEE_PRO,
29
- STUB_NWK_FRAME_CONTROL,
30
- ZIGBEE_PROFILE_INTEROPERABILITY_LINK_KEY,
31
- } from "../consts";
32
- import {
33
- EmberApsOption,
34
- EmberDeviceUpdate,
35
- EmberExtendedSecurityBitmask,
36
- EmberIncomingMessageType,
37
- EmberInitialSecurityBitmask,
38
- EmberInterpanMessageType,
39
- EmberJoinDecision,
40
- EmberJoinMethod,
41
- EmberNetworkInitBitmask,
42
- EmberNetworkStatus,
43
- EmberNodeType,
44
- EmberOutgoingMessageType,
45
- EmberSourceRouteDiscoveryMode,
46
- EmberTransmitPriority,
47
- EmberVersionType,
48
- EzspStatus,
49
- IEEE802154CcaMode,
50
- SecManKeyType,
51
- SLStatus,
52
- } from "../enums";
53
- import {EzspBuffalo} from "../ezsp/buffalo";
54
- import {EMBER_ENCRYPTION_KEY_SIZE, EZSP_MIN_PROTOCOL_VERSION, EZSP_PROTOCOL_VERSION, EZSP_STACK_TYPE_MESH} from "../ezsp/consts";
55
- import {EzspConfigId, EzspDecisionBitmask, EzspDecisionId, EzspPolicyId, EzspValueId} from "../ezsp/enums";
56
- import {Ezsp} from "../ezsp/ezsp";
57
- import {EzspError} from "../ezspError";
58
- import type {
59
- EmberApsFrame,
60
- EmberInitialSecurityState,
61
- EmberKeyData,
62
- EmberMulticastId,
63
- EmberMulticastTableEntry,
64
- EmberNetworkInitStruct,
65
- EmberNetworkParameters,
66
- EmberVersion,
67
- SecManAPSKeyMetadata,
68
- SecManContext,
69
- SecManKey,
70
- } from "../types";
71
- import {initNetworkCache, initSecurityManagerContext} from "../utils/initters";
72
- import {lowHighBytes} from "../utils/math";
73
- import {FIXED_ENDPOINTS} from "./endpoints";
74
- import {EmberOneWaitress, OneWaitressEvents} from "./oneWaitress";
75
-
76
- const NS = "zh:ember";
77
-
78
- export type NetworkCache = {
79
- //-- basic network info
80
- eui64: Eui64;
81
- parameters: EmberNetworkParameters;
82
- };
83
-
84
- /**
85
- * Use for a link key backup.
86
- *
87
- * Each entry notes the EUI64 of the device it is paired to and the key data.
88
- * This key may be hashed and not the actual link key currently in use.
89
- */
90
- export type LinkKeyBackupData = {
91
- deviceEui64: Eui64;
92
- key: EmberKeyData;
93
- outgoingFrameCounter: number;
94
- incomingFrameCounter: number;
95
- };
96
-
97
- enum NetworkInitAction {
98
- /** Ain't that nice! */
99
- DONE = 0,
100
- /** Config mismatch, must leave network. */
101
- LEAVE = 1,
102
- /** Config mismatched, left network. Will evaluate forming from backup or config next. */
103
- LEFT = 2,
104
- /** Form the network using config. No backup, or backup mismatch. */
105
- FORM_CONFIG = 3,
106
- /** Re-form the network using full backed-up data. */
107
- FORM_BACKUP = 4,
108
- }
109
-
110
- /**
111
- * Application generated ZDO messages use sequence numbers 0-127, and the stack
112
- * uses sequence numbers 128-255. This simplifies life by eliminating the need
113
- * for coordination between the two entities, and allows both to send ZDO
114
- * messages with non-conflicting sequence numbers.
115
- */
116
- const APPLICATION_ZDO_SEQUENCE_MASK = 0x7f;
117
- /* Default radius used for broadcast ZDO requests. uint8_t */
118
- const ZDO_REQUEST_RADIUS = 0xff;
119
- /** Oldest supported EZSP version for backups. Don't take the risk to restore a broken network until older backup versions can be investigated. */
120
- const BACKUP_OLDEST_SUPPORTED_EZSP_VERSION = 12;
121
- /**
122
- * 9sec is minimum recommended for `ezspBroadcastNextNetworkKey` to have propagated throughout network.
123
- * NOTE: This is blocking the request queue, so we shouldn't go crazy high.
124
- */
125
- const BROADCAST_NETWORK_KEY_SWITCH_WAIT_TIME = 15000;
126
-
127
- const QUEUE_MAX_SEND_ATTEMPTS = 3;
128
- const QUEUE_BUSY_DEFER_MSEC = 500;
129
- const QUEUE_NETWORK_DOWN_DEFER_MSEC = 1500;
130
-
131
- type StackConfig = {
132
- CONCENTRATOR_RAM_TYPE: "high" | "low";
133
- /**
134
- * Minimum Time between broadcasts (in seconds) <1-60>
135
- * Default: 10
136
- * The minimum amount of time that must pass between MTORR broadcasts.
137
- */
138
- CONCENTRATOR_MIN_TIME: number;
139
- /**
140
- * Maximum Time between broadcasts (in seconds) <30-300>
141
- * Default: 60
142
- * The maximum amount of time that can pass between MTORR broadcasts.
143
- */
144
- CONCENTRATOR_MAX_TIME: number;
145
- /**
146
- * Route Error Threshold <1-100>
147
- * Default: 3
148
- * The number of route errors that will trigger a re-broadcast of the MTORR.
149
- */
150
- CONCENTRATOR_ROUTE_ERROR_THRESHOLD: number;
151
- /**
152
- * Delivery Failure Threshold <1-100>
153
- * Default: 1
154
- * The number of APS delivery failures that will trigger a re-broadcast of the MTORR.
155
- */
156
- CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD: number;
157
- /**
158
- * Maximum number of hops for Broadcast <0-30>
159
- * Default: 0
160
- * The maximum number of hops that the MTORR broadcast will be allowed to have.
161
- * A value of 0 will be converted to the EMBER_MAX_HOPS value set by the stack.
162
- */
163
- CONCENTRATOR_MAX_HOPS: number;
164
- /** <6-64> (Default: 6) @see EzspConfigId.MAX_END_DEVICE_CHILDREN */
165
- MAX_END_DEVICE_CHILDREN: number;
166
- /** <-> (Default: 10000) @see EzspValueId.TRANSIENT_DEVICE_TIMEOUT */
167
- TRANSIENT_DEVICE_TIMEOUT: number;
168
- /** <0-14> (Default: 8) @see EzspConfigId.END_DEVICE_POLL_TIMEOUT */
169
- END_DEVICE_POLL_TIMEOUT: number;
170
- /** <0-65535> (Default: 300) @see EzspConfigId.TRANSIENT_KEY_TIMEOUT_S */
171
- TRANSIENT_KEY_TIMEOUT_S: number;
172
- /**@see Ezsp.ezspSetRadioIeee802154CcaMode */
173
- CCA_MODE?: keyof typeof IEEE802154CcaMode;
174
- };
175
-
176
- /**
177
- * Default stack configuration values.
178
- * @see https://www.silabs.com/documents/public/user-guides/ug100-ezsp-reference-guide.pdf 2.3.1 for descriptions/RAM costs
179
- *
180
- * https://github.com/darkxst/silabs-firmware-builder/tree/main/manifests
181
- * https://github.com/NabuCasa/silabs-firmware/wiki/Zigbee-EmberZNet-NCP-firmware-configuration#skyconnect
182
- * https://github.com/SiliconLabs/UnifySDK/blob/main/applications/zigbeed/project_files/zigbeed.slcp
183
- */
184
- export const DEFAULT_STACK_CONFIG: Readonly<StackConfig> = {
185
- CONCENTRATOR_RAM_TYPE: "high",
186
- CONCENTRATOR_MIN_TIME: 5, // zigpc: 10
187
- CONCENTRATOR_MAX_TIME: 60, // zigpc: 60
188
- CONCENTRATOR_ROUTE_ERROR_THRESHOLD: 3, // zigpc: 3
189
- CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD: 1, // zigpc: 1, ZigbeeMinimalHost: 3
190
- CONCENTRATOR_MAX_HOPS: 0, // zigpc: 0
191
- MAX_END_DEVICE_CHILDREN: 32, // zigpc: 6, nabucasa: 32, Dongle-E (Sonoff firmware): 32
192
- TRANSIENT_DEVICE_TIMEOUT: 10000,
193
- END_DEVICE_POLL_TIMEOUT: 8, // zigpc: 8
194
- TRANSIENT_KEY_TIMEOUT_S: 300, // zigpc: 65535
195
- CCA_MODE: undefined, // not set by default
196
- };
197
- /** Default behavior is to disable app key requests */
198
- const ALLOW_APP_KEY_REQUESTS = false;
199
- /** @see EzspConfigId.TRUST_CENTER_ADDRESS_CACHE_SIZE */
200
- const TRUST_CENTER_ADDRESS_CACHE_SIZE = 2;
201
-
202
- /**
203
- * NOTE: This from SDK is currently ignored here because of issues in below links:
204
- * - BUGZID 12261: Concentrators use MTORRs for route discovery and should not enable route discovery in the APS options.
205
- * - https://community.silabs.com/s/question/0D58Y00008DRfDCSA1/coordinator-cant-send-unicast-to-sleepy-node-after-reboot
206
- * - https://community.silabs.com/s/question/0D58Y0000B4nTb7SQE/largedense-network-communication-problem-source-route-table-not-big-enough
207
- *
208
- * Removing `ENABLE_ROUTE_DISCOVERY` leads to devices that won't reconnect/go offline, and various other issues. Keeping it for now.
209
- */
210
- export const DEFAULT_APS_OPTIONS = EmberApsOption.RETRY | EmberApsOption.ENABLE_ROUTE_DISCOVERY | EmberApsOption.ENABLE_ADDRESS_DISCOVERY;
211
- /** Time for a request to get a callback response. ASH is 2400*6 for ACK timeout. */
212
- const DEFAULT_REQUEST_TIMEOUT = 15000; // msec
213
- /** Time for a network-related request to get a response (usually via event). */
214
- const DEFAULT_NETWORK_REQUEST_TIMEOUT = 10000; // nothing on the network to bother requests, should be much faster than this
215
- /** Time between watchdog counters reading/clearing */
216
- const WATCHDOG_COUNTERS_FEED_INTERVAL = 3600000; // every hour...
217
- /** Default manufacturer code reported by coordinator. */
218
- const DEFAULT_MANUFACTURER_CODE = Zcl.ManufacturerCode.SILICON_LABORATORIES;
219
-
220
- /**
221
- * Relay calls between Z2M and EZSP-layer and handle any error that might occur via queue & waitress.
222
- *
223
- * Anything post `start` that requests anything from the EZSP layer must run through the request queue for proper execution flow.
224
- */
225
- export class EmberAdapter extends Adapter {
226
- /** Current manufacturer code assigned to the coordinator. Used for join workarounds... */
227
- private manufacturerCode: Zcl.ManufacturerCode;
228
- public readonly stackConfig: StackConfig;
229
-
230
- private ezsp: Ezsp;
231
- private version: {ezsp: number; revision: string} & EmberVersion;
232
-
233
- private readonly queue: Queue;
234
- private readonly oneWaitress: EmberOneWaitress;
235
- /** Periodically retrieve counters then clear them. */
236
- private watchdogCountersHandle?: NodeJS.Timeout;
237
-
238
- /** Sequence number used for ZDO requests. static uint8_t */
239
- private zdoRequestSequence: number;
240
-
241
- private interpanLock: boolean;
242
-
243
- /**
244
- * Cached network params to avoid NCP calls. Prevents frequent EZSP transactions.
245
- * NOTE: Do not use directly, use getter functions for it that check if valid or need retrieval from NCP.
246
- */
247
- private networkCache: NetworkCache;
248
- private multicastTable: EmberMulticastId[];
249
-
250
- constructor(
251
- networkOptions: TsType.NetworkOptions,
252
- serialPortOptions: TsType.SerialPortOptions,
253
- backupPath: string,
254
- adapterOptions: TsType.AdapterOptions,
255
- ) {
256
- super(networkOptions, serialPortOptions, backupPath, adapterOptions);
257
- this.hasZdoMessageOverhead = true;
258
- this.manufacturerID = Zcl.ManufacturerCode.SILICON_LABORATORIES;
259
-
260
- this.version = {
261
- ezsp: 0,
262
- revision: "unknown",
263
- build: 0,
264
- major: 0,
265
- minor: 0,
266
- patch: 0,
267
- special: 0,
268
- type: EmberVersionType.GA,
269
- };
270
- this.zdoRequestSequence = 0; // start at 1
271
- this.interpanLock = false;
272
- this.networkCache = initNetworkCache();
273
- this.manufacturerCode = DEFAULT_MANUFACTURER_CODE; // will be set in NCP in initEzsp
274
- this.multicastTable = [];
275
-
276
- this.stackConfig = this.loadStackConfig();
277
- this.queue = new Queue(this.adapterOptions.concurrent || 16); // ORed to avoid 0 (not checked in settings/queue constructor)
278
- this.oneWaitress = new EmberOneWaitress();
279
-
280
- this.ezsp = new Ezsp(serialPortOptions);
281
-
282
- this.ezsp.on("zdoResponse", this.onZDOResponse.bind(this));
283
- this.ezsp.on("incomingMessage", this.onIncomingMessage.bind(this));
284
- this.ezsp.on("touchlinkMessage", this.onTouchlinkMessage.bind(this));
285
- this.ezsp.on("stackStatus", this.onStackStatus.bind(this));
286
- this.ezsp.on("trustCenterJoin", this.onTrustCenterJoin.bind(this));
287
- this.ezsp.on("messageSent", this.onMessageSent.bind(this));
288
- this.ezsp.once("ncpNeedsResetAndInit", this.onNcpNeedsResetAndInit.bind(this));
289
- }
290
-
291
- private loadStackConfig(): StackConfig {
292
- // store stack config in same dir as backup
293
- const configPath = path.join(path.dirname(this.backupPath), "stack_config.json");
294
-
295
- try {
296
- const customConfig: StackConfig = JSON.parse(readFileSync(configPath, "utf8"));
297
- // set any undefined config to default
298
- const config: StackConfig = {...DEFAULT_STACK_CONFIG, ...customConfig};
299
-
300
- const inRange = (value: number, min: number, max: number): boolean => !(value == null || value < min || value > max);
301
-
302
- if (!["high", "low"].includes(config.CONCENTRATOR_RAM_TYPE)) {
303
- config.CONCENTRATOR_RAM_TYPE = DEFAULT_STACK_CONFIG.CONCENTRATOR_RAM_TYPE;
304
- logger.error("[STACK CONFIG] Invalid CONCENTRATOR_RAM_TYPE, using default.", NS);
305
- }
306
-
307
- if (!inRange(config.CONCENTRATOR_MIN_TIME, 1, 60) || config.CONCENTRATOR_MIN_TIME >= config.CONCENTRATOR_MAX_TIME) {
308
- config.CONCENTRATOR_MIN_TIME = DEFAULT_STACK_CONFIG.CONCENTRATOR_MIN_TIME;
309
- logger.error("[STACK CONFIG] Invalid CONCENTRATOR_MIN_TIME, using default.", NS);
310
- }
311
-
312
- if (!inRange(config.CONCENTRATOR_MAX_TIME, 30, 300) || config.CONCENTRATOR_MAX_TIME <= config.CONCENTRATOR_MIN_TIME) {
313
- config.CONCENTRATOR_MAX_TIME = DEFAULT_STACK_CONFIG.CONCENTRATOR_MAX_TIME;
314
- logger.error("[STACK CONFIG] Invalid CONCENTRATOR_MAX_TIME, using default.", NS);
315
- }
316
-
317
- if (!inRange(config.CONCENTRATOR_ROUTE_ERROR_THRESHOLD, 1, 100)) {
318
- config.CONCENTRATOR_ROUTE_ERROR_THRESHOLD = DEFAULT_STACK_CONFIG.CONCENTRATOR_ROUTE_ERROR_THRESHOLD;
319
- logger.error("[STACK CONFIG] Invalid CONCENTRATOR_ROUTE_ERROR_THRESHOLD, using default.", NS);
320
- }
321
-
322
- if (!inRange(config.CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD, 1, 100)) {
323
- config.CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD = DEFAULT_STACK_CONFIG.CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD;
324
- logger.error("[STACK CONFIG] Invalid CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD, using default.", NS);
325
- }
326
-
327
- if (!inRange(config.CONCENTRATOR_MAX_HOPS, 0, 30)) {
328
- config.CONCENTRATOR_MAX_HOPS = DEFAULT_STACK_CONFIG.CONCENTRATOR_MAX_HOPS;
329
- logger.error("[STACK CONFIG] Invalid CONCENTRATOR_MAX_HOPS, using default.", NS);
330
- }
331
-
332
- if (!inRange(config.MAX_END_DEVICE_CHILDREN, 6, 64)) {
333
- config.MAX_END_DEVICE_CHILDREN = DEFAULT_STACK_CONFIG.MAX_END_DEVICE_CHILDREN;
334
- logger.error("[STACK CONFIG] Invalid MAX_END_DEVICE_CHILDREN, using default.", NS);
335
- }
336
-
337
- if (!inRange(config.TRANSIENT_DEVICE_TIMEOUT, 0, 65535)) {
338
- config.TRANSIENT_DEVICE_TIMEOUT = DEFAULT_STACK_CONFIG.TRANSIENT_DEVICE_TIMEOUT;
339
- logger.error("[STACK CONFIG] Invalid TRANSIENT_DEVICE_TIMEOUT, using default.", NS);
340
- }
341
-
342
- if (!inRange(config.END_DEVICE_POLL_TIMEOUT, 0, 14)) {
343
- config.END_DEVICE_POLL_TIMEOUT = DEFAULT_STACK_CONFIG.END_DEVICE_POLL_TIMEOUT;
344
- logger.error("[STACK CONFIG] Invalid END_DEVICE_POLL_TIMEOUT, using default.", NS);
345
- }
346
-
347
- if (!inRange(config.TRANSIENT_KEY_TIMEOUT_S, 0, 65535)) {
348
- config.TRANSIENT_KEY_TIMEOUT_S = DEFAULT_STACK_CONFIG.TRANSIENT_KEY_TIMEOUT_S;
349
- logger.error("[STACK CONFIG] Invalid TRANSIENT_KEY_TIMEOUT_S, using default.", NS);
350
- }
351
-
352
- config.CCA_MODE = config.CCA_MODE ?? undefined; // always default to undefined
353
-
354
- if (config.CCA_MODE && IEEE802154CcaMode[config.CCA_MODE] === undefined) {
355
- config.CCA_MODE = undefined;
356
- logger.error("[STACK CONFIG] Invalid CCA_MODE, ignoring.", NS);
357
- }
358
-
359
- logger.info(`Using stack config ${JSON.stringify(config)}.`, NS);
360
- return config;
361
- } catch {
362
- /* empty */
363
- }
364
-
365
- logger.info("Using default stack config.", NS);
366
- return DEFAULT_STACK_CONFIG;
367
- }
368
-
369
- /**
370
- * Emitted from @see Ezsp.ezspStackStatusHandler
371
- * @param status
372
- */
373
- private async onStackStatus(status: SLStatus): Promise<void> {
374
- // to be extra careful, should clear network cache upon receiving this.
375
- this.clearNetworkCache();
376
-
377
- switch (status) {
378
- case SLStatus.NETWORK_UP: {
379
- this.oneWaitress.resolveEvent(OneWaitressEvents.STACK_STATUS_NETWORK_UP);
380
- logger.info("[STACK STATUS] Network up.", NS);
381
- break;
382
- }
383
- case SLStatus.NETWORK_DOWN: {
384
- this.oneWaitress.resolveEvent(OneWaitressEvents.STACK_STATUS_NETWORK_DOWN);
385
- logger.info("[STACK STATUS] Network down.", NS);
386
- break;
387
- }
388
- case SLStatus.ZIGBEE_NETWORK_OPENED: {
389
- this.oneWaitress.resolveEvent(OneWaitressEvents.STACK_STATUS_NETWORK_OPENED);
390
- logger.info("[STACK STATUS] Network opened.", NS);
391
- break;
392
- }
393
- case SLStatus.ZIGBEE_NETWORK_CLOSED: {
394
- this.oneWaitress.resolveEvent(OneWaitressEvents.STACK_STATUS_NETWORK_CLOSED);
395
- logger.info("[STACK STATUS] Network closed.", NS);
396
-
397
- if (this.manufacturerCode !== DEFAULT_MANUFACTURER_CODE) {
398
- await this.queue.execute<void>(async () => {
399
- logger.debug("[WORKAROUND] Reverting coordinator manufacturer code to default.", NS);
400
- await this.ezsp.ezspSetManufacturerCode(DEFAULT_MANUFACTURER_CODE);
401
-
402
- this.manufacturerCode = DEFAULT_MANUFACTURER_CODE;
403
- });
404
- }
405
-
406
- break;
407
- }
408
- case SLStatus.ZIGBEE_CHANNEL_CHANGED: {
409
- // invalidate cache
410
- this.networkCache.parameters.radioChannel = INVALID_RADIO_CHANNEL;
411
- logger.info("[STACK STATUS] Channel changed.", NS);
412
- break;
413
- }
414
- default: {
415
- logger.debug(`[STACK STATUS] ${SLStatus[status]}.`, NS);
416
- break;
417
- }
418
- }
419
- }
420
-
421
- /**
422
- * Emitted from @see Ezsp.ezspMessageSentHandler
423
- * WARNING: Cannot rely on `ezspMessageSentHandler` > `ezspIncomingMessageHandler` order, some devices mix it up!
424
- *
425
- * @param type
426
- * @param indexOrDestination
427
- * @param apsFrame
428
- * @param messageTag
429
- * @param status
430
- */
431
- private async onMessageSent(
432
- status: SLStatus,
433
- type: EmberOutgoingMessageType,
434
- indexOrDestination: number,
435
- apsFrame: EmberApsFrame,
436
- messageTag: number,
437
- ): Promise<void> {
438
- switch (status) {
439
- case SLStatus.ZIGBEE_DELIVERY_FAILED: {
440
- logger.debug(
441
- () =>
442
- `~x~> DELIVERY_FAILED [indexOrDestination=${indexOrDestination} apsFrame=${JSON.stringify(apsFrame)} messageTag=${messageTag}]`,
443
- NS,
444
- );
445
-
446
- // no ACK was received from the destination
447
- switch (type) {
448
- case EmberOutgoingMessageType.BROADCAST:
449
- case EmberOutgoingMessageType.BROADCAST_WITH_ALIAS:
450
- case EmberOutgoingMessageType.MULTICAST:
451
- case EmberOutgoingMessageType.MULTICAST_WITH_ALIAS: {
452
- // BC/MC not checking for message sent, avoid unnecessary waitress lookups
453
- logger.error(`Delivery of ${EmberOutgoingMessageType[type]} failed for '${indexOrDestination}'.`, NS);
454
- break;
455
- }
456
- default: {
457
- // reject any waitress early (don't wait for timeout if we know we're gonna get there eventually)
458
- this.oneWaitress.deliveryFailedFor(indexOrDestination, apsFrame);
459
- break;
460
- }
461
- }
462
-
463
- break;
464
- }
465
- case SLStatus.OK: {
466
- if (
467
- type === EmberOutgoingMessageType.MULTICAST &&
468
- apsFrame.destinationEndpoint === 0xff &&
469
- apsFrame.groupId < EMBER_MIN_BROADCAST_ADDRESS &&
470
- !this.multicastTable.includes(apsFrame.groupId)
471
- ) {
472
- // workaround for devices using multicast for state update (coordinator passthrough)
473
- const tableIdx = this.multicastTable.length;
474
- const multicastEntry: EmberMulticastTableEntry = {
475
- multicastId: apsFrame.groupId,
476
- endpoint: FIXED_ENDPOINTS[0].endpoint,
477
- networkIndex: FIXED_ENDPOINTS[0].networkIndex,
478
- };
479
- // set immediately to avoid potential race
480
- this.multicastTable.push(multicastEntry.multicastId);
481
-
482
- try {
483
- await this.queue.execute<void>(async () => {
484
- const status = await this.ezsp.ezspSetMulticastTableEntry(tableIdx, multicastEntry);
485
-
486
- if (status !== SLStatus.OK) {
487
- throw new Error(
488
- `Failed to register group '${multicastEntry.multicastId}' in multicast table with status=${SLStatus[status]}.`,
489
- );
490
- }
491
-
492
- logger.debug(() => `Registered multicast table entry (${tableIdx}): ${JSON.stringify(multicastEntry)}.`, NS);
493
- });
494
- } catch (error) {
495
- // remove to allow retry on next occurrence
496
- this.multicastTable.splice(tableIdx, 1);
497
- logger.error((error as Error).message, NS);
498
- }
499
- }
500
-
501
- break;
502
- }
503
- }
504
- // shouldn't be any other status
505
- }
506
-
507
- /**
508
- * Emitted from @see Ezsp.ezspIncomingMessageHandler
509
- *
510
- * @param apsFrame The APS frame associated with the response.
511
- * @param sender The sender of the response. Should match `payload.nodeId` in many responses.
512
- * @param messageContents The content of the response.
513
- */
514
- private onZDOResponse(apsFrame: EmberApsFrame, sender: NodeId, messageContents: Buffer): void {
515
- const result = Zdo.Buffalo.readResponse(this.hasZdoMessageOverhead, apsFrame.clusterId, messageContents);
516
-
517
- if (apsFrame.clusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE) {
518
- // special case to properly resolve a NETWORK_ADDRESS_RESPONSE following a NETWORK_ADDRESS_REQUEST (based on EUI64 from ZDO payload)
519
- // NOTE: if response has invalid status (no EUI64 available), response waiter will eventually time out
520
- if (Zdo.Buffalo.checkStatus<Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE>(result)) {
521
- this.oneWaitress.resolveZDO(result[1].eui64, apsFrame, result);
522
- }
523
- } else {
524
- this.oneWaitress.resolveZDO(sender, apsFrame, result);
525
- }
526
-
527
- this.emit("zdoResponse", apsFrame.clusterId, result);
528
- }
529
-
530
- /**
531
- * Emitted from @see Ezsp.ezspIncomingMessageHandler @see Ezsp.ezspGpepIncomingMessageHandler
532
- *
533
- * @param type
534
- * @param apsFrame
535
- * @param lastHopLqi
536
- * @param sender
537
- * @param messageContents
538
- */
539
- private onIncomingMessage(
540
- type: EmberIncomingMessageType,
541
- apsFrame: EmberApsFrame,
542
- lastHopLqi: number,
543
- sender: NodeId,
544
- messageContents: Buffer,
545
- ): void {
546
- const payload: ZclPayload = {
547
- clusterID: apsFrame.clusterId,
548
- header: Zcl.Header.fromBuffer(messageContents),
549
- address: sender,
550
- data: messageContents,
551
- endpoint: apsFrame.sourceEndpoint,
552
- linkquality: lastHopLqi,
553
- groupID: apsFrame.groupId,
554
- wasBroadcast: type === EmberIncomingMessageType.BROADCAST || type === EmberIncomingMessageType.BROADCAST_LOOPBACK,
555
- destinationEndpoint: apsFrame.destinationEndpoint,
556
- };
557
-
558
- this.oneWaitress.resolveZCL(payload);
559
- this.emit("zclPayload", payload);
560
- }
561
-
562
- /**
563
- * Emitted from @see Ezsp.ezspMacFilterMatchMessageHandler when the message is a valid InterPAN touchlink message.
564
- *
565
- * @param _sourcePanId
566
- * @param sourceAddress
567
- * @param groupId
568
- * @param lastHopLqi
569
- * @param messageContents
570
- */
571
- private onTouchlinkMessage(_sourcePanId: PanId, sourceAddress: Eui64, groupId: number, lastHopLqi: number, messageContents: Buffer): void {
572
- const endpoint = FIXED_ENDPOINTS[0].endpoint;
573
- const payload: ZclPayload = {
574
- clusterID: Zcl.Clusters.touchlink.ID,
575
- data: messageContents,
576
- header: Zcl.Header.fromBuffer(messageContents),
577
- address: sourceAddress,
578
- endpoint: endpoint, // arbitrary since not sent over-the-air
579
- linkquality: lastHopLqi,
580
- groupID: groupId,
581
- wasBroadcast: true, // XXX: since always sent broadcast atm...
582
- destinationEndpoint: endpoint,
583
- };
584
-
585
- this.oneWaitress.resolveZCL(payload);
586
- this.emit("zclPayload", payload);
587
- }
588
-
589
- /**
590
- * Emitted from @see Ezsp.ezspTrustCenterJoinHandler
591
- * Also from @see Ezsp.ezspIdConflictHandler as a DEVICE_LEFT
592
- *
593
- * @param newNodeId
594
- * @param newNodeEui64
595
- * @param status
596
- * @param policyDecision
597
- * @param parentOfNewNodeId
598
- */
599
- private async onTrustCenterJoin(
600
- newNodeId: NodeId,
601
- newNodeEui64: Eui64,
602
- status: EmberDeviceUpdate,
603
- policyDecision: EmberJoinDecision,
604
- parentOfNewNodeId: NodeId,
605
- ): Promise<void> {
606
- if (status === EmberDeviceUpdate.DEVICE_LEFT) {
607
- const payload: DeviceLeavePayload = {
608
- networkAddress: newNodeId,
609
- ieeeAddr: newNodeEui64,
610
- };
611
-
612
- this.emit("deviceLeave", payload);
613
- } else {
614
- if (policyDecision !== EmberJoinDecision.DENY_JOIN) {
615
- const payload: DeviceJoinedPayload = {
616
- networkAddress: newNodeId,
617
- ieeeAddr: newNodeEui64,
618
- };
619
-
620
- // set workaround manuf code if necessary, or revert to default if previous joined device required workaround and new one does not
621
- const joinManufCode = WORKAROUND_JOIN_MANUF_IEEE_PREFIX_TO_CODE[newNodeEui64.substring(0, 8)] ?? DEFAULT_MANUFACTURER_CODE;
622
-
623
- if (this.manufacturerCode !== joinManufCode) {
624
- await this.queue.execute<void>(async () => {
625
- logger.debug(`[WORKAROUND] Setting coordinator manufacturer code to ${Zcl.ManufacturerCode[joinManufCode]}.`, NS);
626
- await this.ezsp.ezspSetManufacturerCode(joinManufCode);
627
-
628
- this.manufacturerCode = joinManufCode;
629
-
630
- this.emit("deviceJoined", payload);
631
- });
632
- } else {
633
- this.emit("deviceJoined", payload);
634
- }
635
- } else {
636
- logger.warning(`[TRUST CENTER] Device ${newNodeId}:${newNodeEui64} was denied joining via ${parentOfNewNodeId}.`, NS);
637
- }
638
- }
639
- }
640
-
641
- private async watchdogCounters(): Promise<void> {
642
- await this.queue.execute<void>(async () => {
643
- // listed as per EmberCounterType
644
- const ncpCounters = await this.ezsp.ezspReadAndClearCounters();
645
-
646
- logger.info(`[NCP COUNTERS] ${ncpCounters.join(",")}`, NS);
647
-
648
- const ashCounters = this.ezsp.ash.readAndClearCounters();
649
-
650
- logger.info(`[ASH COUNTERS] ${ashCounters.join(",")}`, NS);
651
- });
652
- }
653
-
654
- /**
655
- * Proceed to execute the long list of commands required to setup comms between Host<>NCP.
656
- * This is called by start and on internal reset.
657
- */
658
- private async initEzsp(): Promise<TsType.StartResult> {
659
- let result: TsType.StartResult = "resumed";
660
-
661
- // NOTE: something deep in this call can throw too
662
- const startResult = await this.ezsp.start();
663
-
664
- if (startResult !== EzspStatus.SUCCESS) {
665
- throw new Error(`Failed to start EZSP layer with status=${EzspStatus[startResult]}.`);
666
- }
667
-
668
- // call before any other command, else fails
669
- await this.emberVersion();
670
-
671
- /** The address cache needs to be initialized and used with the source routing code for the trust center to operate properly. */
672
- await this.emberSetEzspConfigValue(EzspConfigId.TRUST_CENTER_ADDRESS_CACHE_SIZE, TRUST_CENTER_ADDRESS_CACHE_SIZE);
673
- /** MAC indirect timeout should be 7.68 secs (STACK_PROFILE_ZIGBEE_PRO) */
674
- await this.emberSetEzspConfigValue(EzspConfigId.INDIRECT_TRANSMISSION_TIMEOUT, 7680);
675
- /** Max hops should be 2 * nwkMaxDepth, where nwkMaxDepth is 15 (STACK_PROFILE_ZIGBEE_PRO) */
676
- await this.emberSetEzspConfigValue(EzspConfigId.MAX_HOPS, 30);
677
- await this.emberSetEzspConfigValue(EzspConfigId.SUPPORTED_NETWORKS, 1);
678
- // allow other devices to modify the binding table
679
- await this.emberSetEzspPolicy(
680
- EzspPolicyId.BINDING_MODIFICATION_POLICY,
681
- EzspDecisionId.CHECK_BINDING_MODIFICATIONS_ARE_VALID_ENDPOINT_CLUSTERS,
682
- );
683
- // return message tag only in ezspMessageSentHandler()
684
- await this.emberSetEzspPolicy(EzspPolicyId.MESSAGE_CONTENTS_IN_CALLBACK_POLICY, EzspDecisionId.MESSAGE_TAG_ONLY_IN_CALLBACK);
685
- await this.emberSetEzspValue(EzspValueId.TRANSIENT_DEVICE_TIMEOUT, 2, lowHighBytes(this.stackConfig.TRANSIENT_DEVICE_TIMEOUT));
686
- await this.ezsp.ezspSetManufacturerCode(this.manufacturerCode);
687
- // network security init
688
- await this.emberSetEzspConfigValue(EzspConfigId.STACK_PROFILE, STACK_PROFILE_ZIGBEE_PRO);
689
- await this.emberSetEzspConfigValue(EzspConfigId.SECURITY_LEVEL, SECURITY_LEVEL_Z3);
690
- // common configs
691
- await this.emberSetEzspConfigValue(EzspConfigId.MAX_END_DEVICE_CHILDREN, this.stackConfig.MAX_END_DEVICE_CHILDREN);
692
- await this.emberSetEzspConfigValue(EzspConfigId.END_DEVICE_POLL_TIMEOUT, this.stackConfig.END_DEVICE_POLL_TIMEOUT);
693
- await this.emberSetEzspConfigValue(EzspConfigId.TRANSIENT_KEY_TIMEOUT_S, this.stackConfig.TRANSIENT_KEY_TIMEOUT_S);
694
- // XXX: temp-fix: forces a side-effect in the firmware that prevents broadcast issues in environments with unusual interferences
695
- await this.emberSetEzspValue(EzspValueId.CCA_THRESHOLD, 1, [0]);
696
-
697
- if (this.stackConfig.CCA_MODE) {
698
- // validated in `loadStackConfig`
699
- await this.ezsp.ezspSetRadioIeee802154CcaMode(IEEE802154CcaMode[this.stackConfig.CCA_MODE]);
700
- }
701
-
702
- // WARNING: From here on EZSP commands that affect memory allocation on the NCP should no longer be called (like resizing tables)
703
-
704
- await this.registerFixedEndpoints();
705
- this.clearNetworkCache();
706
-
707
- result = await this.initTrustCenter();
708
-
709
- // after network UP, as per SDK, ensures clean slate
710
- await this.initNCPConcentrator();
711
-
712
- // populate network cache info
713
- const [status, , parameters] = await this.ezsp.ezspGetNetworkParameters();
714
-
715
- if (status !== SLStatus.OK) {
716
- throw new Error(`Failed to get network parameters with status=${SLStatus[status]}.`);
717
- }
718
-
719
- if (this.adapterOptions.transmitPower != null && parameters.radioTxPower !== this.adapterOptions.transmitPower) {
720
- const status = await this.ezsp.ezspSetRadioPower(this.adapterOptions.transmitPower);
721
-
722
- if (status !== SLStatus.OK) {
723
- // soft-fail, don't prevent start
724
- logger.error(`Failed to set transmit power to ${this.adapterOptions.transmitPower} status=${SLStatus[status]}.`, NS);
725
- }
726
- }
727
-
728
- this.networkCache.parameters = parameters;
729
- this.networkCache.eui64 = await this.ezsp.ezspGetEui64();
730
-
731
- logger.debug(() => `[INIT] Network Ready! ${JSON.stringify(this.networkCache)}`, NS);
732
-
733
- this.watchdogCountersHandle = setInterval(this.watchdogCounters.bind(this), WATCHDOG_COUNTERS_FEED_INTERVAL);
734
-
735
- return result;
736
- }
737
-
738
- /**
739
- * NCP concentrator init. Also enables source route discovery mode with RESCHEDULE.
740
- *
741
- * From AN1233:
742
- * To function correctly in a Zigbee PRO network, a trust center also requires that:
743
- *
744
- * 1. The trust center application must act as a concentrator (either high or low RAM).
745
- * 2. The trust center application must have support for source routing.
746
- * It must record the source routes and properly handle requests by the stack for a particular source route.
747
- * 3. The trust center application must use an address cache for security, in order to maintain a mapping of IEEE address to short ID.
748
- *
749
- * Failure to satisfy all of the above requirements may result in failures when joining/rejoining devices to the network across multiple hops
750
- * (through a target node that is neither the trust center nor one of its neighboring routers.)
751
- */
752
- private async initNCPConcentrator(): Promise<void> {
753
- const status = await this.ezsp.ezspSetConcentrator(
754
- true,
755
- this.stackConfig.CONCENTRATOR_RAM_TYPE === "low" ? EMBER_LOW_RAM_CONCENTRATOR : EMBER_HIGH_RAM_CONCENTRATOR,
756
- this.stackConfig.CONCENTRATOR_MIN_TIME,
757
- this.stackConfig.CONCENTRATOR_MAX_TIME,
758
- this.stackConfig.CONCENTRATOR_ROUTE_ERROR_THRESHOLD,
759
- this.stackConfig.CONCENTRATOR_DELIVERY_FAILURE_THRESHOLD,
760
- this.stackConfig.CONCENTRATOR_MAX_HOPS,
761
- );
762
-
763
- if (status !== SLStatus.OK) {
764
- throw new Error(`[CONCENTRATOR] Failed to set concentrator with status=${SLStatus[status]}.`);
765
- }
766
-
767
- const remainTilMTORR = await this.ezsp.ezspSetSourceRouteDiscoveryMode(EmberSourceRouteDiscoveryMode.RESCHEDULE);
768
-
769
- logger.info(`[CONCENTRATOR] Started source route discovery. ${remainTilMTORR}ms until next broadcast.`, NS);
770
- }
771
-
772
- /**
773
- * Register fixed endpoints and set any related multicast entries that need to be.
774
- */
775
- private async registerFixedEndpoints(): Promise<void> {
776
- for (const ep of FIXED_ENDPOINTS) {
777
- const [epStatus] = await this.ezsp.ezspGetEndpointFlags(ep.endpoint);
778
-
779
- // endpoint not already registered
780
- if (epStatus !== SLStatus.OK) {
781
- // check to see if ezspAddEndpoint needs to be called
782
- // if ezspInit is called without NCP reset, ezspAddEndpoint is not necessary and will return an error
783
- const status = await this.ezsp.ezspAddEndpoint(
784
- ep.endpoint,
785
- ep.profileId,
786
- ep.deviceId,
787
- ep.deviceVersion,
788
- ep.inClusterList.slice(), // copy
789
- ep.outClusterList.slice(), // copy
790
- );
791
-
792
- if (status === SLStatus.OK) {
793
- logger.debug(`Registered endpoint '${ep.endpoint}'.`, NS);
794
- } else {
795
- throw new Error(`Failed to register endpoint '${ep.endpoint}' with status=${SLStatus[status]}.`);
796
- }
797
- } else {
798
- logger.debug(`Endpoint '${ep.endpoint}' already registered.`, NS);
799
- }
800
-
801
- for (const multicastId of ep.multicastIds) {
802
- const multicastEntry: EmberMulticastTableEntry = {
803
- multicastId,
804
- endpoint: ep.endpoint,
805
- networkIndex: ep.networkIndex,
806
- };
807
-
808
- const status = await this.ezsp.ezspSetMulticastTableEntry(this.multicastTable.length, multicastEntry);
809
-
810
- if (status !== SLStatus.OK) {
811
- throw new Error(`Failed to register group '${multicastId}' in multicast table with status=${SLStatus[status]}.`);
812
- }
813
-
814
- logger.debug(() => `Registered multicast table entry (${this.multicastTable.length}): ${JSON.stringify(multicastEntry)}.`, NS);
815
- this.multicastTable.push(multicastEntry.multicastId);
816
- }
817
- }
818
- }
819
-
820
- /**
821
- *
822
- * @returns True if the network needed to be formed.
823
- */
824
- private async initTrustCenter(): Promise<TsType.StartResult> {
825
- // init TC policies
826
- {
827
- let status = await this.emberSetEzspPolicy(EzspPolicyId.TC_KEY_REQUEST_POLICY, EzspDecisionId.ALLOW_TC_KEY_REQUESTS_AND_SEND_CURRENT_KEY);
828
-
829
- if (status !== SLStatus.OK) {
830
- throw new Error(
831
- `[INIT TC] Failed to set EzspPolicyId TC_KEY_REQUEST_POLICY to ALLOW_TC_KEY_REQUESTS_AND_SEND_CURRENT_KEY with status=${SLStatus[status]}.`,
832
- );
833
- }
834
-
835
- /* v8 ignore next */
836
- const appKeyRequestsPolicy = ALLOW_APP_KEY_REQUESTS ? EzspDecisionId.ALLOW_APP_KEY_REQUESTS : EzspDecisionId.DENY_APP_KEY_REQUESTS;
837
- status = await this.emberSetEzspPolicy(EzspPolicyId.APP_KEY_REQUEST_POLICY, appKeyRequestsPolicy);
838
-
839
- if (status !== SLStatus.OK) {
840
- throw new Error(
841
- `[INIT TC] Failed to set EzspPolicyId APP_KEY_REQUEST_POLICY to ${EzspDecisionId[appKeyRequestsPolicy]} with status=${SLStatus[status]}.`,
842
- );
843
- }
844
-
845
- status = await this.emberSetJoinPolicy(EmberJoinDecision.USE_PRECONFIGURED_KEY);
846
-
847
- if (status !== SLStatus.OK) {
848
- throw new Error(`[INIT TC] Failed to set join policy to USE_PRECONFIGURED_KEY with status=${SLStatus[status]}.`);
849
- }
850
- }
851
-
852
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
853
- const configNetworkKey = Buffer.from(this.networkOptions.networkKey!);
854
- const networkInitStruct: EmberNetworkInitStruct = {
855
- bitmask: EmberNetworkInitBitmask.PARENT_INFO_IN_TOKEN | EmberNetworkInitBitmask.END_DEVICE_REJOIN_ON_REBOOT,
856
- };
857
- const initStatus = await this.ezsp.ezspNetworkInit(networkInitStruct);
858
-
859
- logger.debug(`[INIT TC] Network init status=${SLStatus[initStatus]}.`, NS);
860
-
861
- if (initStatus !== SLStatus.OK && initStatus !== SLStatus.NOT_JOINED) {
862
- throw new Error(`[INIT TC] Failed network init request with status=${SLStatus[initStatus]}.`);
863
- }
864
-
865
- let action: NetworkInitAction = NetworkInitAction.DONE;
866
-
867
- if (initStatus === SLStatus.OK) {
868
- // network
869
- await this.oneWaitress.startWaitingForEvent(
870
- {eventName: OneWaitressEvents.STACK_STATUS_NETWORK_UP},
871
- DEFAULT_NETWORK_REQUEST_TIMEOUT,
872
- "[INIT TC] Network init",
873
- );
874
-
875
- const [npStatus, nodeType, netParams] = await this.ezsp.ezspGetNetworkParameters();
876
-
877
- logger.debug(() => `[INIT TC] Current adapter network: nodeType=${EmberNodeType[nodeType]} params=${JSON.stringify(netParams)}`, NS);
878
-
879
- if (
880
- npStatus === SLStatus.OK &&
881
- nodeType === EmberNodeType.COORDINATOR &&
882
- this.networkOptions.panID === netParams.panId &&
883
- equals(this.networkOptions.extendedPanID, netParams.extendedPanId)
884
- ) {
885
- // config matches adapter so far, no error, we can check the network key
886
- const context = initSecurityManagerContext();
887
- context.coreKeyType = SecManKeyType.NETWORK;
888
- context.keyIndex = 0;
889
- const [nkStatus, networkKey] = await this.ezsp.ezspExportKey(context);
890
-
891
- if (nkStatus !== SLStatus.OK) {
892
- throw new Error(`[INIT TC] Failed to export Network Key with status=${SLStatus[nkStatus]}.`);
893
- }
894
-
895
- // config doesn't match adapter anymore
896
- if (!networkKey.contents.equals(configNetworkKey)) {
897
- action = NetworkInitAction.LEAVE;
898
- }
899
- } else {
900
- // config doesn't match adapter
901
- action = NetworkInitAction.LEAVE;
902
- }
903
-
904
- if (action === NetworkInitAction.LEAVE) {
905
- logger.info("[INIT TC] Adapter network does not match config. Leaving network...", NS);
906
- const leaveStatus = await this.ezsp.ezspLeaveNetwork();
907
-
908
- if (leaveStatus !== SLStatus.OK) {
909
- throw new Error(`[INIT TC] Failed leave network request with status=${SLStatus[leaveStatus]}.`);
910
- }
911
-
912
- await this.oneWaitress.startWaitingForEvent(
913
- {eventName: OneWaitressEvents.STACK_STATUS_NETWORK_DOWN},
914
- DEFAULT_NETWORK_REQUEST_TIMEOUT,
915
- "[INIT TC] Leave network",
916
- );
917
-
918
- await wait(200); // settle down
919
-
920
- action = NetworkInitAction.LEFT;
921
- }
922
- }
923
-
924
- const backup = this.getStoredBackup();
925
-
926
- if (initStatus === SLStatus.NOT_JOINED || action === NetworkInitAction.LEFT) {
927
- // no network
928
- if (backup !== undefined) {
929
- if (
930
- this.networkOptions.panID === backup.networkOptions.panId &&
931
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
932
- Buffer.from(this.networkOptions.extendedPanID!).equals(backup.networkOptions.extendedPanId) &&
933
- this.networkOptions.channelList.includes(backup.logicalChannel) &&
934
- configNetworkKey.equals(backup.networkOptions.networkKey)
935
- ) {
936
- // config matches backup
937
- action = NetworkInitAction.FORM_BACKUP;
938
- } else {
939
- // config doesn't match backup
940
- logger.info("[INIT TC] Config does not match backup.", NS);
941
- action = NetworkInitAction.FORM_CONFIG;
942
- }
943
- } else {
944
- // no backup
945
- logger.info("[INIT TC] No valid backup found.", NS);
946
- action = NetworkInitAction.FORM_CONFIG;
947
- }
948
- }
949
-
950
- //---- from here on, we assume everything is in place for whatever decision was taken above
951
-
952
- let result: TsType.StartResult = "resumed";
953
-
954
- switch (action) {
955
- case NetworkInitAction.FORM_BACKUP: {
956
- logger.info("[INIT TC] Forming from backup.", NS);
957
- // `backup` valid in this `action` path (not detected by TS)
958
- /* v8 ignore start */
959
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
960
- const keyList: LinkKeyBackupData[] = backup!.devices.map((device) => ({
961
- deviceEui64: ZSpec.Utils.eui64BEBufferToHex(device.ieeeAddress),
962
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
963
- key: {contents: device.linkKey!.key},
964
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
965
- outgoingFrameCounter: device.linkKey!.txCounter,
966
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
967
- incomingFrameCounter: device.linkKey!.rxCounter,
968
- }));
969
- /* v8 ignore stop */
970
-
971
- // before forming
972
- await this.importLinkKeys(keyList);
973
-
974
- await this.formNetwork(
975
- true /*from backup*/,
976
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
977
- backup!.networkOptions.networkKey,
978
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
979
- backup!.networkKeyInfo.sequenceNumber,
980
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
981
- backup!.networkKeyInfo.frameCounter,
982
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
983
- backup!.networkOptions.panId,
984
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
985
- Array.from(backup!.networkOptions.extendedPanId),
986
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
987
- backup!.logicalChannel,
988
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
989
- backup!.ezsp!.hashed_tclk!, // valid from getStoredBackup
990
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
991
- backup!.networkUpdateId,
992
- );
993
-
994
- result = "restored";
995
- break;
996
- }
997
- case NetworkInitAction.FORM_CONFIG: {
998
- logger.info("[INIT TC] Forming from config.", NS);
999
- await this.formNetwork(
1000
- false /*from config*/,
1001
- configNetworkKey,
1002
- 0,
1003
- 0,
1004
- this.networkOptions.panID,
1005
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
1006
- this.networkOptions.extendedPanID!,
1007
- this.networkOptions.channelList[0],
1008
- randomBytes(EMBER_ENCRYPTION_KEY_SIZE), // rnd TC link key
1009
- 0,
1010
- );
1011
-
1012
- result = "reset";
1013
- break;
1014
- }
1015
- case NetworkInitAction.DONE: {
1016
- logger.info("[INIT TC] Adapter network matches config.", NS);
1017
- break;
1018
- }
1019
- }
1020
-
1021
- // can't let frame counter wrap to zero (uint32_t), will force a broadcast after init if getting too close
1022
- if (backup != null && backup.networkKeyInfo.frameCounter > 0xfeeeeeee) {
1023
- // XXX: while this remains a pretty low occurrence in most (small) networks,
1024
- // currently Z2M won't support the key update because of one-way config...
1025
- // need to investigate handling this properly
1026
-
1027
- // logger.warning(`[INIT TC] Network key frame counter is reaching its limit. Scheduling broadcast to update network key. `
1028
- // + `This may result in some devices (especially battery-powered) temporarily losing connection.`, NS);
1029
- // // XXX: no idea here on the proper timer value, but this will block the network for several seconds on exec
1030
- // // (probably have to take the behavior of sleepy-end devices into account to improve chances of reaching everyone right away?)
1031
- // setTimeout(async () => {
1032
- // this.requestQueue.enqueue(async (): Promise<SLStatus> => {
1033
- // await this.broadcastNetworkKeyUpdate();
1034
-
1035
- // return SLStatus.OK;
1036
- // }, logger.error, true);// no reject just log error if any, will retry next start, & prioritize so we know it'll run when expected
1037
- // }, 300000);
1038
- logger.warning("[INIT TC] Network key frame counter is reaching its limit. A new network key will have to be instaured soon.", NS);
1039
- }
1040
-
1041
- return result;
1042
- }
1043
-
1044
- /**
1045
- * Form a network using given parameters.
1046
- */
1047
- private async formNetwork(
1048
- fromBackup: boolean,
1049
- networkKey: Buffer,
1050
- networkKeySequenceNumber: number,
1051
- networkKeyFrameCounter: number,
1052
- panId: PanId,
1053
- extendedPanId: ExtendedPanId,
1054
- radioChannel: number,
1055
- tcLinkKey: Buffer,
1056
- nwkUpdateId: number,
1057
- ): Promise<void> {
1058
- const state: EmberInitialSecurityState = {
1059
- bitmask:
1060
- EmberInitialSecurityBitmask.TRUST_CENTER_GLOBAL_LINK_KEY |
1061
- EmberInitialSecurityBitmask.HAVE_PRECONFIGURED_KEY |
1062
- EmberInitialSecurityBitmask.HAVE_NETWORK_KEY |
1063
- EmberInitialSecurityBitmask.TRUST_CENTER_USES_HASHED_LINK_KEY |
1064
- EmberInitialSecurityBitmask.REQUIRE_ENCRYPTED_KEY,
1065
- preconfiguredKey: {contents: tcLinkKey},
1066
- networkKey: {contents: networkKey},
1067
- networkKeySequenceNumber: networkKeySequenceNumber,
1068
- preconfiguredTrustCenterEui64: ZSpec.BLANK_EUI64,
1069
- };
1070
-
1071
- if (fromBackup) {
1072
- state.bitmask |= EmberInitialSecurityBitmask.NO_FRAME_COUNTER_RESET;
1073
-
1074
- const status = await this.ezsp.ezspSetNWKFrameCounter(networkKeyFrameCounter);
1075
-
1076
- if (status !== SLStatus.OK) {
1077
- throw new Error(`[INIT FORM] Failed to set NWK frame counter with status=${SLStatus[status]}.`);
1078
- }
1079
-
1080
- // status = await this.ezsp.ezspSetAPSFrameCounter(tcLinkKeyFrameCounter);
1081
-
1082
- // if (status !== SLStatus.OK) {
1083
- // throw new Error(`[INIT FORM] Failed to set TC APS frame counter with status=${SLStatus[status]}.`);
1084
- // }
1085
- }
1086
-
1087
- let status = await this.ezsp.ezspSetInitialSecurityState(state);
1088
-
1089
- if (status !== SLStatus.OK) {
1090
- throw new Error(`[INIT FORM] Failed to set initial security state with status=${SLStatus[status]}.`);
1091
- }
1092
-
1093
- const extended: EmberExtendedSecurityBitmask =
1094
- EmberExtendedSecurityBitmask.JOINER_GLOBAL_LINK_KEY | EmberExtendedSecurityBitmask.NWK_LEAVE_REQUEST_NOT_ALLOWED;
1095
- status = await this.ezsp.ezspSetExtendedSecurityBitmask(extended);
1096
-
1097
- if (status !== SLStatus.OK) {
1098
- throw new Error(`[INIT FORM] Failed to set extended security bitmask to ${extended} with status=${SLStatus[status]}.`);
1099
- }
1100
-
1101
- if (!fromBackup) {
1102
- status = await this.ezsp.ezspClearKeyTable();
1103
-
1104
- if (status !== SLStatus.OK) {
1105
- logger.error(`[INIT FORM] Failed to clear key table with status=${SLStatus[status]}.`, NS);
1106
- }
1107
- }
1108
-
1109
- const netParams: EmberNetworkParameters = {
1110
- panId,
1111
- extendedPanId,
1112
- radioTxPower: this.adapterOptions.transmitPower || 5,
1113
- radioChannel,
1114
- joinMethod: EmberJoinMethod.MAC_ASSOCIATION,
1115
- nwkManagerId: ZSpec.COORDINATOR_ADDRESS,
1116
- nwkUpdateId,
1117
- channels: ZSpec.ALL_802_15_4_CHANNELS_MASK,
1118
- };
1119
-
1120
- logger.info(() => `[INIT FORM] Forming new network with: ${JSON.stringify(netParams)}`, NS);
1121
-
1122
- status = await this.ezsp.ezspFormNetwork(netParams);
1123
-
1124
- if (status !== SLStatus.OK) {
1125
- throw new Error(`[INIT FORM] Failed form network request with status=${SLStatus[status]}.`);
1126
- }
1127
-
1128
- await this.oneWaitress.startWaitingForEvent(
1129
- {eventName: OneWaitressEvents.STACK_STATUS_NETWORK_UP},
1130
- DEFAULT_NETWORK_REQUEST_TIMEOUT,
1131
- "[INIT FORM] Form network",
1132
- );
1133
-
1134
- status = await this.ezsp.ezspStartWritingStackTokens();
1135
-
1136
- logger.debug(`[INIT FORM] Start writing stack tokens status=${SLStatus[status]}.`, NS);
1137
- logger.info("[INIT FORM] New network formed!", NS);
1138
- }
1139
-
1140
- /**
1141
- * Loads currently stored backup and returns it in internal backup model.
1142
- */
1143
- private getStoredBackup(): Backup | undefined {
1144
- if (!existsSync(this.backupPath)) {
1145
- return undefined;
1146
- }
1147
-
1148
- let data: UnifiedBackupStorage;
1149
-
1150
- try {
1151
- data = JSON.parse(readFileSync(this.backupPath).toString());
1152
- } catch (error) {
1153
- throw new Error(`[BACKUP] Coordinator backup is corrupted. (${(error as Error).stack})`);
1154
- }
1155
-
1156
- if (data.metadata?.format === "zigpy/open-coordinator-backup" && data.metadata?.version) {
1157
- if (data.metadata?.version !== 1) {
1158
- throw new Error(`[BACKUP] Unsupported open coordinator backup version (version=${data.metadata?.version}).`);
1159
- }
1160
-
1161
- if (!data.stack_specific?.ezsp || !data.metadata.internal.ezspVersion) {
1162
- throw new Error("[BACKUP] Current backup file is not for EmberZNet stack.");
1163
- }
1164
-
1165
- if (data.metadata.internal.ezspVersion < BACKUP_OLDEST_SUPPORTED_EZSP_VERSION) {
1166
- renameSync(this.backupPath, `${this.backupPath}.old`);
1167
- logger.warning("[BACKUP] Current backup file is from an unsupported EZSP version. Renaming and ignoring.", NS);
1168
- return undefined;
1169
- }
1170
-
1171
- return BackupUtils.fromUnifiedBackup(data);
1172
- }
1173
-
1174
- throw new Error("[BACKUP] Unknown backup format.");
1175
- }
1176
-
1177
- /**
1178
- * Export link keys for backup.
1179
- *
1180
- * @return List of keys data with AES hashed keys
1181
- */
1182
- public async exportLinkKeys(): Promise<LinkKeyBackupData[]> {
1183
- const [confStatus, keyTableSize] = await this.ezsp.ezspGetConfigurationValue(EzspConfigId.KEY_TABLE_SIZE);
1184
-
1185
- if (confStatus !== SLStatus.OK) {
1186
- throw new Error(`[BACKUP] Failed to retrieve key table size from NCP with status=${SLStatus[confStatus]}.`);
1187
- }
1188
-
1189
- let context: SecManContext;
1190
- let plaintextKey: SecManKey;
1191
- let apsKeyMeta: SecManAPSKeyMetadata;
1192
- let status: SLStatus;
1193
- const keyList: LinkKeyBackupData[] = [];
1194
-
1195
- for (let i = 0; i < keyTableSize; i++) {
1196
- [status, context, plaintextKey, apsKeyMeta] = await this.ezsp.ezspExportLinkKeyByIndex(i);
1197
- logger.debug(`[BACKUP] Export link key at index ${i}, status=${SLStatus[status]}.`, NS);
1198
-
1199
- // only include key if we could retrieve one at index and hash it properly
1200
- if (status === SLStatus.OK) {
1201
- // Rather than give the real link key, the backup contains a hashed version of the key.
1202
- // This is done to prevent a compromise of the backup data from compromising the current link keys.
1203
- // This is per the Smart Energy spec.
1204
- const hashedKey = ZSpec.Utils.aes128MmoHash(plaintextKey.contents);
1205
-
1206
- keyList.push({
1207
- deviceEui64: context.eui64,
1208
- key: {contents: hashedKey},
1209
- outgoingFrameCounter: apsKeyMeta.outgoingFrameCounter,
1210
- incomingFrameCounter: apsKeyMeta.incomingFrameCounter,
1211
- });
1212
- }
1213
- }
1214
-
1215
- logger.info(`[BACKUP] Retrieved ${keyList.length} link keys.`, NS);
1216
-
1217
- return keyList;
1218
- }
1219
-
1220
- /**
1221
- * Import link keys from backup.
1222
- *
1223
- * @param backupData
1224
- */
1225
- public async importLinkKeys(backupData: LinkKeyBackupData[]): Promise<void> {
1226
- if (!backupData?.length) {
1227
- return;
1228
- }
1229
-
1230
- const [confStatus, keyTableSize] = await this.ezsp.ezspGetConfigurationValue(EzspConfigId.KEY_TABLE_SIZE);
1231
-
1232
- if (confStatus !== SLStatus.OK) {
1233
- throw new Error(`[BACKUP] Failed to retrieve key table size from NCP with status=${SLStatus[confStatus]}.`);
1234
- }
1235
-
1236
- if (backupData.length > keyTableSize) {
1237
- throw new Error(`[BACKUP] Current key table of ${keyTableSize} is too small to import backup of ${backupData.length}!`);
1238
- }
1239
-
1240
- const networkStatus = await this.ezsp.ezspNetworkState();
1241
-
1242
- if (networkStatus !== EmberNetworkStatus.NO_NETWORK) {
1243
- throw new Error(`[BACKUP] Cannot import TC data while network is up, networkStatus=${EmberNetworkStatus[networkStatus]}.`);
1244
- }
1245
-
1246
- let status: SLStatus;
1247
-
1248
- for (let i = 0; i < keyTableSize; i++) {
1249
- // erase any key index not present in backup but available on the NCP
1250
- status =
1251
- i >= backupData.length
1252
- ? await this.ezsp.ezspEraseKeyTableEntry(i)
1253
- : await this.ezsp.ezspImportLinkKey(i, backupData[i].deviceEui64, backupData[i].key);
1254
-
1255
- if (status !== SLStatus.OK) {
1256
- throw new Error(
1257
- `[BACKUP] Failed to ${i >= backupData.length ? "erase" : "set"} key table entry at index ${i} with status=${SLStatus[status]}.`,
1258
- );
1259
- }
1260
- }
1261
-
1262
- logger.info(`[BACKUP] Imported ${backupData.length} keys.`, NS);
1263
- }
1264
-
1265
- /**
1266
- * Routine to update the network key and broadcast the update to the network after a set time.
1267
- * NOTE: This should run at a large interval, but before the uint32_t of the frame counter is able to reach all Fs (can't wrap to 0).
1268
- * This may disrupt sleepy end devices that miss the update, but they should be able to TC rejoin (in most cases...).
1269
- * On the other hand, the more often this runs, the more secure the network is...
1270
- */
1271
- public async broadcastNetworkKeyUpdate(): Promise<void> {
1272
- return await this.queue.execute<void>(async () => {
1273
- logger.warning("[TRUST CENTER] Performing a network key update. This might take a while and disrupt normal operation.", NS);
1274
-
1275
- // zero-filled = let stack generate new random network key
1276
- let status = await this.ezsp.ezspBroadcastNextNetworkKey({contents: Buffer.alloc(EMBER_ENCRYPTION_KEY_SIZE)});
1277
-
1278
- if (status !== SLStatus.OK) {
1279
- throw new Error(`[TRUST CENTER] Failed to broadcast next network key with status=${SLStatus[status]}.`);
1280
- }
1281
-
1282
- // XXX: this will block other requests for a while, but should ensure the key propagates without interference?
1283
- // could also stop dispatching entirely and do this outside the queue if necessary/better
1284
- await wait(BROADCAST_NETWORK_KEY_SWITCH_WAIT_TIME);
1285
-
1286
- status = await this.ezsp.ezspBroadcastNetworkKeySwitch();
1287
-
1288
- if (status !== SLStatus.OK) {
1289
- // XXX: Not sure how likely this is, but this is bad, probably should hard fail?
1290
- throw new Error(`[TRUST CENTER] Failed to broadcast network key switch with status=${SLStatus[status]}.`);
1291
- }
1292
- });
1293
- }
1294
-
1295
- /**
1296
- * Received when EZSP layer alerts of a problem that needs the NCP to be reset.
1297
- * @param status
1298
- */
1299
- private onNcpNeedsResetAndInit(status: EzspStatus): void {
1300
- logger.error(`Adapter fatal error: ${EzspStatus[status]}`, NS);
1301
- this.emit("disconnected");
1302
- }
1303
-
1304
- //---- START Events
1305
-
1306
- //---- END Events
1307
-
1308
- //---- START Cache-enabled EZSP wrappers
1309
-
1310
- /**
1311
- * Clear the cached network values (set to invalid values).
1312
- */
1313
- public clearNetworkCache(): void {
1314
- this.networkCache = initNetworkCache();
1315
- }
1316
-
1317
- /**
1318
- * Return the EUI 64 of the local node
1319
- * This call caches the results on the host to prevent frequent EZSP transactions.
1320
- * Check against BLANK_EUI64 for validity.
1321
- */
1322
- public async emberGetEui64(): Promise<Eui64> {
1323
- if (this.networkCache.eui64 === ZSpec.BLANK_EUI64) {
1324
- this.networkCache.eui64 = await this.ezsp.ezspGetEui64();
1325
- }
1326
-
1327
- return this.networkCache.eui64;
1328
- }
1329
-
1330
- /**
1331
- * Return the PAN ID of the local node.
1332
- * This call caches the results on the host to prevent frequent EZSP transactions.
1333
- * Check against INVALID_PAN_ID for validity.
1334
- */
1335
- public async emberGetPanId(): Promise<PanId> {
1336
- if (this.networkCache.parameters.panId === ZSpec.INVALID_PAN_ID) {
1337
- const [status, , parameters] = await this.ezsp.ezspGetNetworkParameters();
1338
-
1339
- if (status === SLStatus.OK) {
1340
- this.networkCache.parameters = parameters;
1341
- } else {
1342
- throw new Error(`Failed to get PAN ID (via network parameters) with status=${SLStatus[status]}.`);
1343
- }
1344
- }
1345
-
1346
- return this.networkCache.parameters.panId;
1347
- }
1348
-
1349
- /**
1350
- * Return the Extended PAN ID of the local node.
1351
- * This call caches the results on the host to prevent frequent EZSP transactions.
1352
- * Check against BLANK_EXTENDED_PAN_ID for validity.
1353
- */
1354
- public async emberGetExtendedPanId(): Promise<ExtendedPanId> {
1355
- if (equals(this.networkCache.parameters.extendedPanId, ZSpec.BLANK_EXTENDED_PAN_ID)) {
1356
- const [status, , parameters] = await this.ezsp.ezspGetNetworkParameters();
1357
-
1358
- if (status === SLStatus.OK) {
1359
- this.networkCache.parameters = parameters;
1360
- } else {
1361
- throw new Error(`Failed to get Extended PAN ID (via network parameters) with status=${SLStatus[status]}.`);
1362
- }
1363
- }
1364
-
1365
- return this.networkCache.parameters.extendedPanId;
1366
- }
1367
-
1368
- /**
1369
- * Return the radio channel (uint8_t) of the current network.
1370
- * This call caches the results on the host to prevent frequent EZSP transactions.
1371
- * Check against INVALID_RADIO_CHANNEL for validity.
1372
- */
1373
- public async emberGetRadioChannel(): Promise<number> {
1374
- if (this.networkCache.parameters.radioChannel === INVALID_RADIO_CHANNEL) {
1375
- const [status, , parameters] = await this.ezsp.ezspGetNetworkParameters();
1376
-
1377
- if (status === SLStatus.OK) {
1378
- this.networkCache.parameters = parameters;
1379
- } else {
1380
- throw new Error(`Failed to get radio channel (via network parameters) with status=${SLStatus[status]}.`);
1381
- }
1382
- }
1383
-
1384
- return this.networkCache.parameters.radioChannel;
1385
- }
1386
-
1387
- //---- END Cache-enabled EZSP wrappers
1388
-
1389
- //---- START EZSP wrappers
1390
-
1391
- /**
1392
- * Ensure the Host & NCP are aligned on protocols using version.
1393
- * Cache the retrieved information.
1394
- *
1395
- * NOTE: currently throws on mismatch until support for lower versions is implemented (not planned atm)
1396
- *
1397
- * Does nothing if ncpNeedsResetAndInit == true.
1398
- */
1399
- private async emberVersion(): Promise<void> {
1400
- // send the Host version number to the NCP.
1401
- // The NCP returns the EZSP version that the NCP is running along with the stackType and stackVersion
1402
- let [ncpEzspProtocolVer, ncpStackType, ncpStackVer] = await this.ezsp.ezspVersion(EZSP_PROTOCOL_VERSION);
1403
-
1404
- // verify that the stack type is what is expected
1405
- if (ncpStackType !== EZSP_STACK_TYPE_MESH) {
1406
- throw new Error(`Stack type ${ncpStackType} is not expected!`);
1407
- }
1408
-
1409
- if (ncpEzspProtocolVer === EZSP_PROTOCOL_VERSION) {
1410
- logger.debug(`Adapter EZSP protocol version (${ncpEzspProtocolVer}) matches Host.`, NS);
1411
- } else if (ncpEzspProtocolVer < EZSP_PROTOCOL_VERSION && ncpEzspProtocolVer >= EZSP_MIN_PROTOCOL_VERSION) {
1412
- [ncpEzspProtocolVer, ncpStackType, ncpStackVer] = await this.ezsp.ezspVersion(ncpEzspProtocolVer);
1413
-
1414
- logger.info(`Adapter EZSP protocol version (${ncpEzspProtocolVer}) lower than Host. Switched.`, NS);
1415
- } else {
1416
- throw new Error(
1417
- `Adapter EZSP protocol version (${ncpEzspProtocolVer}) is not supported by Host [${EZSP_MIN_PROTOCOL_VERSION}-${EZSP_PROTOCOL_VERSION}].`,
1418
- );
1419
- }
1420
-
1421
- this.ezsp.setProtocolVersion(ncpEzspProtocolVer);
1422
- logger.debug(`Adapter info: EZSPVersion=${ncpEzspProtocolVer} StackType=${ncpStackType} StackVersion=${ncpStackVer}`, NS);
1423
-
1424
- const [status, versionStruct] = await this.ezsp.ezspGetVersionStruct();
1425
-
1426
- if (status !== SLStatus.OK) {
1427
- // Should never happen with support of only EZSP v13+
1428
- throw new Error("NCP has old-style version number. Not supported.");
1429
- }
1430
-
1431
- this.version = {
1432
- ezsp: ncpEzspProtocolVer,
1433
- revision: `${versionStruct.major}.${versionStruct.minor}.${versionStruct.patch} [${EmberVersionType[versionStruct.type]}]`,
1434
- ...versionStruct,
1435
- };
1436
-
1437
- if (versionStruct.type !== EmberVersionType.GA) {
1438
- logger.warning(`Adapter is running a non-GA version (${EmberVersionType[versionStruct.type]}).`, NS);
1439
- }
1440
-
1441
- logger.info(() => `Adapter version info: ${JSON.stringify(this.version)}`, NS);
1442
- }
1443
-
1444
- /**
1445
- * This function sets an EZSP config value.
1446
- * WARNING: Do not call for values that cannot be set after init without first resetting NCP (like table sizes).
1447
- * To avoid an extra NCP call, this does not check for it.
1448
- * @param configId
1449
- * @param value uint16_t
1450
- * @returns
1451
- */
1452
- private async emberSetEzspConfigValue(configId: EzspConfigId, value: number): Promise<SLStatus> {
1453
- const status = await this.ezsp.ezspSetConfigurationValue(configId, value);
1454
-
1455
- logger.debug(`[EzspConfigId] SET '${EzspConfigId[configId]}' TO '${value}' with status=${SLStatus[status]}.`, NS);
1456
-
1457
- if (status !== SLStatus.OK) {
1458
- logger.info(
1459
- `[EzspConfigId] Failed to SET '${EzspConfigId[configId]}' TO '${value}' with status=${SLStatus[status]}. Firmware value will be used instead.`,
1460
- NS,
1461
- );
1462
- }
1463
-
1464
- return status;
1465
- }
1466
-
1467
- /**
1468
- * This function sets an EZSP value.
1469
- * @param valueId
1470
- * @param valueLength uint8_t
1471
- * @param value uint8_t *
1472
- * @returns
1473
- */
1474
- private async emberSetEzspValue(valueId: EzspValueId, valueLength: number, value: number[]): Promise<SLStatus> {
1475
- const status = await this.ezsp.ezspSetValue(valueId, valueLength, value);
1476
-
1477
- logger.debug(`[EzspValueId] SET '${EzspValueId[valueId]}' TO '${value}' with status=${SLStatus[status]}.`, NS);
1478
-
1479
- return status;
1480
- }
1481
-
1482
- /**
1483
- * This function sets an EZSP policy.
1484
- * @param policyId
1485
- * @param decisionId Can be bitop
1486
- * @returns
1487
- */
1488
- private async emberSetEzspPolicy(policyId: EzspPolicyId, decisionId: number): Promise<SLStatus> {
1489
- const status = await this.ezsp.ezspSetPolicy(policyId, decisionId);
1490
-
1491
- logger.debug(`[EzspPolicyId] SET '${EzspPolicyId[policyId]}' TO '${decisionId}' with status=${SLStatus[status]}.`, NS);
1492
-
1493
- return status;
1494
- }
1495
-
1496
- /**
1497
- * Set the trust center policy bitmask using decision.
1498
- * @param decision
1499
- * @returns
1500
- */
1501
- private async emberSetJoinPolicy(decision: EmberJoinDecision): Promise<SLStatus> {
1502
- let policy: number = EzspDecisionBitmask.DEFAULT_CONFIGURATION;
1503
-
1504
- switch (decision) {
1505
- case EmberJoinDecision.USE_PRECONFIGURED_KEY: {
1506
- policy = EzspDecisionBitmask.ALLOW_JOINS | EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS;
1507
- break;
1508
- }
1509
- case EmberJoinDecision.ALLOW_REJOINS_ONLY: {
1510
- policy = EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS;
1511
- break;
1512
- }
1513
- /*case EmberJoinDecision.SEND_KEY_IN_THE_CLEAR: {
1514
- policy = EzspDecisionBitmask.ALLOW_JOINS | EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS | EzspDecisionBitmask.SEND_KEY_IN_CLEAR;
1515
- break;
1516
- }*/
1517
- }
1518
-
1519
- return await this.emberSetEzspPolicy(EzspPolicyId.TRUST_CENTER_POLICY, policy);
1520
- }
1521
-
1522
- //---- END EZSP wrappers
1523
-
1524
- //---- START Ember ZDO
1525
-
1526
- /**
1527
- * ZDO
1528
- * Get the next device request sequence number.
1529
- *
1530
- * Requests have sequence numbers so that they can be matched up with the
1531
- * responses. To avoid complexities, the library uses numbers with the high
1532
- * bit clear and the stack uses numbers with the high bit set.
1533
- *
1534
- * @return uint8_t The next device request sequence number
1535
- */
1536
- private nextZDORequestSequence(): number {
1537
- this.zdoRequestSequence = ++this.zdoRequestSequence & APPLICATION_ZDO_SEQUENCE_MASK;
1538
- return this.zdoRequestSequence;
1539
- }
1540
-
1541
- //---- END Ember ZDO
1542
-
1543
- //-- START Adapter implementation
1544
-
1545
- public async start(): Promise<TsType.StartResult> {
1546
- logger.info("======== Ember Adapter Starting ========", NS);
1547
- const result = await this.initEzsp();
1548
-
1549
- return result;
1550
- }
1551
-
1552
- public async stop(): Promise<void> {
1553
- clearInterval(this.watchdogCountersHandle);
1554
- await this.ezsp.stop();
1555
- this.ezsp.removeAllListeners();
1556
-
1557
- logger.info("======== Ember Adapter Stopped ========", NS);
1558
- }
1559
-
1560
- public async getCoordinatorIEEE(): Promise<string> {
1561
- return await this.queue.execute(async () => {
1562
- this.checkInterpanLock();
1563
-
1564
- // in all likelihood this will be retrieved from cache
1565
- return await this.emberGetEui64();
1566
- });
1567
- }
1568
-
1569
- public async getCoordinatorVersion(): Promise<TsType.CoordinatorVersion> {
1570
- return await Promise.resolve({type: "EmberZNet", meta: this.version});
1571
- }
1572
-
1573
- // queued
1574
- public async reset(type: "soft" | "hard"): Promise<void> {
1575
- // NOTE: although this function is legacy atm, a couple of new untested EZSP functions that could also prove useful:
1576
- // this.ezsp.ezspTokenFactoryReset(true/*excludeOutgoingFC*/, true/*excludeBootCounter*/);
1577
- // this.ezsp.ezspResetNode()
1578
- /* v8 ignore next */ // weird coverage bug
1579
- await Promise.reject(new Error(`Not supported '${type}'.`));
1580
- }
1581
-
1582
- public async supportsBackup(): Promise<boolean> {
1583
- return await Promise.resolve(true);
1584
- }
1585
-
1586
- // queued
1587
- public async backup(_ieeeAddressesInDatabase: string[]): Promise<Backup> {
1588
- return await this.queue.execute<Backup>(async () => {
1589
- // grab fresh version here, bypass cache
1590
- const [netStatus, , netParams] = await this.ezsp.ezspGetNetworkParameters();
1591
-
1592
- if (netStatus !== SLStatus.OK) {
1593
- throw new Error(`[BACKUP] Failed to get network parameters with status=${SLStatus[netStatus]}.`);
1594
- }
1595
-
1596
- // update cache
1597
- this.networkCache.parameters = netParams;
1598
- this.networkCache.eui64 = await this.ezsp.ezspGetEui64();
1599
-
1600
- const [netKeyStatus, netKeyInfo] = await this.ezsp.ezspGetNetworkKeyInfo();
1601
-
1602
- if (netKeyStatus !== SLStatus.OK) {
1603
- throw new Error(`[BACKUP] Failed to get network keys info with status=${SLStatus[netKeyStatus]}.`);
1604
- }
1605
-
1606
- if (!netKeyInfo.networkKeySet) {
1607
- throw new Error("[BACKUP] No network key set.");
1608
- }
1609
-
1610
- /* v8 ignore next */
1611
- const keyList: LinkKeyBackupData[] = ALLOW_APP_KEY_REQUESTS ? await this.exportLinkKeys() : [];
1612
-
1613
- let context: SecManContext = initSecurityManagerContext();
1614
- context.coreKeyType = SecManKeyType.TC_LINK;
1615
- const [tclkStatus, tcLinkKey] = await this.ezsp.ezspExportKey(context);
1616
-
1617
- if (tclkStatus !== SLStatus.OK) {
1618
- throw new Error(`[BACKUP] Failed to export TC Link Key with status=${SLStatus[tclkStatus]}.`);
1619
- }
1620
-
1621
- // const [tcKeyStatus, tcKeyInfo] = await this.ezsp.ezspGetApsKeyInfo(context);
1622
-
1623
- // if (tcKeyStatus !== SLStatus.OK) {
1624
- // throw new Error(`[BACKUP] Failed to get TC APS key info with status=${SLStatus[tcKeyStatus]}.`);
1625
- // }
1626
-
1627
- context = initSecurityManagerContext(); // make sure it's back to zeroes
1628
- context.coreKeyType = SecManKeyType.NETWORK;
1629
- context.keyIndex = 0;
1630
- const [nkStatus, networkKey] = await this.ezsp.ezspExportKey(context);
1631
-
1632
- if (nkStatus !== SLStatus.OK) {
1633
- throw new Error(`[BACKUP] Failed to export Network Key with status=${SLStatus[nkStatus]}.`);
1634
- }
1635
-
1636
- return {
1637
- networkOptions: {
1638
- panId: netParams.panId, // uint16_t
1639
- extendedPanId: Buffer.from(netParams.extendedPanId),
1640
- channelList: ZSpec.Utils.uint32MaskToChannels(netParams.channels),
1641
- networkKey: networkKey.contents,
1642
- networkKeyDistribute: false,
1643
- },
1644
- logicalChannel: netParams.radioChannel,
1645
- networkKeyInfo: {
1646
- sequenceNumber: netKeyInfo.networkKeySequenceNumber,
1647
- frameCounter: netKeyInfo.networkKeyFrameCounter,
1648
- },
1649
- // tcLinkKeyInfo: {
1650
- // incomingFrameCounter: tcKeyInfo.bitmask & EmberKeyStructBitmask.HAS_INCOMING_FRAME_COUNTER ? tcKeyInfo.incomingFrameCounter : 0,
1651
- // outgoingFrameCounter: tcKeyInfo.bitmask & EmberKeyStructBitmask.HAS_OUTGOING_FRAME_COUNTER ? tcKeyInfo.outgoingFrameCounter : 0,
1652
- // },
1653
- securityLevel: SECURITY_LEVEL_Z3,
1654
- networkUpdateId: netParams.nwkUpdateId,
1655
- coordinatorIeeeAddress: Buffer.from(this.networkCache.eui64.substring(2) /*take out 0x*/, "hex").reverse(),
1656
- devices: keyList.map(
1657
- /* v8 ignore start */
1658
- (key) => ({
1659
- networkAddress: null, // not used for restore, no reason to make NCP calls for nothing
1660
- ieeeAddress: Buffer.from(key.deviceEui64.substring(2) /*take out 0x*/, "hex").reverse(),
1661
- isDirectChild: false, // not used
1662
- linkKey: {
1663
- key: key.key.contents,
1664
- rxCounter: key.incomingFrameCounter,
1665
- txCounter: key.outgoingFrameCounter,
1666
- },
1667
- }),
1668
- /* v8 ignore stop */
1669
- ),
1670
- ezsp: {
1671
- version: this.version.ezsp,
1672
- hashed_tclk: tcLinkKey.contents,
1673
- // tokens: tokensBuf.toString('hex'),
1674
- // altNetworkKey: altNetworkKey.contents,
1675
- },
1676
- };
1677
- });
1678
- }
1679
-
1680
- // queued, non-InterPAN
1681
- public async getNetworkParameters(): Promise<TsType.NetworkParameters> {
1682
- return await this.queue.execute<TsType.NetworkParameters>(async () => {
1683
- this.checkInterpanLock();
1684
-
1685
- // first call will cache for the others, but in all likelihood, it will all be from freshly cached after init
1686
- // since Controller caches this also.
1687
- const channel = await this.emberGetRadioChannel();
1688
- const panID = await this.emberGetPanId();
1689
- const extendedPanID = await this.emberGetExtendedPanId();
1690
-
1691
- return {
1692
- panID,
1693
- extendedPanID: ZSpec.Utils.eui64LEBufferToHex(Buffer.from(extendedPanID)),
1694
- channel,
1695
- nwkUpdateID: this.networkCache.parameters.nwkUpdateId,
1696
- };
1697
- });
1698
- }
1699
-
1700
- // queued
1701
- public async addInstallCode(ieeeAddress: string, key: Buffer, hashed: boolean): Promise<void> {
1702
- return await this.queue.execute<void>(async () => {
1703
- // Add the key to the transient key table.
1704
- // This will be used while the DUT joins.
1705
- const impStatus = await this.ezsp.ezspImportTransientKey(ieeeAddress as Eui64, {contents: hashed ? key : ZSpec.Utils.aes128MmoHash(key)});
1706
-
1707
- if (impStatus === SLStatus.OK) {
1708
- logger.debug(`[ADD INSTALL CODE] Success for '${ieeeAddress}'.`, NS);
1709
- } else {
1710
- throw new Error(`[ADD INSTALL CODE] Failed for '${ieeeAddress}' with status=${SLStatus[impStatus]}.`);
1711
- }
1712
- });
1713
- }
1714
-
1715
- /** WARNING: Adapter impl. Starts timer immediately upon returning */
1716
- public waitFor(
1717
- networkAddress: number | undefined,
1718
- endpoint: number,
1719
- _frameType: Zcl.FrameType,
1720
- _direction: Zcl.Direction,
1721
- transactionSequenceNumber: number | undefined,
1722
- clusterID: number,
1723
- commandIdentifier: number,
1724
- timeout: number,
1725
- ): {promise: Promise<ZclPayload>; cancel: () => void} {
1726
- const sourceEndpointInfo = FIXED_ENDPOINTS[0];
1727
- const waiter = this.oneWaitress.waitFor<ZclPayload>(
1728
- {
1729
- target: networkAddress,
1730
- apsFrame: {
1731
- clusterId: clusterID,
1732
- profileId: sourceEndpointInfo.profileId, // XXX: only used by OTA upstream
1733
- sequence: 0, // set by stack
1734
- sourceEndpoint: sourceEndpointInfo.endpoint,
1735
- destinationEndpoint: endpoint,
1736
- groupId: 0,
1737
- options: EmberApsOption.NONE,
1738
- },
1739
- zclSequence: transactionSequenceNumber,
1740
- commandIdentifier,
1741
- },
1742
- timeout,
1743
- );
1744
-
1745
- return {
1746
- cancel: (): void => this.oneWaitress.remove(waiter.id),
1747
- promise: waiter.start().promise,
1748
- };
1749
- }
1750
-
1751
- //---- ZDO
1752
-
1753
- // queued, non-InterPAN
1754
- public async sendZdo(
1755
- ieeeAddress: string,
1756
- networkAddress: number,
1757
- clusterId: Zdo.ClusterId,
1758
- payload: Buffer,
1759
- disableResponse: true,
1760
- ): Promise<void>;
1761
- public async sendZdo<K extends keyof ZdoTypes.RequestToResponseMap>(
1762
- ieeeAddress: string,
1763
- networkAddress: number,
1764
- clusterId: K,
1765
- payload: Buffer,
1766
- disableResponse: false,
1767
- ): Promise<ZdoTypes.RequestToResponseMap[K]>;
1768
- public async sendZdo<K extends keyof ZdoTypes.RequestToResponseMap>(
1769
- ieeeAddress: string,
1770
- networkAddress: number,
1771
- clusterId: K,
1772
- payload: Buffer,
1773
- disableResponse: boolean,
1774
- ): Promise<ZdoTypes.RequestToResponseMap[K] | undefined> {
1775
- return await this.queue.execute(async () => {
1776
- this.checkInterpanLock();
1777
-
1778
- const clusterName = Zdo.ClusterId[clusterId];
1779
- const messageTag = this.nextZDORequestSequence();
1780
- payload[0] = messageTag;
1781
- const apsFrame: EmberApsFrame = {
1782
- profileId: Zdo.ZDO_PROFILE_ID,
1783
- clusterId,
1784
- sourceEndpoint: Zdo.ZDO_ENDPOINT,
1785
- destinationEndpoint: Zdo.ZDO_ENDPOINT,
1786
- options: DEFAULT_APS_OPTIONS,
1787
- groupId: 0,
1788
- sequence: 0, // set by stack
1789
- };
1790
- let status: SLStatus | undefined;
1791
- let apsSequence: number | undefined;
1792
-
1793
- if (ZSpec.Utils.isBroadcastAddress(networkAddress)) {
1794
- logger.debug(
1795
- () => `~~~> [ZDO ${clusterName} BROADCAST to=${networkAddress} messageTag=${messageTag} payload=${payload.toString("hex")}]`,
1796
- NS,
1797
- );
1798
-
1799
- [status, apsSequence] = await this.ezsp.ezspSendBroadcast(
1800
- ZSpec.NULL_NODE_ID, // alias
1801
- networkAddress,
1802
- 0, // nwkSequence
1803
- apsFrame,
1804
- ZDO_REQUEST_RADIUS,
1805
- messageTag,
1806
- payload,
1807
- );
1808
-
1809
- apsFrame.sequence = apsSequence;
1810
-
1811
- logger.debug(`~~~> [SENT ZDO BROADCAST messageTag=${messageTag} apsSequence=${apsSequence} status=${SLStatus[status]}]`, NS);
1812
-
1813
- if (status !== SLStatus.OK) {
1814
- throw new Error(
1815
- `~x~> [ZDO ${clusterName} BROADCAST to=${networkAddress} messageTag=${messageTag}] Failed to send request with status=${SLStatus[status]}.`,
1816
- );
1817
- }
1818
- } else {
1819
- logger.debug(
1820
- () =>
1821
- `~~~> [ZDO ${clusterName} UNICAST to=${ieeeAddress}:${networkAddress} messageTag=${messageTag} payload=${payload.toString("hex")}]`,
1822
- NS,
1823
- );
1824
-
1825
- [status, apsSequence] = await this.ezsp.ezspSendUnicast(
1826
- EmberOutgoingMessageType.DIRECT,
1827
- networkAddress,
1828
- apsFrame,
1829
- messageTag,
1830
- payload,
1831
- );
1832
- apsFrame.sequence = apsSequence;
1833
-
1834
- logger.debug(`~~~> [SENT ZDO UNICAST messageTag=${messageTag} apsSequence=${apsSequence} status=${SLStatus[status]}]`, NS);
1835
-
1836
- if (status !== SLStatus.OK) {
1837
- throw new Error(
1838
- `~x~> [ZDO ${clusterName} UNICAST to=${ieeeAddress}:${networkAddress} messageTag=${messageTag}] Failed to send request with status=${SLStatus[status]}.`,
1839
- );
1840
- }
1841
- }
1842
-
1843
- if (!disableResponse) {
1844
- const responseClusterId = Zdo.Utils.getResponseClusterId(clusterId);
1845
-
1846
- if (responseClusterId) {
1847
- return await this.oneWaitress.startWaitingFor(
1848
- {
1849
- target: responseClusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE ? (ieeeAddress as Eui64) : networkAddress,
1850
- apsFrame,
1851
- zdoResponseClusterId: responseClusterId,
1852
- },
1853
- DEFAULT_REQUEST_TIMEOUT,
1854
- );
1855
- }
1856
- }
1857
- }, networkAddress);
1858
- }
1859
-
1860
- // queued, non-InterPAN
1861
- public async permitJoin(seconds: number, networkAddress?: number): Promise<void> {
1862
- const clusterId = Zdo.ClusterId.PERMIT_JOINING_REQUEST;
1863
- const preJoining = async (): Promise<void> => {
1864
- if (seconds) {
1865
- const plaintextKey: SecManKey = {contents: Buffer.from(ZIGBEE_PROFILE_INTEROPERABILITY_LINK_KEY)};
1866
- const impKeyStatus = await this.ezsp.ezspImportTransientKey(ZSpec.BLANK_EUI64, plaintextKey);
1867
-
1868
- if (impKeyStatus !== SLStatus.OK) {
1869
- throw new Error(`[ZDO] Failed import transient key with status=${SLStatus[impKeyStatus]}.`);
1870
- }
1871
-
1872
- const setJPstatus = await this.emberSetJoinPolicy(EmberJoinDecision.USE_PRECONFIGURED_KEY);
1873
-
1874
- if (setJPstatus !== SLStatus.OK) {
1875
- throw new Error(`[ZDO] Failed set join policy with status=${SLStatus[setJPstatus]}.`);
1876
- }
1877
- } else {
1878
- await this.ezsp.ezspClearTransientLinkKeys();
1879
-
1880
- const setJPstatus = await this.emberSetJoinPolicy(EmberJoinDecision.ALLOW_REJOINS_ONLY);
1881
-
1882
- if (setJPstatus !== SLStatus.OK) {
1883
- throw new Error(`[ZDO] Failed set join policy with status=${SLStatus[setJPstatus]}.`);
1884
- }
1885
- }
1886
- };
1887
-
1888
- if (networkAddress) {
1889
- // specific device that is not `Coordinator`
1890
- await this.queue.execute<void>(async () => {
1891
- this.checkInterpanLock();
1892
- await preJoining();
1893
- });
1894
-
1895
- // `authentication`: TC significance always 1 (zb specs)
1896
- const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, clusterId, seconds, 1, []);
1897
-
1898
- const result = await this.sendZdo(ZSpec.BLANK_EUI64, networkAddress, clusterId, zdoPayload, false);
1899
-
1900
- /* v8 ignore start */
1901
- if (!Zdo.Buffalo.checkStatus<Zdo.ClusterId.PERMIT_JOINING_RESPONSE>(result)) {
1902
- // TODO: will disappear once moved upstream
1903
- throw new Zdo.StatusError(result[0]);
1904
- }
1905
- /* v8 ignore stop */
1906
- } else {
1907
- // coordinator-only (0), or all
1908
- await this.queue.execute<void>(async () => {
1909
- this.checkInterpanLock();
1910
- await preJoining();
1911
- });
1912
-
1913
- const status = await this.ezsp.ezspPermitJoining(seconds);
1914
-
1915
- if (status !== SLStatus.OK) {
1916
- throw new Error(`[ZDO] Failed coordinator permit joining request with status=${SLStatus[status]}.`);
1917
- }
1918
-
1919
- logger.debug(`Permit joining on coordinator for ${seconds} sec.`, NS);
1920
-
1921
- // broadcast permit joining ZDO
1922
- if (networkAddress === undefined) {
1923
- // `authentication`: TC significance always 1 (zb specs)
1924
- const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, clusterId, seconds, 1, []);
1925
-
1926
- await this.sendZdo(ZSpec.BLANK_EUI64, ZSpec.BroadcastAddress.DEFAULT, clusterId, zdoPayload, true);
1927
- }
1928
- }
1929
- }
1930
-
1931
- //---- ZCL
1932
-
1933
- // queued, non-InterPAN
1934
- public async sendZclFrameToEndpoint(
1935
- ieeeAddr: string,
1936
- networkAddress: number,
1937
- endpoint: number,
1938
- zclFrame: Zcl.Frame,
1939
- timeout: number,
1940
- disableResponse: boolean,
1941
- disableRecovery: boolean,
1942
- sourceEndpoint?: number,
1943
- ): Promise<ZclPayload | undefined> {
1944
- const sourceEndpointInfo = (sourceEndpoint && FIXED_ENDPOINTS.find((epi) => epi.endpoint === sourceEndpoint)) || FIXED_ENDPOINTS[0];
1945
- const command = zclFrame.command;
1946
- let commandResponseId: number | undefined;
1947
-
1948
- if (command.response !== undefined && disableResponse === false) {
1949
- commandResponseId = command.response;
1950
- } else if (!zclFrame.header.frameControl.disableDefaultResponse) {
1951
- commandResponseId = Zcl.Foundation.defaultRsp.ID;
1952
- }
1953
-
1954
- const apsFrame: EmberApsFrame = {
1955
- profileId: sourceEndpointInfo.profileId,
1956
- clusterId: zclFrame.cluster.ID,
1957
- sourceEndpoint: sourceEndpoint || FIXED_ENDPOINTS[0].endpoint,
1958
- destinationEndpoint: endpoint,
1959
- options: DEFAULT_APS_OPTIONS,
1960
- groupId: 0,
1961
- sequence: 0, // set by stack
1962
- };
1963
-
1964
- // don't RETRY if no response expected
1965
- if (commandResponseId === undefined) {
1966
- apsFrame.options &= ~EmberApsOption.RETRY;
1967
- }
1968
-
1969
- const data = zclFrame.toBuffer();
1970
-
1971
- return await this.queue.execute<ZclPayload | undefined>(async () => {
1972
- this.checkInterpanLock();
1973
-
1974
- logger.debug(
1975
- () => `~~~> [ZCL to=${ieeeAddr}:${networkAddress} apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.header)}]`,
1976
- NS,
1977
- );
1978
-
1979
- for (let i = 1; i <= QUEUE_MAX_SEND_ATTEMPTS; i++) {
1980
- let status: SLStatus = SLStatus.FAIL;
1981
-
1982
- try {
1983
- [status] = await this.ezsp.send(
1984
- EmberOutgoingMessageType.DIRECT,
1985
- networkAddress,
1986
- apsFrame,
1987
- data,
1988
- 0, // alias
1989
- 0, // alias seq
1990
- );
1991
- } catch (error) {
1992
- if (error instanceof EzspError) {
1993
- switch (error.code) {
1994
- case EzspStatus.NO_TX_SPACE: {
1995
- status = SLStatus.BUSY;
1996
- break;
1997
- }
1998
- case EzspStatus.NOT_CONNECTED: {
1999
- status = SLStatus.NETWORK_DOWN;
2000
- break;
2001
- }
2002
- }
2003
- }
2004
- }
2005
-
2006
- // `else if` order matters
2007
- if (status === SLStatus.OK) {
2008
- break;
2009
- }
2010
-
2011
- if (disableRecovery || i === QUEUE_MAX_SEND_ATTEMPTS) {
2012
- throw new Error(
2013
- `~x~> [ZCL to=${ieeeAddr}:${networkAddress} apsFrame=${JSON.stringify(apsFrame)}] Failed to send request with status=${SLStatus[status]}.`,
2014
- );
2015
- }
2016
-
2017
- if (status === SLStatus.ZIGBEE_MAX_MESSAGE_LIMIT_REACHED || status === SLStatus.BUSY) {
2018
- await wait(QUEUE_BUSY_DEFER_MSEC);
2019
- } else if (status === SLStatus.NETWORK_DOWN) {
2020
- await wait(QUEUE_NETWORK_DOWN_DEFER_MSEC);
2021
- } else {
2022
- throw new Error(
2023
- `~x~> [ZCL to=${ieeeAddr}:${networkAddress} apsFrame=${JSON.stringify(apsFrame)}] Failed to send request with status=${SLStatus[status]}.`,
2024
- );
2025
- }
2026
-
2027
- logger.debug(
2028
- `~x~> [ZCL to=${ieeeAddr}:${networkAddress}] Failed to send request attempt ${i}/${QUEUE_MAX_SEND_ATTEMPTS} with status=${SLStatus[status]}.`,
2029
- NS,
2030
- );
2031
- }
2032
-
2033
- if (commandResponseId !== undefined) {
2034
- // NOTE: aps sequence number will have been set by send function
2035
- const result = await this.oneWaitress.startWaitingFor<ZclPayload>(
2036
- {
2037
- target: networkAddress,
2038
- apsFrame,
2039
- zclSequence: zclFrame.header.transactionSequenceNumber,
2040
- commandIdentifier: commandResponseId,
2041
- },
2042
- timeout,
2043
- );
2044
-
2045
- return result;
2046
- }
2047
- }, networkAddress);
2048
- }
2049
-
2050
- // queued, non-InterPAN
2051
- public async sendZclFrameToGroup(groupID: number, zclFrame: Zcl.Frame, sourceEndpoint?: number): Promise<void> {
2052
- const sourceEndpointInfo = (sourceEndpoint && FIXED_ENDPOINTS.find((epi) => epi.endpoint === sourceEndpoint)) || FIXED_ENDPOINTS[0];
2053
- const apsFrame: EmberApsFrame = {
2054
- profileId: sourceEndpointInfo.profileId,
2055
- clusterId: zclFrame.cluster.ID,
2056
- sourceEndpoint: sourceEndpoint || FIXED_ENDPOINTS[0].endpoint,
2057
- destinationEndpoint: 0xff,
2058
- options: DEFAULT_APS_OPTIONS,
2059
- groupId: groupID,
2060
- sequence: 0, // set by stack
2061
- };
2062
- const data = zclFrame.toBuffer();
2063
-
2064
- return await this.queue.execute<void>(async () => {
2065
- this.checkInterpanLock();
2066
-
2067
- logger.debug(() => `~~~> [ZCL GROUP apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.header)}]`, NS);
2068
- const [status] = await this.ezsp.send(
2069
- EmberOutgoingMessageType.MULTICAST,
2070
- groupID, // not used with MULTICAST
2071
- apsFrame,
2072
- data,
2073
- 0, // alias
2074
- 0, // alias seq
2075
- );
2076
-
2077
- if (status !== SLStatus.OK) {
2078
- throw new Error(`~x~> [ZCL GROUP groupId=${groupID}] Failed to send with status=${SLStatus[status]}.`);
2079
- }
2080
-
2081
- // NOTE: since ezspMessageSentHandler could take a while here, we don't block, it'll just be logged if the delivery failed
2082
- await wait(QUEUE_BUSY_DEFER_MSEC);
2083
- });
2084
- }
2085
-
2086
- // queued, non-InterPAN
2087
- public async sendZclFrameToAll(
2088
- endpoint: number,
2089
- zclFrame: Zcl.Frame,
2090
- sourceEndpoint: number,
2091
- destination: ZSpec.BroadcastAddress,
2092
- ): Promise<void> {
2093
- const sourceEndpointInfo = FIXED_ENDPOINTS.find((epi) => epi.endpoint === sourceEndpoint) ?? FIXED_ENDPOINTS[0];
2094
- const apsFrame: EmberApsFrame = {
2095
- profileId: sourceEndpointInfo.profileId,
2096
- clusterId: zclFrame.cluster.ID,
2097
- sourceEndpoint,
2098
- destinationEndpoint: endpoint,
2099
- options: DEFAULT_APS_OPTIONS,
2100
- groupId: destination,
2101
- sequence: 0, // set by stack
2102
- };
2103
- const data = zclFrame.toBuffer();
2104
-
2105
- return await this.queue.execute<void>(async () => {
2106
- this.checkInterpanLock();
2107
-
2108
- logger.debug(() => `~~~> [ZCL BROADCAST apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.header)}]`, NS);
2109
- const [status] = await this.ezsp.send(
2110
- EmberOutgoingMessageType.BROADCAST,
2111
- destination,
2112
- apsFrame,
2113
- data,
2114
- 0, // alias
2115
- 0, // alias seq
2116
- );
2117
-
2118
- if (status !== SLStatus.OK) {
2119
- throw new Error(`~x~> [ZCL BROADCAST destination=${destination}] Failed to send with status=${SLStatus[status]}.`);
2120
- }
2121
-
2122
- // NOTE: since ezspMessageSentHandler could take a while here, we don't block, it'll just be logged if the delivery failed
2123
- await wait(QUEUE_BUSY_DEFER_MSEC);
2124
- });
2125
- }
2126
-
2127
- //---- InterPAN for Touchlink
2128
- // XXX: There might be a better way to handle touchlink with ZLL ezsp functions, but I don't have any device to test so, didn't look into it...
2129
- // TODO: check all this touchlink/interpan stuff
2130
-
2131
- // queued
2132
- public async setChannelInterPAN(channel: number): Promise<void> {
2133
- return await this.queue.execute<void>(async () => {
2134
- this.interpanLock = true;
2135
- const status = await this.ezsp.ezspSetLogicalAndRadioChannel(channel);
2136
-
2137
- if (status !== SLStatus.OK) {
2138
- this.interpanLock = false; // XXX: ok?
2139
- throw new Error(`Failed to set InterPAN channel to '${channel}' with status=${SLStatus[status]}.`);
2140
- }
2141
- });
2142
- }
2143
-
2144
- // queued
2145
- public async sendZclFrameInterPANToIeeeAddr(zclFrame: Zcl.Frame, ieeeAddress: string): Promise<void> {
2146
- return await this.queue.execute<void>(async () => {
2147
- const msgBuffalo = new EzspBuffalo(Buffer.alloc(MAXIMUM_INTERPAN_LENGTH));
2148
-
2149
- // cache-enabled getters
2150
- const sourcePanId = await this.emberGetPanId();
2151
- const sourceEui64 = await this.emberGetEui64();
2152
-
2153
- msgBuffalo.writeUInt16(LONG_DEST_FRAME_CONTROL | MAC_ACK_REQUIRED); // macFrameControl
2154
- msgBuffalo.writeUInt8(0); // sequence Skip Sequence number, stack sets the sequence number.
2155
- msgBuffalo.writeUInt16(ZSpec.INVALID_PAN_ID); // destPanId
2156
- msgBuffalo.writeIeeeAddr(ieeeAddress); // destAddress (longAddress)
2157
- msgBuffalo.writeUInt16(sourcePanId); // sourcePanId
2158
- msgBuffalo.writeIeeeAddr(sourceEui64); // sourceAddress
2159
- msgBuffalo.writeUInt16(STUB_NWK_FRAME_CONTROL); // nwkFrameControl
2160
- msgBuffalo.writeUInt8(EmberInterpanMessageType.UNICAST | INTERPAN_APS_FRAME_TYPE); // apsFrameControl
2161
- msgBuffalo.writeUInt16(zclFrame.cluster.ID);
2162
- msgBuffalo.writeUInt16(ZSpec.TOUCHLINK_PROFILE_ID);
2163
-
2164
- logger.debug(() => `~~~> [ZCL TOUCHLINK to=${ieeeAddress} header=${JSON.stringify(zclFrame.header)}]`, NS);
2165
- const status = await this.ezsp.ezspSendRawMessage(
2166
- Buffer.concat([msgBuffalo.getWritten(), zclFrame.toBuffer()]),
2167
- EmberTransmitPriority.NORMAL,
2168
- true,
2169
- );
2170
-
2171
- if (status !== SLStatus.OK) {
2172
- throw new Error(`~x~> [ZCL TOUCHLINK to=${ieeeAddress}] Failed to send with status=${SLStatus[status]}.`);
2173
- }
2174
-
2175
- // NOTE: can use ezspRawTransmitCompleteHandler if needed here
2176
- });
2177
- }
2178
-
2179
- // queued
2180
- public async sendZclFrameInterPANBroadcast(zclFrame: Zcl.Frame, timeout: number): Promise<ZclPayload> {
2181
- const command = zclFrame.command;
2182
-
2183
- if (command.response === undefined) {
2184
- throw new Error(`Command '${command.name}' has no response, cannot wait for response.`);
2185
- }
2186
-
2187
- const endpoint = FIXED_ENDPOINTS[0].endpoint;
2188
- // just for waitress
2189
- const apsFrame: EmberApsFrame = {
2190
- profileId: ZSpec.TOUCHLINK_PROFILE_ID,
2191
- clusterId: zclFrame.cluster.ID,
2192
- sourceEndpoint: endpoint, // arbitrary since not sent over-the-air
2193
- destinationEndpoint: endpoint,
2194
- options: EmberApsOption.NONE,
2195
- groupId: ZSpec.BroadcastAddress.SLEEPY,
2196
- sequence: 0, // set by stack
2197
- };
2198
-
2199
- return await this.queue.execute<ZclPayload>(async () => {
2200
- const msgBuffalo = new EzspBuffalo(Buffer.alloc(MAXIMUM_INTERPAN_LENGTH));
2201
-
2202
- // cache-enabled getters
2203
- const sourcePanId = await this.emberGetPanId();
2204
- const sourceEui64 = await this.emberGetEui64();
2205
-
2206
- msgBuffalo.writeUInt16(SHORT_DEST_FRAME_CONTROL); // macFrameControl
2207
- msgBuffalo.writeUInt8(0); // sequence Skip Sequence number, stack sets the sequence number.
2208
- msgBuffalo.writeUInt16(ZSpec.INVALID_PAN_ID); // destPanId
2209
- msgBuffalo.writeUInt16(apsFrame.groupId); // destAddress (longAddress)
2210
- msgBuffalo.writeUInt16(sourcePanId); // sourcePanId
2211
- msgBuffalo.writeIeeeAddr(sourceEui64); // sourceAddress
2212
- msgBuffalo.writeUInt16(STUB_NWK_FRAME_CONTROL); // nwkFrameControl
2213
- msgBuffalo.writeUInt8(EmberInterpanMessageType.BROADCAST | INTERPAN_APS_FRAME_TYPE); // apsFrameControl
2214
- msgBuffalo.writeUInt16(apsFrame.clusterId);
2215
- msgBuffalo.writeUInt16(apsFrame.profileId);
2216
-
2217
- const data = Buffer.concat([msgBuffalo.getWritten(), zclFrame.toBuffer()]);
2218
-
2219
- logger.debug(() => `~~~> [ZCL TOUCHLINK BROADCAST header=${JSON.stringify(zclFrame.header)}]`, NS);
2220
- const status = await this.ezsp.ezspSendRawMessage(data, EmberTransmitPriority.NORMAL, true);
2221
-
2222
- if (status !== SLStatus.OK) {
2223
- throw new Error(`~x~> [ZCL TOUCHLINK BROADCAST] Failed to send with status=${SLStatus[status]}.`);
2224
- }
2225
-
2226
- // NOTE: can use ezspRawTransmitCompleteHandler if needed here
2227
-
2228
- const result = await this.oneWaitress.startWaitingFor<ZclPayload>(
2229
- {
2230
- target: undefined,
2231
- apsFrame: apsFrame,
2232
- zclSequence: zclFrame.header.transactionSequenceNumber,
2233
- commandIdentifier: command.response,
2234
- },
2235
- timeout,
2236
- );
2237
-
2238
- return result;
2239
- });
2240
- }
2241
-
2242
- // queued
2243
- public async restoreChannelInterPAN(): Promise<void> {
2244
- return await this.queue.execute<void>(async () => {
2245
- const status = await this.ezsp.ezspSetLogicalAndRadioChannel(this.networkOptions.channelList[0]);
2246
-
2247
- if (status !== SLStatus.OK) {
2248
- throw new Error(`Failed to restore InterPAN channel to '${this.networkOptions.channelList[0]}' with status=${SLStatus[status]}.`);
2249
- }
2250
-
2251
- // let adapter settle down
2252
- await wait(QUEUE_NETWORK_DOWN_DEFER_MSEC);
2253
-
2254
- this.interpanLock = false;
2255
- });
2256
- }
2257
-
2258
- //-- END Adapter implementation
2259
-
2260
- private checkInterpanLock(): void {
2261
- if (this.interpanLock) {
2262
- throw new Error("[INTERPAN MODE] Cannot execute non-InterPAN commands.");
2263
- }
2264
- }
2265
- }