@willieee802/zigbee-herdsman 0.48.2 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1025) hide show
  1. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  2. package/.github/copilot-instructions.md +689 -0
  3. package/.github/dependabot.yml +22 -0
  4. package/.github/prompts/copilot-instructions-blueprint-generator.prompt.md +294 -0
  5. package/.github/prompts/create-agentsmd.prompt.md +249 -0
  6. package/.github/prompts/create-specification.prompt.md +127 -0
  7. package/.github/prompts/review-and-refactor.prompt.md +15 -0
  8. package/.github/prompts/update-specification.prompt.md +127 -0
  9. package/.github/workflows/ci.yml +70 -0
  10. package/.github/workflows/release-please.yml +18 -0
  11. package/.github/workflows/stale.yml +20 -0
  12. package/.github/workflows/typedoc.yaml +47 -0
  13. package/.release-please-manifest.json +2 -2
  14. package/.vscode/extensions.json +3 -0
  15. package/.vscode/settings.json +11 -0
  16. package/AGENTS.md +441 -0
  17. package/CHANGELOG.md +1245 -0
  18. package/README.md +2 -2
  19. package/biome.json +103 -0
  20. package/examples/join-and-log.js +18 -18
  21. package/package.json +83 -70
  22. package/release-please-config.json +8 -8
  23. package/scripts/check-clusters-changes.ts +328 -0
  24. package/scripts/clusters-changes.log +584 -0
  25. package/scripts/clusters-typegen.ts +608 -0
  26. package/scripts/utils.ts +88 -0
  27. package/scripts/zap-update-clusters-report.json +303 -0
  28. package/scripts/zap-update-clusters.ts +1520 -0
  29. package/scripts/zap-update-types.ts +707 -0
  30. package/scripts/zap-xml-clusters-overrides-data.ts +52 -0
  31. package/scripts/zap-xml-clusters-overrides.ts +400 -0
  32. package/scripts/zap-xml-types.ts +146 -0
  33. package/src/adapter/adapter.ts +210 -0
  34. package/src/adapter/adapterDiscovery.ts +736 -0
  35. package/src/adapter/const.ts +12 -0
  36. package/src/adapter/deconz/adapter/deconzAdapter.ts +888 -0
  37. package/src/adapter/deconz/driver/constants.ts +246 -0
  38. package/src/adapter/deconz/driver/driver.ts +1528 -0
  39. package/src/adapter/deconz/driver/frame.ts +11 -0
  40. package/src/adapter/deconz/driver/frameParser.ts +766 -0
  41. package/src/adapter/deconz/driver/parser.ts +45 -0
  42. package/src/adapter/deconz/driver/writer.ts +22 -0
  43. package/src/adapter/deconz/types.d.ts +13 -0
  44. package/src/adapter/ember/adapter/emberAdapter.ts +2262 -0
  45. package/src/adapter/ember/adapter/endpoints.ts +86 -0
  46. package/src/adapter/ember/adapter/oneWaitress.ts +324 -0
  47. package/src/adapter/ember/adapter/tokensManager.ts +780 -0
  48. package/src/adapter/ember/consts.ts +178 -0
  49. package/src/adapter/ember/enums.ts +1746 -0
  50. package/src/adapter/ember/ezsp/buffalo.ts +1392 -0
  51. package/src/adapter/ember/ezsp/consts.ts +148 -0
  52. package/src/adapter/ember/ezsp/enums.ts +1114 -0
  53. package/src/adapter/ember/ezsp/ezsp.ts +9073 -0
  54. package/src/adapter/ember/ezspError.ts +10 -0
  55. package/src/adapter/ember/types.ts +866 -0
  56. package/src/adapter/ember/uart/ash.ts +1933 -0
  57. package/src/adapter/ember/uart/consts.ts +109 -0
  58. package/src/adapter/ember/uart/enums.ts +192 -0
  59. package/src/adapter/ember/uart/parser.ts +42 -0
  60. package/src/adapter/ember/uart/queues.ts +247 -0
  61. package/src/adapter/ember/uart/writer.ts +50 -0
  62. package/src/adapter/ember/utils/initters.ts +58 -0
  63. package/src/adapter/ember/utils/math.ts +71 -0
  64. package/src/adapter/events.ts +21 -0
  65. package/src/adapter/ezsp/adapter/backup.ts +100 -0
  66. package/src/adapter/ezsp/adapter/ezspAdapter.ts +632 -0
  67. package/src/adapter/ezsp/driver/commands.ts +2497 -0
  68. package/src/adapter/ezsp/driver/consts.ts +11 -0
  69. package/src/adapter/ezsp/driver/driver.ts +1002 -0
  70. package/src/adapter/ezsp/driver/ezsp.ts +802 -0
  71. package/src/adapter/ezsp/driver/frame.ts +101 -0
  72. package/src/adapter/ezsp/driver/index.ts +4 -0
  73. package/src/adapter/ezsp/driver/multicast.ts +78 -0
  74. package/src/adapter/ezsp/driver/parser.ts +81 -0
  75. package/src/adapter/ezsp/driver/types/basic.ts +201 -0
  76. package/src/adapter/ezsp/driver/types/index.ts +239 -0
  77. package/src/adapter/ezsp/driver/types/named.ts +2330 -0
  78. package/src/adapter/ezsp/driver/types/struct.ts +844 -0
  79. package/src/adapter/ezsp/driver/uart.ts +460 -0
  80. package/src/adapter/ezsp/driver/utils/crc16ccitt.ts +44 -0
  81. package/src/adapter/ezsp/driver/utils/index.ts +32 -0
  82. package/src/adapter/ezsp/driver/writer.ts +64 -0
  83. package/src/adapter/index.ts +3 -0
  84. package/src/adapter/serialPort.ts +58 -0
  85. package/src/adapter/tstype.ts +57 -0
  86. package/src/adapter/utils.ts +41 -0
  87. package/src/adapter/z-stack/adapter/adapter-backup.ts +509 -0
  88. package/src/adapter/z-stack/adapter/adapter-nv-memory.ts +457 -0
  89. package/src/adapter/z-stack/adapter/endpoints.ts +60 -0
  90. package/src/adapter/z-stack/adapter/manager.ts +543 -0
  91. package/src/adapter/z-stack/adapter/tstype.ts +6 -0
  92. package/src/adapter/z-stack/adapter/zStackAdapter.ts +1350 -0
  93. package/src/adapter/z-stack/constants/af.ts +27 -0
  94. package/src/adapter/z-stack/constants/common.ts +285 -0
  95. package/src/adapter/z-stack/constants/dbg.ts +23 -0
  96. package/src/adapter/z-stack/constants/index.ts +11 -0
  97. package/src/adapter/z-stack/constants/mac.ts +128 -0
  98. package/src/adapter/z-stack/constants/sapi.ts +25 -0
  99. package/src/adapter/z-stack/constants/sys.ts +72 -0
  100. package/src/adapter/z-stack/constants/util.ts +82 -0
  101. package/src/adapter/z-stack/constants/utils.ts +14 -0
  102. package/src/adapter/z-stack/constants/zdo.ts +103 -0
  103. package/src/adapter/z-stack/models/startup-options.ts +13 -0
  104. package/src/adapter/z-stack/structs/entries/address-manager-entry.ts +44 -0
  105. package/src/adapter/z-stack/structs/entries/address-manager-table.ts +19 -0
  106. package/src/adapter/z-stack/structs/entries/aps-link-key-data-entry.ts +12 -0
  107. package/src/adapter/z-stack/structs/entries/aps-link-key-data-table.ts +21 -0
  108. package/src/adapter/z-stack/structs/entries/aps-tc-link-key-entry.ts +19 -0
  109. package/src/adapter/z-stack/structs/entries/aps-tc-link-key-table.ts +21 -0
  110. package/src/adapter/z-stack/structs/entries/channel-list.ts +8 -0
  111. package/src/adapter/z-stack/structs/entries/has-configured.ts +16 -0
  112. package/src/adapter/z-stack/structs/entries/index.ts +16 -0
  113. package/src/adapter/z-stack/structs/entries/nib.ts +66 -0
  114. package/src/adapter/z-stack/structs/entries/nwk-key-descriptor.ts +15 -0
  115. package/src/adapter/z-stack/structs/entries/nwk-key.ts +13 -0
  116. package/src/adapter/z-stack/structs/entries/nwk-pan-id.ts +8 -0
  117. package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.ts +20 -0
  118. package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.ts +19 -0
  119. package/src/adapter/z-stack/structs/entries/security-manager-entry.ts +33 -0
  120. package/src/adapter/z-stack/structs/entries/security-manager-table.ts +22 -0
  121. package/src/adapter/z-stack/structs/index.ts +4 -0
  122. package/src/adapter/z-stack/structs/serializable-memory-object.ts +14 -0
  123. package/src/adapter/z-stack/structs/struct.ts +367 -0
  124. package/src/adapter/z-stack/structs/table.ts +198 -0
  125. package/src/adapter/z-stack/unpi/constants.ts +33 -0
  126. package/src/adapter/z-stack/unpi/frame.ts +62 -0
  127. package/src/adapter/z-stack/unpi/index.ts +4 -0
  128. package/src/adapter/z-stack/unpi/parser.ts +67 -0
  129. package/src/adapter/z-stack/unpi/writer.ts +37 -0
  130. package/src/adapter/z-stack/utils/channel-list.ts +40 -0
  131. package/src/adapter/z-stack/utils/index.ts +2 -0
  132. package/src/adapter/z-stack/utils/network-options.ts +26 -0
  133. package/src/adapter/z-stack/znp/buffaloZnp.ts +175 -0
  134. package/src/adapter/z-stack/znp/definition.ts +2713 -0
  135. package/src/adapter/z-stack/znp/index.ts +2 -0
  136. package/src/adapter/z-stack/znp/parameterType.ts +22 -0
  137. package/src/adapter/z-stack/znp/tstype.ts +44 -0
  138. package/src/adapter/z-stack/znp/utils.ts +10 -0
  139. package/src/adapter/z-stack/znp/znp.ts +345 -0
  140. package/src/adapter/z-stack/znp/zpiObject.ts +148 -0
  141. package/src/adapter/zboss/adapter/zbossAdapter.ts +535 -0
  142. package/src/adapter/zboss/commands.ts +1184 -0
  143. package/src/adapter/zboss/consts.ts +9 -0
  144. package/src/adapter/zboss/driver.ts +422 -0
  145. package/src/adapter/zboss/enums.ts +360 -0
  146. package/src/adapter/zboss/frame.ts +227 -0
  147. package/src/adapter/zboss/reader.ts +65 -0
  148. package/src/adapter/zboss/types.ts +0 -0
  149. package/src/adapter/zboss/uart.ts +428 -0
  150. package/src/adapter/zboss/utils.ts +58 -0
  151. package/src/adapter/zboss/writer.ts +49 -0
  152. package/src/adapter/zigate/adapter/patchZdoBuffaloBE.ts +25 -0
  153. package/src/adapter/zigate/adapter/zigateAdapter.ts +633 -0
  154. package/src/adapter/zigate/driver/LICENSE +17 -0
  155. package/src/adapter/zigate/driver/buffaloZiGate.ts +210 -0
  156. package/src/adapter/zigate/driver/commandType.ts +418 -0
  157. package/src/adapter/zigate/driver/constants.ts +150 -0
  158. package/src/adapter/zigate/driver/frame.ts +197 -0
  159. package/src/adapter/zigate/driver/messageType.ts +287 -0
  160. package/src/adapter/zigate/driver/parameterType.ts +32 -0
  161. package/src/adapter/zigate/driver/ziGateObject.ts +146 -0
  162. package/src/adapter/zigate/driver/zigate.ts +422 -0
  163. package/src/adapter/zoh/adapter/utils.ts +27 -0
  164. package/src/adapter/zoh/adapter/zohAdapter.ts +931 -0
  165. package/src/buffalo/buffalo.ts +336 -0
  166. package/src/buffalo/index.ts +1 -0
  167. package/src/controller/controller.ts +1159 -0
  168. package/src/controller/database.ts +148 -0
  169. package/src/controller/events.ts +52 -0
  170. package/src/controller/greenPower.ts +613 -0
  171. package/src/controller/helpers/index.ts +1 -0
  172. package/src/controller/helpers/installCodes.ts +107 -0
  173. package/src/controller/helpers/ota.ts +575 -0
  174. package/src/controller/helpers/request.ts +96 -0
  175. package/src/controller/helpers/requestQueue.ts +126 -0
  176. package/src/controller/helpers/zclFrameConverter.ts +64 -0
  177. package/src/controller/helpers/zclTransactionSequenceNumber.ts +15 -0
  178. package/src/controller/index.ts +6 -0
  179. package/src/controller/model/device.ts +1791 -0
  180. package/src/controller/model/endpoint.ts +1235 -0
  181. package/src/controller/model/entity.ts +43 -0
  182. package/src/controller/model/group.ts +446 -0
  183. package/src/controller/model/index.ts +5 -0
  184. package/src/controller/model/konnextConfig.ts +6 -0
  185. package/src/controller/model/zigbeeEntity.ts +30 -0
  186. package/src/controller/touchlink.ts +195 -0
  187. package/src/controller/tstype.ts +374 -0
  188. package/src/index.ts +14 -0
  189. package/src/models/backup-storage-legacy.ts +48 -0
  190. package/src/models/backup-storage-unified.ts +47 -0
  191. package/src/models/backup.ts +37 -0
  192. package/src/models/index.ts +5 -0
  193. package/src/models/network-options.ts +11 -0
  194. package/src/utils/aes.ts +218 -0
  195. package/src/utils/async-mutex.ts +31 -0
  196. package/src/utils/backup.ts +152 -0
  197. package/src/utils/index.ts +5 -0
  198. package/src/utils/logger.ts +20 -0
  199. package/src/utils/patchBigIntSerialization.ts +8 -0
  200. package/src/utils/queue.ts +79 -0
  201. package/src/utils/timeService.ts +139 -0
  202. package/src/utils/utils.ts +19 -0
  203. package/src/utils/wait.ts +5 -0
  204. package/src/utils/waitress.ts +96 -0
  205. package/src/zspec/consts.ts +89 -0
  206. package/src/zspec/enums.ts +22 -0
  207. package/src/zspec/index.ts +3 -0
  208. package/src/zspec/tstypes.ts +18 -0
  209. package/src/zspec/utils.ts +247 -0
  210. package/src/zspec/zcl/buffaloZcl.ts +1073 -0
  211. package/src/zspec/zcl/definition/cluster.ts +7845 -0
  212. package/src/zspec/zcl/definition/clusters-types.ts +8577 -0
  213. package/src/zspec/zcl/definition/consts.ts +24 -0
  214. package/src/zspec/zcl/definition/datatypes.ts +2454 -0
  215. package/src/zspec/zcl/definition/enums.ts +224 -0
  216. package/src/zspec/zcl/definition/foundation.ts +342 -0
  217. package/src/zspec/zcl/definition/manufacturerCode.ts +730 -0
  218. package/src/zspec/zcl/definition/status.ts +69 -0
  219. package/src/zspec/zcl/definition/tstype.ts +446 -0
  220. package/src/zspec/zcl/index.ts +11 -0
  221. package/src/zspec/zcl/utils.ts +521 -0
  222. package/src/zspec/zcl/zclFrame.ts +383 -0
  223. package/src/zspec/zcl/zclHeader.ts +102 -0
  224. package/src/zspec/zcl/zclStatusError.ts +10 -0
  225. package/src/zspec/zdo/buffaloZdo.ts +2336 -0
  226. package/src/zspec/zdo/definition/clusters.ts +722 -0
  227. package/src/zspec/zdo/definition/consts.ts +16 -0
  228. package/src/zspec/zdo/definition/enums.ts +99 -0
  229. package/src/zspec/zdo/definition/status.ts +105 -0
  230. package/src/zspec/zdo/definition/tstypes.ts +1062 -0
  231. package/src/zspec/zdo/index.ts +7 -0
  232. package/src/zspec/zdo/utils.ts +76 -0
  233. package/src/zspec/zdo/zdoStatusError.ts +10 -0
  234. package/test/adapter/adapter.test.ts +1276 -0
  235. package/test/adapter/ember/ash.test.ts +337 -0
  236. package/test/adapter/ember/consts.ts +131 -0
  237. package/test/adapter/ember/emberAdapter.test.ts +3447 -0
  238. package/test/adapter/ember/ezsp.test.ts +389 -0
  239. package/test/adapter/ember/ezspBuffalo.test.ts +93 -0
  240. package/test/adapter/ember/ezspError.test.ts +12 -0
  241. package/test/adapter/ember/math.test.ts +190 -0
  242. package/test/adapter/ezsp/frame.test.ts +30 -0
  243. package/test/adapter/ezsp/uart.test.ts +181 -0
  244. package/test/adapter/z-stack/adapter.test.ts +4260 -0
  245. package/test/adapter/z-stack/constants.test.ts +33 -0
  246. package/test/adapter/z-stack/structs.test.ts +115 -0
  247. package/test/adapter/z-stack/unpi.test.ts +213 -0
  248. package/test/adapter/z-stack/znp.test.ts +1288 -0
  249. package/test/adapter/zboss/fixZdoResponse.test.ts +179 -0
  250. package/test/adapter/zigate/patchZdoBuffaloBE.test.ts +81 -0
  251. package/test/adapter/zigate/zdo.test.ts +187 -0
  252. package/test/adapter/zoh/utils.test.ts +36 -0
  253. package/test/adapter/zoh/zohAdapter.test.ts +1451 -0
  254. package/test/benchOptions.ts +14 -0
  255. package/test/buffalo.test.ts +431 -0
  256. package/test/controller.bench.ts +215 -0
  257. package/test/controller.test.ts +10030 -0
  258. package/test/data/integrity-code-1166-012B-24031511-upgradeMe-RB 249 T.zigbee +0 -0
  259. package/test/data/manuf-tags-tradfri-cv-cct-unified_release_prod_v587757105_33e34452-9267-4665-bc5a-844c8f61f063.ota +0 -0
  260. package/test/data/padded-tretakt_smart_plug_soc-0x1100-2.4.25-prod.ota.ota.signed +0 -0
  261. package/test/data/telink-aes-A60_RGBW_T-0x00B6-0x03483712-MF_DIS.OTA +0 -0
  262. package/test/data/zbminir2_v1.0.8.ota +0 -0
  263. package/test/device-ota.test.ts +3332 -0
  264. package/test/greenpower.test.ts +1409 -0
  265. package/test/mockAdapters.ts +95 -0
  266. package/test/mockDevices.ts +623 -0
  267. package/test/requests.bench.ts +321 -0
  268. package/test/testUtils.ts +20 -0
  269. package/test/timeService.test.ts +214 -0
  270. package/test/tsconfig.json +9 -0
  271. package/test/utils/math.ts +19 -0
  272. package/test/utils.test.ts +352 -0
  273. package/test/vitest.config.mts +28 -0
  274. package/test/vitest.ts +9 -0
  275. package/test/zcl.test.ts +2858 -0
  276. package/test/zspec/utils.test.ts +68 -0
  277. package/test/zspec/zcl/buffalo.test.ts +1316 -0
  278. package/test/zspec/zcl/frame.test.ts +1056 -0
  279. package/test/zspec/zcl/utils.test.ts +650 -0
  280. package/test/zspec/zdo/buffalo.test.ts +1850 -0
  281. package/test/zspec/zdo/utils.test.ts +241 -0
  282. package/tsconfig.json +8 -10
  283. package/.babelrc.js +0 -6
  284. package/.eslintignore +0 -3
  285. package/dist/adapter/adapter.d.ts +0 -64
  286. package/dist/adapter/adapter.d.ts.map +0 -1
  287. package/dist/adapter/adapter.js +0 -157
  288. package/dist/adapter/adapter.js.map +0 -1
  289. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +0 -71
  290. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +0 -1
  291. package/dist/adapter/deconz/adapter/deconzAdapter.js +0 -1072
  292. package/dist/adapter/deconz/adapter/deconzAdapter.js.map +0 -1
  293. package/dist/adapter/deconz/adapter/index.d.ts +0 -3
  294. package/dist/adapter/deconz/adapter/index.d.ts.map +0 -1
  295. package/dist/adapter/deconz/adapter/index.js +0 -11
  296. package/dist/adapter/deconz/adapter/index.js.map +0 -1
  297. package/dist/adapter/deconz/driver/constants.d.ts +0 -105
  298. package/dist/adapter/deconz/driver/constants.d.ts.map +0 -1
  299. package/dist/adapter/deconz/driver/constants.js +0 -56
  300. package/dist/adapter/deconz/driver/constants.js.map +0 -1
  301. package/dist/adapter/deconz/driver/driver.d.ts +0 -82
  302. package/dist/adapter/deconz/driver/driver.d.ts.map +0 -1
  303. package/dist/adapter/deconz/driver/driver.js +0 -751
  304. package/dist/adapter/deconz/driver/driver.js.map +0 -1
  305. package/dist/adapter/deconz/driver/frame.d.ts +0 -7
  306. package/dist/adapter/deconz/driver/frame.d.ts.map +0 -1
  307. package/dist/adapter/deconz/driver/frame.js +0 -14
  308. package/dist/adapter/deconz/driver/frame.js.map +0 -1
  309. package/dist/adapter/deconz/driver/frameParser.d.ts +0 -3
  310. package/dist/adapter/deconz/driver/frameParser.d.ts.map +0 -1
  311. package/dist/adapter/deconz/driver/frameParser.js +0 -444
  312. package/dist/adapter/deconz/driver/frameParser.js.map +0 -1
  313. package/dist/adapter/deconz/driver/parser.d.ts +0 -13
  314. package/dist/adapter/deconz/driver/parser.d.ts.map +0 -1
  315. package/dist/adapter/deconz/driver/parser.js +0 -64
  316. package/dist/adapter/deconz/driver/parser.js.map +0 -1
  317. package/dist/adapter/deconz/driver/writer.d.ts +0 -9
  318. package/dist/adapter/deconz/driver/writer.d.ts.map +0 -1
  319. package/dist/adapter/deconz/driver/writer.js +0 -45
  320. package/dist/adapter/deconz/driver/writer.js.map +0 -1
  321. package/dist/adapter/ember/adapter/emberAdapter.d.ts +0 -806
  322. package/dist/adapter/ember/adapter/emberAdapter.d.ts.map +0 -1
  323. package/dist/adapter/ember/adapter/emberAdapter.js +0 -2942
  324. package/dist/adapter/ember/adapter/emberAdapter.js.map +0 -1
  325. package/dist/adapter/ember/adapter/endpoints.d.ts +0 -27
  326. package/dist/adapter/ember/adapter/endpoints.d.ts.map +0 -1
  327. package/dist/adapter/ember/adapter/endpoints.js +0 -68
  328. package/dist/adapter/ember/adapter/endpoints.js.map +0 -1
  329. package/dist/adapter/ember/adapter/index.d.ts +0 -3
  330. package/dist/adapter/ember/adapter/index.d.ts.map +0 -1
  331. package/dist/adapter/ember/adapter/index.js +0 -6
  332. package/dist/adapter/ember/adapter/index.js.map +0 -1
  333. package/dist/adapter/ember/adapter/oneWaitress.d.ts +0 -108
  334. package/dist/adapter/ember/adapter/oneWaitress.d.ts.map +0 -1
  335. package/dist/adapter/ember/adapter/oneWaitress.js +0 -241
  336. package/dist/adapter/ember/adapter/oneWaitress.js.map +0 -1
  337. package/dist/adapter/ember/adapter/requestQueue.d.ts +0 -57
  338. package/dist/adapter/ember/adapter/requestQueue.d.ts.map +0 -1
  339. package/dist/adapter/ember/adapter/requestQueue.js +0 -139
  340. package/dist/adapter/ember/adapter/requestQueue.js.map +0 -1
  341. package/dist/adapter/ember/adapter/tokensManager.d.ts +0 -69
  342. package/dist/adapter/ember/adapter/tokensManager.d.ts.map +0 -1
  343. package/dist/adapter/ember/adapter/tokensManager.js +0 -688
  344. package/dist/adapter/ember/adapter/tokensManager.js.map +0 -1
  345. package/dist/adapter/ember/consts.d.ts +0 -191
  346. package/dist/adapter/ember/consts.d.ts.map +0 -1
  347. package/dist/adapter/ember/consts.js +0 -246
  348. package/dist/adapter/ember/consts.js.map +0 -1
  349. package/dist/adapter/ember/enums.d.ts +0 -2172
  350. package/dist/adapter/ember/enums.d.ts.map +0 -1
  351. package/dist/adapter/ember/enums.js +0 -2375
  352. package/dist/adapter/ember/enums.js.map +0 -1
  353. package/dist/adapter/ember/ezsp/buffalo.d.ts +0 -156
  354. package/dist/adapter/ember/ezsp/buffalo.d.ts.map +0 -1
  355. package/dist/adapter/ember/ezsp/buffalo.js +0 -1033
  356. package/dist/adapter/ember/ezsp/buffalo.js.map +0 -1
  357. package/dist/adapter/ember/ezsp/consts.d.ts +0 -116
  358. package/dist/adapter/ember/ezsp/consts.d.ts.map +0 -1
  359. package/dist/adapter/ember/ezsp/consts.js +0 -128
  360. package/dist/adapter/ember/ezsp/consts.js.map +0 -1
  361. package/dist/adapter/ember/ezsp/enums.d.ts +0 -879
  362. package/dist/adapter/ember/ezsp/enums.d.ts.map +0 -1
  363. package/dist/adapter/ember/ezsp/enums.js +0 -948
  364. package/dist/adapter/ember/ezsp/enums.js.map +0 -1
  365. package/dist/adapter/ember/ezsp/ezsp.d.ts +0 -2662
  366. package/dist/adapter/ember/ezsp/ezsp.d.ts.map +0 -1
  367. package/dist/adapter/ember/ezsp/ezsp.js +0 -6454
  368. package/dist/adapter/ember/ezsp/ezsp.js.map +0 -1
  369. package/dist/adapter/ember/types.d.ts +0 -733
  370. package/dist/adapter/ember/types.d.ts.map +0 -1
  371. package/dist/adapter/ember/types.js +0 -3
  372. package/dist/adapter/ember/types.js.map +0 -1
  373. package/dist/adapter/ember/uart/ash.d.ts +0 -464
  374. package/dist/adapter/ember/uart/ash.d.ts.map +0 -1
  375. package/dist/adapter/ember/uart/ash.js +0 -1633
  376. package/dist/adapter/ember/uart/ash.js.map +0 -1
  377. package/dist/adapter/ember/uart/consts.d.ts +0 -91
  378. package/dist/adapter/ember/uart/consts.d.ts.map +0 -1
  379. package/dist/adapter/ember/uart/consts.js +0 -100
  380. package/dist/adapter/ember/uart/consts.js.map +0 -1
  381. package/dist/adapter/ember/uart/enums.d.ts +0 -191
  382. package/dist/adapter/ember/uart/enums.d.ts.map +0 -1
  383. package/dist/adapter/ember/uart/enums.js +0 -197
  384. package/dist/adapter/ember/uart/enums.js.map +0 -1
  385. package/dist/adapter/ember/uart/parser.d.ts +0 -10
  386. package/dist/adapter/ember/uart/parser.d.ts.map +0 -1
  387. package/dist/adapter/ember/uart/parser.js +0 -37
  388. package/dist/adapter/ember/uart/parser.js.map +0 -1
  389. package/dist/adapter/ember/uart/queues.d.ts +0 -85
  390. package/dist/adapter/ember/uart/queues.d.ts.map +0 -1
  391. package/dist/adapter/ember/uart/queues.js +0 -214
  392. package/dist/adapter/ember/uart/queues.js.map +0 -1
  393. package/dist/adapter/ember/uart/writer.d.ts +0 -15
  394. package/dist/adapter/ember/uart/writer.d.ts.map +0 -1
  395. package/dist/adapter/ember/uart/writer.js +0 -46
  396. package/dist/adapter/ember/uart/writer.js.map +0 -1
  397. package/dist/adapter/ember/utils/initters.d.ts +0 -20
  398. package/dist/adapter/ember/utils/initters.d.ts.map +0 -1
  399. package/dist/adapter/ember/utils/initters.js +0 -58
  400. package/dist/adapter/ember/utils/initters.js.map +0 -1
  401. package/dist/adapter/ember/utils/math.d.ts +0 -51
  402. package/dist/adapter/ember/utils/math.d.ts.map +0 -1
  403. package/dist/adapter/ember/utils/math.js +0 -102
  404. package/dist/adapter/ember/utils/math.js.map +0 -1
  405. package/dist/adapter/ember/zdo.d.ts +0 -925
  406. package/dist/adapter/ember/zdo.d.ts.map +0 -1
  407. package/dist/adapter/ember/zdo.js +0 -723
  408. package/dist/adapter/ember/zdo.js.map +0 -1
  409. package/dist/adapter/events.d.ts +0 -42
  410. package/dist/adapter/events.d.ts.map +0 -1
  411. package/dist/adapter/events.js +0 -13
  412. package/dist/adapter/events.js.map +0 -1
  413. package/dist/adapter/ezsp/adapter/backup.d.ts +0 -13
  414. package/dist/adapter/ezsp/adapter/backup.d.ts.map +0 -1
  415. package/dist/adapter/ezsp/adapter/backup.js +0 -101
  416. package/dist/adapter/ezsp/adapter/backup.js.map +0 -1
  417. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts +0 -65
  418. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts.map +0 -1
  419. package/dist/adapter/ezsp/adapter/ezspAdapter.js +0 -634
  420. package/dist/adapter/ezsp/adapter/ezspAdapter.js.map +0 -1
  421. package/dist/adapter/ezsp/adapter/index.d.ts +0 -3
  422. package/dist/adapter/ezsp/adapter/index.d.ts.map +0 -1
  423. package/dist/adapter/ezsp/adapter/index.js +0 -11
  424. package/dist/adapter/ezsp/adapter/index.js.map +0 -1
  425. package/dist/adapter/ezsp/driver/commands.d.ts +0 -37
  426. package/dist/adapter/ezsp/driver/commands.d.ts.map +0 -1
  427. package/dist/adapter/ezsp/driver/commands.js +0 -2387
  428. package/dist/adapter/ezsp/driver/commands.js.map +0 -1
  429. package/dist/adapter/ezsp/driver/consts.d.ts +0 -11
  430. package/dist/adapter/ezsp/driver/consts.d.ts.map +0 -1
  431. package/dist/adapter/ezsp/driver/consts.js +0 -14
  432. package/dist/adapter/ezsp/driver/consts.js.map +0 -1
  433. package/dist/adapter/ezsp/driver/driver.d.ts +0 -109
  434. package/dist/adapter/ezsp/driver/driver.d.ts.map +0 -1
  435. package/dist/adapter/ezsp/driver/driver.js +0 -796
  436. package/dist/adapter/ezsp/driver/driver.js.map +0 -1
  437. package/dist/adapter/ezsp/driver/ezsp.d.ts +0 -106
  438. package/dist/adapter/ezsp/driver/ezsp.d.ts.map +0 -1
  439. package/dist/adapter/ezsp/driver/ezsp.js +0 -664
  440. package/dist/adapter/ezsp/driver/ezsp.js.map +0 -1
  441. package/dist/adapter/ezsp/driver/frame.d.ts +0 -40
  442. package/dist/adapter/ezsp/driver/frame.d.ts.map +0 -1
  443. package/dist/adapter/ezsp/driver/frame.js +0 -101
  444. package/dist/adapter/ezsp/driver/frame.js.map +0 -1
  445. package/dist/adapter/ezsp/driver/index.d.ts +0 -4
  446. package/dist/adapter/ezsp/driver/index.d.ts.map +0 -1
  447. package/dist/adapter/ezsp/driver/index.js +0 -9
  448. package/dist/adapter/ezsp/driver/index.js.map +0 -1
  449. package/dist/adapter/ezsp/driver/multicast.d.ts +0 -13
  450. package/dist/adapter/ezsp/driver/multicast.d.ts.map +0 -1
  451. package/dist/adapter/ezsp/driver/multicast.js +0 -74
  452. package/dist/adapter/ezsp/driver/multicast.js.map +0 -1
  453. package/dist/adapter/ezsp/driver/parser.d.ts +0 -12
  454. package/dist/adapter/ezsp/driver/parser.d.ts.map +0 -1
  455. package/dist/adapter/ezsp/driver/parser.js +0 -105
  456. package/dist/adapter/ezsp/driver/parser.js.map +0 -1
  457. package/dist/adapter/ezsp/driver/types/basic.d.ts +0 -63
  458. package/dist/adapter/ezsp/driver/types/basic.d.ts.map +0 -1
  459. package/dist/adapter/ezsp/driver/types/basic.js +0 -209
  460. package/dist/adapter/ezsp/driver/types/basic.js.map +0 -1
  461. package/dist/adapter/ezsp/driver/types/index.d.ts +0 -10
  462. package/dist/adapter/ezsp/driver/types/index.d.ts.map +0 -1
  463. package/dist/adapter/ezsp/driver/types/index.js +0 -139
  464. package/dist/adapter/ezsp/driver/types/index.js.map +0 -1
  465. package/dist/adapter/ezsp/driver/types/named.d.ts +0 -1288
  466. package/dist/adapter/ezsp/driver/types/named.d.ts.map +0 -1
  467. package/dist/adapter/ezsp/driver/types/named.js +0 -2330
  468. package/dist/adapter/ezsp/driver/types/named.js.map +0 -1
  469. package/dist/adapter/ezsp/driver/types/struct.d.ts +0 -271
  470. package/dist/adapter/ezsp/driver/types/struct.d.ts.map +0 -1
  471. package/dist/adapter/ezsp/driver/types/struct.js +0 -804
  472. package/dist/adapter/ezsp/driver/types/struct.js.map +0 -1
  473. package/dist/adapter/ezsp/driver/uart.d.ts +0 -49
  474. package/dist/adapter/ezsp/driver/uart.d.ts.map +0 -1
  475. package/dist/adapter/ezsp/driver/uart.js +0 -383
  476. package/dist/adapter/ezsp/driver/uart.js.map +0 -1
  477. package/dist/adapter/ezsp/driver/utils/crc16ccitt.d.ts +0 -3
  478. package/dist/adapter/ezsp/driver/utils/crc16ccitt.d.ts.map +0 -1
  479. package/dist/adapter/ezsp/driver/utils/crc16ccitt.js +0 -56
  480. package/dist/adapter/ezsp/driver/utils/crc16ccitt.js.map +0 -1
  481. package/dist/adapter/ezsp/driver/utils/index.d.ts +0 -20
  482. package/dist/adapter/ezsp/driver/utils/index.d.ts.map +0 -1
  483. package/dist/adapter/ezsp/driver/utils/index.js +0 -73
  484. package/dist/adapter/ezsp/driver/utils/index.js.map +0 -1
  485. package/dist/adapter/ezsp/driver/writer.d.ts +0 -14
  486. package/dist/adapter/ezsp/driver/writer.d.ts.map +0 -1
  487. package/dist/adapter/ezsp/driver/writer.js +0 -83
  488. package/dist/adapter/ezsp/driver/writer.js.map +0 -1
  489. package/dist/adapter/index.d.ts +0 -5
  490. package/dist/adapter/index.d.ts.map +0 -1
  491. package/dist/adapter/index.js +0 -36
  492. package/dist/adapter/index.js.map +0 -1
  493. package/dist/adapter/serialPort.d.ts +0 -14
  494. package/dist/adapter/serialPort.d.ts.map +0 -1
  495. package/dist/adapter/serialPort.js +0 -47
  496. package/dist/adapter/serialPort.js.map +0 -1
  497. package/dist/adapter/serialPortUtils.d.ts +0 -13
  498. package/dist/adapter/serialPortUtils.d.ts.map +0 -1
  499. package/dist/adapter/serialPortUtils.js +0 -19
  500. package/dist/adapter/serialPortUtils.js.map +0 -1
  501. package/dist/adapter/socketPortUtils.d.ts +0 -11
  502. package/dist/adapter/socketPortUtils.d.ts.map +0 -1
  503. package/dist/adapter/socketPortUtils.js +0 -17
  504. package/dist/adapter/socketPortUtils.js.map +0 -1
  505. package/dist/adapter/tstype.d.ts +0 -86
  506. package/dist/adapter/tstype.d.ts.map +0 -1
  507. package/dist/adapter/tstype.js +0 -3
  508. package/dist/adapter/tstype.js.map +0 -1
  509. package/dist/adapter/z-stack/adapter/adapter-backup.d.ts +0 -62
  510. package/dist/adapter/z-stack/adapter/adapter-backup.d.ts.map +0 -1
  511. package/dist/adapter/z-stack/adapter/adapter-backup.js +0 -459
  512. package/dist/adapter/z-stack/adapter/adapter-backup.js.map +0 -1
  513. package/dist/adapter/z-stack/adapter/adapter-nv-memory.d.ts +0 -151
  514. package/dist/adapter/z-stack/adapter/adapter-nv-memory.d.ts.map +0 -1
  515. package/dist/adapter/z-stack/adapter/adapter-nv-memory.js +0 -259
  516. package/dist/adapter/z-stack/adapter/adapter-nv-memory.js.map +0 -1
  517. package/dist/adapter/z-stack/adapter/endpoints.d.ts +0 -12
  518. package/dist/adapter/z-stack/adapter/endpoints.d.ts.map +0 -1
  519. package/dist/adapter/z-stack/adapter/endpoints.js +0 -74
  520. package/dist/adapter/z-stack/adapter/endpoints.js.map +0 -1
  521. package/dist/adapter/z-stack/adapter/index.d.ts +0 -3
  522. package/dist/adapter/z-stack/adapter/index.d.ts.map +0 -1
  523. package/dist/adapter/z-stack/adapter/index.js +0 -9
  524. package/dist/adapter/z-stack/adapter/index.js.map +0 -1
  525. package/dist/adapter/z-stack/adapter/manager.d.ts +0 -84
  526. package/dist/adapter/z-stack/adapter/manager.d.ts.map +0 -1
  527. package/dist/adapter/z-stack/adapter/manager.js +0 -474
  528. package/dist/adapter/z-stack/adapter/manager.js.map +0 -1
  529. package/dist/adapter/z-stack/adapter/tstype.d.ts +0 -7
  530. package/dist/adapter/z-stack/adapter/tstype.d.ts.map +0 -1
  531. package/dist/adapter/z-stack/adapter/tstype.js +0 -10
  532. package/dist/adapter/z-stack/adapter/tstype.js.map +0 -1
  533. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts +0 -86
  534. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts.map +0 -1
  535. package/dist/adapter/z-stack/adapter/zStackAdapter.js +0 -912
  536. package/dist/adapter/z-stack/adapter/zStackAdapter.js.map +0 -1
  537. package/dist/adapter/z-stack/constants/af.d.ts +0 -24
  538. package/dist/adapter/z-stack/constants/af.d.ts.map +0 -1
  539. package/dist/adapter/z-stack/constants/af.js +0 -28
  540. package/dist/adapter/z-stack/constants/af.js.map +0 -1
  541. package/dist/adapter/z-stack/constants/common.d.ts +0 -279
  542. package/dist/adapter/z-stack/constants/common.d.ts.map +0 -1
  543. package/dist/adapter/z-stack/constants/common.js +0 -293
  544. package/dist/adapter/z-stack/constants/common.js.map +0 -1
  545. package/dist/adapter/z-stack/constants/dbg.d.ts +0 -23
  546. package/dist/adapter/z-stack/constants/dbg.d.ts.map +0 -1
  547. package/dist/adapter/z-stack/constants/dbg.js +0 -25
  548. package/dist/adapter/z-stack/constants/dbg.js.map +0 -1
  549. package/dist/adapter/z-stack/constants/index.d.ts +0 -11
  550. package/dist/adapter/z-stack/constants/index.d.ts.map +0 -1
  551. package/dist/adapter/z-stack/constants/index.js +0 -48
  552. package/dist/adapter/z-stack/constants/index.js.map +0 -1
  553. package/dist/adapter/z-stack/constants/mac.d.ts +0 -128
  554. package/dist/adapter/z-stack/constants/mac.d.ts.map +0 -1
  555. package/dist/adapter/z-stack/constants/mac.js +0 -130
  556. package/dist/adapter/z-stack/constants/mac.js.map +0 -1
  557. package/dist/adapter/z-stack/constants/sapi.d.ts +0 -25
  558. package/dist/adapter/z-stack/constants/sapi.d.ts.map +0 -1
  559. package/dist/adapter/z-stack/constants/sapi.js +0 -27
  560. package/dist/adapter/z-stack/constants/sapi.js.map +0 -1
  561. package/dist/adapter/z-stack/constants/sys.d.ts +0 -72
  562. package/dist/adapter/z-stack/constants/sys.d.ts.map +0 -1
  563. package/dist/adapter/z-stack/constants/sys.js +0 -74
  564. package/dist/adapter/z-stack/constants/sys.js.map +0 -1
  565. package/dist/adapter/z-stack/constants/util.d.ts +0 -82
  566. package/dist/adapter/z-stack/constants/util.d.ts.map +0 -1
  567. package/dist/adapter/z-stack/constants/util.js +0 -84
  568. package/dist/adapter/z-stack/constants/util.js.map +0 -1
  569. package/dist/adapter/z-stack/constants/utils.d.ts +0 -5
  570. package/dist/adapter/z-stack/constants/utils.d.ts.map +0 -1
  571. package/dist/adapter/z-stack/constants/utils.js +0 -15
  572. package/dist/adapter/z-stack/constants/utils.js.map +0 -1
  573. package/dist/adapter/z-stack/constants/zdo.d.ts +0 -103
  574. package/dist/adapter/z-stack/constants/zdo.d.ts.map +0 -1
  575. package/dist/adapter/z-stack/constants/zdo.js +0 -105
  576. package/dist/adapter/z-stack/constants/zdo.js.map +0 -1
  577. package/dist/adapter/z-stack/models/index.d.ts +0 -2
  578. package/dist/adapter/z-stack/models/index.d.ts.map +0 -1
  579. package/dist/adapter/z-stack/models/index.js +0 -18
  580. package/dist/adapter/z-stack/models/index.js.map +0 -1
  581. package/dist/adapter/z-stack/models/startup-options.d.ts +0 -13
  582. package/dist/adapter/z-stack/models/startup-options.d.ts.map +0 -1
  583. package/dist/adapter/z-stack/models/startup-options.js +0 -3
  584. package/dist/adapter/z-stack/models/startup-options.js.map +0 -1
  585. package/dist/adapter/z-stack/structs/entries/address-manager-entry.d.ts +0 -24
  586. package/dist/adapter/z-stack/structs/entries/address-manager-entry.d.ts.map +0 -1
  587. package/dist/adapter/z-stack/structs/entries/address-manager-entry.js +0 -46
  588. package/dist/adapter/z-stack/structs/entries/address-manager-entry.js.map +0 -1
  589. package/dist/adapter/z-stack/structs/entries/address-manager-table.d.ts +0 -11
  590. package/dist/adapter/z-stack/structs/entries/address-manager-table.d.ts.map +0 -1
  591. package/dist/adapter/z-stack/structs/entries/address-manager-table.js +0 -23
  592. package/dist/adapter/z-stack/structs/entries/address-manager-table.js.map +0 -1
  593. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.d.ts +0 -11
  594. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.d.ts.map +0 -1
  595. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.js +0 -22
  596. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.js.map +0 -1
  597. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.d.ts +0 -11
  598. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.d.ts.map +0 -1
  599. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.js +0 -24
  600. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.js.map +0 -1
  601. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.d.ts +0 -11
  602. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.d.ts.map +0 -1
  603. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.js +0 -25
  604. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.js.map +0 -1
  605. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.d.ts +0 -11
  606. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.d.ts.map +0 -1
  607. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.js +0 -24
  608. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.js.map +0 -1
  609. package/dist/adapter/z-stack/structs/entries/channel-list.d.ts +0 -9
  610. package/dist/adapter/z-stack/structs/entries/channel-list.d.ts.map +0 -1
  611. package/dist/adapter/z-stack/structs/entries/channel-list.js +0 -16
  612. package/dist/adapter/z-stack/structs/entries/channel-list.js.map +0 -1
  613. package/dist/adapter/z-stack/structs/entries/has-configured.d.ts +0 -9
  614. package/dist/adapter/z-stack/structs/entries/has-configured.d.ts.map +0 -1
  615. package/dist/adapter/z-stack/structs/entries/has-configured.js +0 -17
  616. package/dist/adapter/z-stack/structs/entries/has-configured.js.map +0 -1
  617. package/dist/adapter/z-stack/structs/entries/index.d.ts +0 -17
  618. package/dist/adapter/z-stack/structs/entries/index.d.ts.map +0 -1
  619. package/dist/adapter/z-stack/structs/entries/index.js +0 -33
  620. package/dist/adapter/z-stack/structs/entries/index.js.map +0 -1
  621. package/dist/adapter/z-stack/structs/entries/nib.d.ts +0 -11
  622. package/dist/adapter/z-stack/structs/entries/nib.d.ts.map +0 -1
  623. package/dist/adapter/z-stack/structs/entries/nib.js +0 -69
  624. package/dist/adapter/z-stack/structs/entries/nib.js.map +0 -1
  625. package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.d.ts +0 -11
  626. package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.d.ts.map +0 -1
  627. package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.js +0 -19
  628. package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.js.map +0 -1
  629. package/dist/adapter/z-stack/structs/entries/nwk-key.d.ts +0 -9
  630. package/dist/adapter/z-stack/structs/entries/nwk-key.d.ts.map +0 -1
  631. package/dist/adapter/z-stack/structs/entries/nwk-key.js +0 -16
  632. package/dist/adapter/z-stack/structs/entries/nwk-key.js.map +0 -1
  633. package/dist/adapter/z-stack/structs/entries/nwk-pan-id.d.ts +0 -9
  634. package/dist/adapter/z-stack/structs/entries/nwk-pan-id.d.ts.map +0 -1
  635. package/dist/adapter/z-stack/structs/entries/nwk-pan-id.js +0 -16
  636. package/dist/adapter/z-stack/structs/entries/nwk-pan-id.js.map +0 -1
  637. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.d.ts +0 -14
  638. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.d.ts.map +0 -1
  639. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.js +0 -24
  640. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.js.map +0 -1
  641. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.d.ts +0 -11
  642. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.d.ts.map +0 -1
  643. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.js +0 -23
  644. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.js.map +0 -1
  645. package/dist/adapter/z-stack/structs/entries/security-manager-entry.d.ts +0 -21
  646. package/dist/adapter/z-stack/structs/entries/security-manager-entry.d.ts.map +0 -1
  647. package/dist/adapter/z-stack/structs/entries/security-manager-entry.js +0 -37
  648. package/dist/adapter/z-stack/structs/entries/security-manager-entry.js.map +0 -1
  649. package/dist/adapter/z-stack/structs/entries/security-manager-table.d.ts +0 -11
  650. package/dist/adapter/z-stack/structs/entries/security-manager-table.d.ts.map +0 -1
  651. package/dist/adapter/z-stack/structs/entries/security-manager-table.js +0 -25
  652. package/dist/adapter/z-stack/structs/entries/security-manager-table.js.map +0 -1
  653. package/dist/adapter/z-stack/structs/index.d.ts +0 -5
  654. package/dist/adapter/z-stack/structs/index.d.ts.map +0 -1
  655. package/dist/adapter/z-stack/structs/index.js +0 -21
  656. package/dist/adapter/z-stack/structs/index.js.map +0 -1
  657. package/dist/adapter/z-stack/structs/serializable-memory-object.d.ts +0 -14
  658. package/dist/adapter/z-stack/structs/serializable-memory-object.d.ts.map +0 -1
  659. package/dist/adapter/z-stack/structs/serializable-memory-object.js +0 -3
  660. package/dist/adapter/z-stack/structs/serializable-memory-object.js.map +0 -1
  661. package/dist/adapter/z-stack/structs/struct.d.ts +0 -100
  662. package/dist/adapter/z-stack/structs/struct.d.ts.map +0 -1
  663. package/dist/adapter/z-stack/structs/struct.js +0 -297
  664. package/dist/adapter/z-stack/structs/struct.js.map +0 -1
  665. package/dist/adapter/z-stack/structs/table.d.ts +0 -95
  666. package/dist/adapter/z-stack/structs/table.d.ts.map +0 -1
  667. package/dist/adapter/z-stack/structs/table.js +0 -164
  668. package/dist/adapter/z-stack/structs/table.js.map +0 -1
  669. package/dist/adapter/z-stack/unpi/constants.d.ts +0 -29
  670. package/dist/adapter/z-stack/unpi/constants.d.ts.map +0 -1
  671. package/dist/adapter/z-stack/unpi/constants.js +0 -40
  672. package/dist/adapter/z-stack/unpi/constants.js.map +0 -1
  673. package/dist/adapter/z-stack/unpi/frame.d.ts +0 -17
  674. package/dist/adapter/z-stack/unpi/frame.d.ts.map +0 -1
  675. package/dist/adapter/z-stack/unpi/frame.js +0 -55
  676. package/dist/adapter/z-stack/unpi/frame.js.map +0 -1
  677. package/dist/adapter/z-stack/unpi/index.d.ts +0 -6
  678. package/dist/adapter/z-stack/unpi/index.d.ts.map +0 -1
  679. package/dist/adapter/z-stack/unpi/index.js +0 -38
  680. package/dist/adapter/z-stack/unpi/index.js.map +0 -1
  681. package/dist/adapter/z-stack/unpi/parser.d.ts +0 -13
  682. package/dist/adapter/z-stack/unpi/parser.d.ts.map +0 -1
  683. package/dist/adapter/z-stack/unpi/parser.js +0 -86
  684. package/dist/adapter/z-stack/unpi/parser.js.map +0 -1
  685. package/dist/adapter/z-stack/unpi/writer.d.ts +0 -12
  686. package/dist/adapter/z-stack/unpi/writer.d.ts.map +0 -1
  687. package/dist/adapter/z-stack/unpi/writer.js +0 -55
  688. package/dist/adapter/z-stack/unpi/writer.js.map +0 -1
  689. package/dist/adapter/z-stack/utils/channel-list.d.ts +0 -21
  690. package/dist/adapter/z-stack/utils/channel-list.d.ts.map +0 -1
  691. package/dist/adapter/z-stack/utils/channel-list.js +0 -41
  692. package/dist/adapter/z-stack/utils/channel-list.js.map +0 -1
  693. package/dist/adapter/z-stack/utils/index.d.ts +0 -3
  694. package/dist/adapter/z-stack/utils/index.d.ts.map +0 -1
  695. package/dist/adapter/z-stack/utils/index.js +0 -19
  696. package/dist/adapter/z-stack/utils/index.js.map +0 -1
  697. package/dist/adapter/z-stack/utils/network-options.d.ts +0 -9
  698. package/dist/adapter/z-stack/utils/network-options.d.ts.map +0 -1
  699. package/dist/adapter/z-stack/utils/network-options.js +0 -23
  700. package/dist/adapter/z-stack/utils/network-options.js.map +0 -1
  701. package/dist/adapter/z-stack/znp/buffaloZnp.d.ts +0 -14
  702. package/dist/adapter/z-stack/znp/buffaloZnp.d.ts.map +0 -1
  703. package/dist/adapter/z-stack/znp/buffaloZnp.js +0 -243
  704. package/dist/adapter/z-stack/znp/buffaloZnp.js.map +0 -1
  705. package/dist/adapter/z-stack/znp/definition.d.ts +0 -6
  706. package/dist/adapter/z-stack/znp/definition.d.ts.map +0 -1
  707. package/dist/adapter/z-stack/znp/definition.js +0 -3052
  708. package/dist/adapter/z-stack/znp/definition.js.map +0 -1
  709. package/dist/adapter/z-stack/znp/index.d.ts +0 -4
  710. package/dist/adapter/z-stack/znp/index.d.ts.map +0 -1
  711. package/dist/adapter/z-stack/znp/index.js +0 -11
  712. package/dist/adapter/z-stack/znp/index.js.map +0 -1
  713. package/dist/adapter/z-stack/znp/parameterType.d.ts +0 -23
  714. package/dist/adapter/z-stack/znp/parameterType.d.ts.map +0 -1
  715. package/dist/adapter/z-stack/znp/parameterType.js +0 -26
  716. package/dist/adapter/z-stack/znp/parameterType.js.map +0 -1
  717. package/dist/adapter/z-stack/znp/tstype.d.ts +0 -23
  718. package/dist/adapter/z-stack/znp/tstype.d.ts.map +0 -1
  719. package/dist/adapter/z-stack/znp/tstype.js +0 -3
  720. package/dist/adapter/z-stack/znp/tstype.js.map +0 -1
  721. package/dist/adapter/z-stack/znp/znp.d.ts +0 -47
  722. package/dist/adapter/z-stack/znp/znp.d.ts.map +0 -1
  723. package/dist/adapter/z-stack/znp/znp.js +0 -322
  724. package/dist/adapter/z-stack/znp/znp.js.map +0 -1
  725. package/dist/adapter/z-stack/znp/zpiObject.d.ts +0 -20
  726. package/dist/adapter/z-stack/znp/zpiObject.d.ts.map +0 -1
  727. package/dist/adapter/z-stack/znp/zpiObject.js +0 -103
  728. package/dist/adapter/z-stack/znp/zpiObject.js.map +0 -1
  729. package/dist/adapter/zigate/adapter/index.d.ts +0 -3
  730. package/dist/adapter/zigate/adapter/index.d.ts.map +0 -1
  731. package/dist/adapter/zigate/adapter/index.js +0 -11
  732. package/dist/adapter/zigate/adapter/index.js.map +0 -1
  733. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts +0 -72
  734. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts.map +0 -1
  735. package/dist/adapter/zigate/adapter/zigateAdapter.js +0 -681
  736. package/dist/adapter/zigate/adapter/zigateAdapter.js.map +0 -1
  737. package/dist/adapter/zigate/driver/buffaloZiGate.d.ts +0 -18
  738. package/dist/adapter/zigate/driver/buffaloZiGate.d.ts.map +0 -1
  739. package/dist/adapter/zigate/driver/buffaloZiGate.js +0 -190
  740. package/dist/adapter/zigate/driver/buffaloZiGate.js.map +0 -1
  741. package/dist/adapter/zigate/driver/commandType.d.ts +0 -43
  742. package/dist/adapter/zigate/driver/commandType.d.ts.map +0 -1
  743. package/dist/adapter/zigate/driver/commandType.js +0 -390
  744. package/dist/adapter/zigate/driver/commandType.js.map +0 -1
  745. package/dist/adapter/zigate/driver/constants.d.ts +0 -277
  746. package/dist/adapter/zigate/driver/constants.d.ts.map +0 -1
  747. package/dist/adapter/zigate/driver/constants.js +0 -372
  748. package/dist/adapter/zigate/driver/constants.js.map +0 -1
  749. package/dist/adapter/zigate/driver/frame.d.ts +0 -27
  750. package/dist/adapter/zigate/driver/frame.d.ts.map +0 -1
  751. package/dist/adapter/zigate/driver/frame.js +0 -173
  752. package/dist/adapter/zigate/driver/frame.js.map +0 -1
  753. package/dist/adapter/zigate/driver/messageType.d.ts +0 -13
  754. package/dist/adapter/zigate/driver/messageType.d.ts.map +0 -1
  755. package/dist/adapter/zigate/driver/messageType.js +0 -284
  756. package/dist/adapter/zigate/driver/messageType.js.map +0 -1
  757. package/dist/adapter/zigate/driver/parameterType.d.ts +0 -28
  758. package/dist/adapter/zigate/driver/parameterType.d.ts.map +0 -1
  759. package/dist/adapter/zigate/driver/parameterType.js +0 -33
  760. package/dist/adapter/zigate/driver/parameterType.js.map +0 -1
  761. package/dist/adapter/zigate/driver/ziGateObject.d.ts +0 -24
  762. package/dist/adapter/zigate/driver/ziGateObject.d.ts.map +0 -1
  763. package/dist/adapter/zigate/driver/ziGateObject.js +0 -111
  764. package/dist/adapter/zigate/driver/ziGateObject.js.map +0 -1
  765. package/dist/adapter/zigate/driver/zigate.d.ts +0 -50
  766. package/dist/adapter/zigate/driver/zigate.d.ts.map +0 -1
  767. package/dist/adapter/zigate/driver/zigate.js +0 -289
  768. package/dist/adapter/zigate/driver/zigate.js.map +0 -1
  769. package/dist/buffalo/buffalo.d.ts +0 -55
  770. package/dist/buffalo/buffalo.d.ts.map +0 -1
  771. package/dist/buffalo/buffalo.js +0 -230
  772. package/dist/buffalo/buffalo.js.map +0 -1
  773. package/dist/buffalo/index.d.ts +0 -3
  774. package/dist/buffalo/index.d.ts.map +0 -1
  775. package/dist/buffalo/index.js +0 -9
  776. package/dist/buffalo/index.js.map +0 -1
  777. package/dist/controller/controller.d.ts +0 -119
  778. package/dist/controller/controller.d.ts.map +0 -1
  779. package/dist/controller/controller.js +0 -700
  780. package/dist/controller/controller.js.map +0 -1
  781. package/dist/controller/database.d.ts +0 -20
  782. package/dist/controller/database.d.ts.map +0 -1
  783. package/dist/controller/database.js +0 -94
  784. package/dist/controller/database.js.map +0 -1
  785. package/dist/controller/events.d.ts +0 -59
  786. package/dist/controller/events.d.ts.map +0 -1
  787. package/dist/controller/events.js +0 -113
  788. package/dist/controller/events.js.map +0 -1
  789. package/dist/controller/greenPower.d.ts +0 -14
  790. package/dist/controller/greenPower.d.ts.map +0 -1
  791. package/dist/controller/greenPower.js +0 -216
  792. package/dist/controller/greenPower.js.map +0 -1
  793. package/dist/controller/helpers/index.d.ts +0 -3
  794. package/dist/controller/helpers/index.d.ts.map +0 -1
  795. package/dist/controller/helpers/index.js +0 -29
  796. package/dist/controller/helpers/index.js.map +0 -1
  797. package/dist/controller/helpers/request.d.ts +0 -22
  798. package/dist/controller/helpers/request.d.ts.map +0 -1
  799. package/dist/controller/helpers/request.js +0 -78
  800. package/dist/controller/helpers/request.js.map +0 -1
  801. package/dist/controller/helpers/requestQueue.d.ts +0 -13
  802. package/dist/controller/helpers/requestQueue.d.ts.map +0 -1
  803. package/dist/controller/helpers/requestQueue.js +0 -106
  804. package/dist/controller/helpers/requestQueue.js.map +0 -1
  805. package/dist/controller/helpers/zclFrameConverter.d.ts +0 -9
  806. package/dist/controller/helpers/zclFrameConverter.d.ts.map +0 -1
  807. package/dist/controller/helpers/zclFrameConverter.js +0 -70
  808. package/dist/controller/helpers/zclFrameConverter.js.map +0 -1
  809. package/dist/controller/helpers/zclTransactionSequenceNumber.d.ts +0 -6
  810. package/dist/controller/helpers/zclTransactionSequenceNumber.d.ts.map +0 -1
  811. package/dist/controller/helpers/zclTransactionSequenceNumber.js +0 -14
  812. package/dist/controller/helpers/zclTransactionSequenceNumber.js.map +0 -1
  813. package/dist/controller/index.d.ts +0 -6
  814. package/dist/controller/index.d.ts.map +0 -1
  815. package/dist/controller/index.js +0 -9
  816. package/dist/controller/index.js.map +0 -1
  817. package/dist/controller/model/device.d.ts +0 -140
  818. package/dist/controller/model/device.d.ts.map +0 -1
  819. package/dist/controller/model/device.js +0 -746
  820. package/dist/controller/model/device.js.map +0 -1
  821. package/dist/controller/model/endpoint.d.ts +0 -134
  822. package/dist/controller/model/endpoint.d.ts.map +0 -1
  823. package/dist/controller/model/endpoint.js +0 -667
  824. package/dist/controller/model/endpoint.js.map +0 -1
  825. package/dist/controller/model/entity.d.ts +0 -15
  826. package/dist/controller/model/entity.d.ts.map +0 -1
  827. package/dist/controller/model/entity.js +0 -27
  828. package/dist/controller/model/entity.js.map +0 -1
  829. package/dist/controller/model/group.d.ts +0 -39
  830. package/dist/controller/model/group.d.ts.map +0 -1
  831. package/dist/controller/model/group.js +0 -226
  832. package/dist/controller/model/group.js.map +0 -1
  833. package/dist/controller/model/index.d.ts +0 -6
  834. package/dist/controller/model/index.d.ts.map +0 -1
  835. package/dist/controller/model/index.js +0 -15
  836. package/dist/controller/model/index.js.map +0 -1
  837. package/dist/controller/model/konnextConfig.d.ts +0 -7
  838. package/dist/controller/model/konnextConfig.d.ts.map +0 -1
  839. package/dist/controller/model/konnextConfig.js +0 -3
  840. package/dist/controller/model/konnextConfig.js.map +0 -1
  841. package/dist/controller/touchlink.d.ts +0 -20
  842. package/dist/controller/touchlink.d.ts.map +0 -1
  843. package/dist/controller/touchlink.js +0 -157
  844. package/dist/controller/touchlink.js.map +0 -1
  845. package/dist/controller/tstype.d.ts +0 -21
  846. package/dist/controller/tstype.d.ts.map +0 -1
  847. package/dist/controller/tstype.js +0 -9
  848. package/dist/controller/tstype.js.map +0 -1
  849. package/dist/index.d.ts +0 -6
  850. package/dist/index.d.ts.map +0 -1
  851. package/dist/index.js +0 -37
  852. package/dist/index.js.map +0 -1
  853. package/dist/models/backup-storage-legacy.d.ts +0 -27
  854. package/dist/models/backup-storage-legacy.d.ts.map +0 -1
  855. package/dist/models/backup-storage-legacy.js +0 -3
  856. package/dist/models/backup-storage-legacy.js.map +0 -1
  857. package/dist/models/backup-storage-unified.d.ts +0 -50
  858. package/dist/models/backup-storage-unified.d.ts.map +0 -1
  859. package/dist/models/backup-storage-unified.js +0 -3
  860. package/dist/models/backup-storage-unified.js.map +0 -1
  861. package/dist/models/backup.d.ts +0 -38
  862. package/dist/models/backup.d.ts.map +0 -1
  863. package/dist/models/backup.js +0 -3
  864. package/dist/models/backup.js.map +0 -1
  865. package/dist/models/index.d.ts +0 -5
  866. package/dist/models/index.d.ts.map +0 -1
  867. package/dist/models/index.js +0 -21
  868. package/dist/models/index.js.map +0 -1
  869. package/dist/models/network-options.d.ts +0 -13
  870. package/dist/models/network-options.d.ts.map +0 -1
  871. package/dist/models/network-options.js +0 -3
  872. package/dist/models/network-options.js.map +0 -1
  873. package/dist/utils/aes.d.ts +0 -40
  874. package/dist/utils/aes.d.ts.map +0 -1
  875. package/dist/utils/aes.js +0 -198
  876. package/dist/utils/aes.js.map +0 -1
  877. package/dist/utils/assertString.d.ts +0 -3
  878. package/dist/utils/assertString.d.ts.map +0 -1
  879. package/dist/utils/assertString.js +0 -9
  880. package/dist/utils/assertString.js.map +0 -1
  881. package/dist/utils/backup.d.ts +0 -21
  882. package/dist/utils/backup.d.ts.map +0 -1
  883. package/dist/utils/backup.js +0 -190
  884. package/dist/utils/backup.js.map +0 -1
  885. package/dist/utils/equalsPartial.d.ts +0 -3
  886. package/dist/utils/equalsPartial.d.ts.map +0 -1
  887. package/dist/utils/equalsPartial.js +0 -12
  888. package/dist/utils/equalsPartial.js.map +0 -1
  889. package/dist/utils/index.d.ts +0 -10
  890. package/dist/utils/index.d.ts.map +0 -1
  891. package/dist/utils/index.js +0 -46
  892. package/dist/utils/index.js.map +0 -1
  893. package/dist/utils/isNumberArray.d.ts +0 -3
  894. package/dist/utils/isNumberArray.d.ts.map +0 -1
  895. package/dist/utils/isNumberArray.js +0 -7
  896. package/dist/utils/isNumberArray.js.map +0 -1
  897. package/dist/utils/logger.d.ts +0 -9
  898. package/dist/utils/logger.d.ts.map +0 -1
  899. package/dist/utils/logger.js +0 -14
  900. package/dist/utils/logger.js.map +0 -1
  901. package/dist/utils/queue.d.ts +0 -12
  902. package/dist/utils/queue.d.ts.map +0 -1
  903. package/dist/utils/queue.js +0 -62
  904. package/dist/utils/queue.js.map +0 -1
  905. package/dist/utils/realpathSync.d.ts +0 -3
  906. package/dist/utils/realpathSync.d.ts.map +0 -1
  907. package/dist/utils/realpathSync.js +0 -13
  908. package/dist/utils/realpathSync.js.map +0 -1
  909. package/dist/utils/wait.d.ts +0 -3
  910. package/dist/utils/wait.d.ts.map +0 -1
  911. package/dist/utils/wait.js +0 -9
  912. package/dist/utils/wait.js.map +0 -1
  913. package/dist/utils/waitress.d.ts +0 -22
  914. package/dist/utils/waitress.d.ts.map +0 -1
  915. package/dist/utils/waitress.js +0 -69
  916. package/dist/utils/waitress.js.map +0 -1
  917. package/dist/zspec/consts.d.ts +0 -60
  918. package/dist/zspec/consts.d.ts.map +0 -1
  919. package/dist/zspec/consts.js +0 -64
  920. package/dist/zspec/consts.js.map +0 -1
  921. package/dist/zspec/enums.d.ts +0 -19
  922. package/dist/zspec/enums.d.ts.map +0 -1
  923. package/dist/zspec/enums.js +0 -28
  924. package/dist/zspec/enums.js.map +0 -1
  925. package/dist/zspec/index.d.ts +0 -4
  926. package/dist/zspec/index.d.ts.map +0 -1
  927. package/dist/zspec/index.js +0 -43
  928. package/dist/zspec/index.js.map +0 -1
  929. package/dist/zspec/tstypes.d.ts +0 -19
  930. package/dist/zspec/tstypes.d.ts.map +0 -1
  931. package/dist/zspec/tstypes.js +0 -3
  932. package/dist/zspec/tstypes.js.map +0 -1
  933. package/dist/zspec/utils.d.ts +0 -14
  934. package/dist/zspec/utils.d.ts.map +0 -1
  935. package/dist/zspec/utils.js +0 -29
  936. package/dist/zspec/utils.js.map +0 -1
  937. package/dist/zspec/zcl/buffaloZcl.d.ts +0 -55
  938. package/dist/zspec/zcl/buffaloZcl.d.ts.map +0 -1
  939. package/dist/zspec/zcl/buffaloZcl.js +0 -929
  940. package/dist/zspec/zcl/buffaloZcl.js.map +0 -1
  941. package/dist/zspec/zcl/definition/cluster.d.ts +0 -3
  942. package/dist/zspec/zcl/definition/cluster.d.ts.map +0 -1
  943. package/dist/zspec/zcl/definition/cluster.js +0 -5500
  944. package/dist/zspec/zcl/definition/cluster.js.map +0 -1
  945. package/dist/zspec/zcl/definition/consts.d.ts +0 -9
  946. package/dist/zspec/zcl/definition/consts.d.ts.map +0 -1
  947. package/dist/zspec/zcl/definition/consts.js +0 -27
  948. package/dist/zspec/zcl/definition/consts.js.map +0 -1
  949. package/dist/zspec/zcl/definition/enums.d.ts +0 -177
  950. package/dist/zspec/zcl/definition/enums.d.ts.map +0 -1
  951. package/dist/zspec/zcl/definition/enums.js +0 -187
  952. package/dist/zspec/zcl/definition/enums.js.map +0 -1
  953. package/dist/zspec/zcl/definition/foundation.d.ts +0 -11
  954. package/dist/zspec/zcl/definition/foundation.d.ts.map +0 -1
  955. package/dist/zspec/zcl/definition/foundation.js +0 -241
  956. package/dist/zspec/zcl/definition/foundation.js.map +0 -1
  957. package/dist/zspec/zcl/definition/manufacturerCode.d.ts +0 -727
  958. package/dist/zspec/zcl/definition/manufacturerCode.d.ts.map +0 -1
  959. package/dist/zspec/zcl/definition/manufacturerCode.js +0 -733
  960. package/dist/zspec/zcl/definition/manufacturerCode.js.map +0 -1
  961. package/dist/zspec/zcl/definition/status.d.ts +0 -69
  962. package/dist/zspec/zcl/definition/status.d.ts.map +0 -1
  963. package/dist/zspec/zcl/definition/status.js +0 -74
  964. package/dist/zspec/zcl/definition/status.js.map +0 -1
  965. package/dist/zspec/zcl/definition/tstype.d.ts +0 -114
  966. package/dist/zspec/zcl/definition/tstype.d.ts.map +0 -1
  967. package/dist/zspec/zcl/definition/tstype.js +0 -4
  968. package/dist/zspec/zcl/definition/tstype.js.map +0 -1
  969. package/dist/zspec/zcl/index.d.ts +0 -11
  970. package/dist/zspec/zcl/index.d.ts.map +0 -1
  971. package/dist/zspec/zcl/index.js +0 -47
  972. package/dist/zspec/zcl/index.js.map +0 -1
  973. package/dist/zspec/zcl/utils.d.ts +0 -7
  974. package/dist/zspec/zcl/utils.d.ts.map +0 -1
  975. package/dist/zspec/zcl/utils.js +0 -234
  976. package/dist/zspec/zcl/utils.js.map +0 -1
  977. package/dist/zspec/zcl/zclFrame.d.ts +0 -36
  978. package/dist/zspec/zcl/zclFrame.d.ts.map +0 -1
  979. package/dist/zspec/zcl/zclFrame.js +0 -304
  980. package/dist/zspec/zcl/zclFrame.js.map +0 -1
  981. package/dist/zspec/zcl/zclHeader.d.ts +0 -17
  982. package/dist/zspec/zcl/zclHeader.d.ts.map +0 -1
  983. package/dist/zspec/zcl/zclHeader.js +0 -88
  984. package/dist/zspec/zcl/zclHeader.js.map +0 -1
  985. package/dist/zspec/zcl/zclStatusError.d.ts +0 -6
  986. package/dist/zspec/zcl/zclStatusError.d.ts.map +0 -1
  987. package/dist/zspec/zcl/zclStatusError.js +0 -13
  988. package/dist/zspec/zcl/zclStatusError.js.map +0 -1
  989. package/dist/zspec/zdo/buffaloZdo.d.ts +0 -438
  990. package/dist/zspec/zdo/buffaloZdo.d.ts.map +0 -1
  991. package/dist/zspec/zdo/buffaloZdo.js +0 -1892
  992. package/dist/zspec/zdo/buffaloZdo.js.map +0 -1
  993. package/dist/zspec/zdo/definition/clusters.d.ts +0 -624
  994. package/dist/zspec/zdo/definition/clusters.d.ts.map +0 -1
  995. package/dist/zspec/zdo/definition/clusters.js +0 -687
  996. package/dist/zspec/zdo/definition/clusters.js.map +0 -1
  997. package/dist/zspec/zdo/definition/consts.d.ts +0 -13
  998. package/dist/zspec/zdo/definition/consts.d.ts.map +0 -1
  999. package/dist/zspec/zdo/definition/consts.js +0 -16
  1000. package/dist/zspec/zdo/definition/consts.js.map +0 -1
  1001. package/dist/zspec/zdo/definition/enums.d.ts +0 -75
  1002. package/dist/zspec/zdo/definition/enums.d.ts.map +0 -1
  1003. package/dist/zspec/zdo/definition/enums.js +0 -97
  1004. package/dist/zspec/zdo/definition/enums.js.map +0 -1
  1005. package/dist/zspec/zdo/definition/status.d.ts +0 -99
  1006. package/dist/zspec/zdo/definition/status.d.ts.map +0 -1
  1007. package/dist/zspec/zdo/definition/status.js +0 -109
  1008. package/dist/zspec/zdo/definition/status.js.map +0 -1
  1009. package/dist/zspec/zdo/definition/tstypes.d.ts +0 -787
  1010. package/dist/zspec/zdo/definition/tstypes.d.ts.map +0 -1
  1011. package/dist/zspec/zdo/definition/tstypes.js +0 -3
  1012. package/dist/zspec/zdo/definition/tstypes.js.map +0 -1
  1013. package/dist/zspec/zdo/index.d.ts +0 -7
  1014. package/dist/zspec/zdo/index.d.ts.map +0 -1
  1015. package/dist/zspec/zdo/index.js +0 -39
  1016. package/dist/zspec/zdo/index.js.map +0 -1
  1017. package/dist/zspec/zdo/utils.d.ts +0 -25
  1018. package/dist/zspec/zdo/utils.d.ts.map +0 -1
  1019. package/dist/zspec/zdo/utils.js +0 -75
  1020. package/dist/zspec/zdo/utils.js.map +0 -1
  1021. package/dist/zspec/zdo/zdoStatusError.d.ts +0 -6
  1022. package/dist/zspec/zdo/zdoStatusError.d.ts.map +0 -1
  1023. package/dist/zspec/zdo/zdoStatusError.js +0 -13
  1024. package/dist/zspec/zdo/zdoStatusError.js.map +0 -1
  1025. package/typedoc-tsconfig.json +0 -44
@@ -0,0 +1,3332 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import {performance} from "node:perf_hooks";
5
+ import {afterEach, beforeEach, describe, expect, it, vi} from "vitest";
6
+ import type {Adapter} from "../src/adapter";
7
+ import Database from "../src/controller/database";
8
+ import * as otaHelpers from "../src/controller/helpers/ota";
9
+ import {Device, Entity} from "../src/controller/model";
10
+ import {InterviewState} from "../src/controller/model/device";
11
+ import type Endpoint from "../src/controller/model/endpoint";
12
+ import type {OtaDataSettings, OtaImage, OtaSource, ZigbeeOtaImageMeta} from "../src/controller/tstype";
13
+ import {logger} from "../src/utils/logger";
14
+ import * as Zcl from "../src/zspec/zcl";
15
+ import type {TClusterCommandPayload} from "../src/zspec/zcl/definition/clusters-types";
16
+
17
+ const tempDirs: string[] = [];
18
+
19
+ const OTA_CLUSTER_ID = Zcl.Clusters.genOta.ID;
20
+ const QUERY_NEXT_IMAGE_REQUEST_ID = Zcl.Clusters.genOta.commands.queryNextImageRequest.ID;
21
+ const UPGRADE_END_REQUEST_ID = Zcl.Clusters.genOta.commands.upgradeEndRequest.ID;
22
+ const IMAGE_BLOCK_REQUEST_ID = Zcl.Clusters.genOta.commands.imageBlockRequest.ID;
23
+ const IMAGE_PAGE_REQUEST_ID = Zcl.Clusters.genOta.commands.imagePageRequest.ID;
24
+
25
+ type OtaDeviceBehavior = {
26
+ baseSize: number;
27
+ /** Mimick the device sending image page requests instead of image block requests */
28
+ usePageRequests?: boolean;
29
+ pageSize?: number;
30
+ /** Mimick the device sending non-value (0xff) as `maximumDataSize` */
31
+ useNonValueDataSize?: boolean;
32
+ /** Mimick the device stopping image block/page requests at specified block (i.e. stalling) */
33
+ stopAfterBlocks?: number;
34
+ /** Mimick the device sending out-of-order offset for block/page request, block 2 swapped with block 3 */
35
+ shuffleOffsets?: boolean;
36
+ /**
37
+ * TODO: implement this
38
+ * Mimick the device sending block/page request with an offset that is lower or higher than expected flow of "previous offset+data size" at block 2:
39
+ * - normal flow would be something like: block1=[offset=0, dataSize=50], block2=[offset=50, dataSize=50], block3=[offset=100, dataSize=50]
40
+ * - with this block2 has this applied to offset: block1=[offset=0, dataSize=50], block2=[offset=(dataSize-misalignedOffset), dataSize=50], block3=[offset=(dataSize*2-misalignedOffset), dataSize=50]
41
+ */
42
+ misalignedOffset?: number;
43
+ /** Mimick failing block 2 response (mimick device sending new image block/page request for same offset) */
44
+ failBlockResponse?: boolean;
45
+ /** Mimick the device sending or not of `upgradeEndRequest` (at end of block/page requests, or as specified by other behaviors) */
46
+ sendUpgradeEnd?: boolean;
47
+ /** Mimick the device sending that specific status in `upgradeEndRequest` */
48
+ upgradeEndStatus?: Zcl.Status;
49
+ /** Mimick the device sending `upgradeEndRequest` after that specific block/page request */
50
+ upgradeEndAfterBlocks?: number;
51
+ };
52
+
53
+ const DEFAULT_BEHAVIOR: Readonly<OtaDeviceBehavior> = {
54
+ baseSize: 64,
55
+ sendUpgradeEnd: true,
56
+ upgradeEndStatus: Zcl.Status.SUCCESS,
57
+ };
58
+
59
+ const createEndpointStub = () => {
60
+ const commandResponse = vi.fn().mockResolvedValue(undefined);
61
+ const defaultResponse = vi.fn().mockResolvedValue(undefined);
62
+ const supportsOutputCluster = vi.fn((clusterKey: number | string) => clusterKey === OTA_CLUSTER_ID || clusterKey === "genOta");
63
+
64
+ return {ID: 1, supportsOutputCluster, commandResponse, defaultResponse} as unknown as Endpoint;
65
+ };
66
+
67
+ const createOtaDeviceWaitFor = (
68
+ endpoint: Endpoint,
69
+ image: OtaImage,
70
+ current: TClusterCommandPayload<"genOta", "queryNextImageRequest">,
71
+ behavior: OtaDeviceBehavior,
72
+ ): Adapter["waitFor"] => {
73
+ const settings = {...DEFAULT_BEHAVIOR, ...behavior};
74
+ let nextOffset = 0;
75
+ let blocksServed = 0;
76
+ let blockTsn = 2;
77
+ let upgradeEndResolver: ((frame: Zcl.Frame) => void) | undefined;
78
+ let upgradeEndTimer: NodeJS.Timeout | undefined;
79
+ let upgradeEndSent = false;
80
+ let repeatLastBlock = false;
81
+ let previousBlock: {offset: number; size: number} | undefined;
82
+
83
+ const pendingPromise = (): ReturnType<Adapter["waitFor"]>["promise"] => new Promise<never>(() => {}) as never;
84
+ const makeFrame = (
85
+ commandId: number,
86
+ payload: TClusterCommandPayload<"genOta", "imageBlockRequest" | "imagePageRequest" | "upgradeEndRequest" | "queryNextImageRequest">,
87
+ tsn: number,
88
+ ) => Zcl.Frame.create(Zcl.FrameType.SPECIFIC, Zcl.Direction.CLIENT_TO_SERVER, true, undefined, tsn, commandId, OTA_CLUSTER_ID, payload, {});
89
+
90
+ const maybeScheduleUpgradeEnd = () => {
91
+ if (!settings.sendUpgradeEnd || upgradeEndSent || !upgradeEndResolver) {
92
+ return;
93
+ }
94
+
95
+ upgradeEndSent = true;
96
+
97
+ queueMicrotask(() => {
98
+ const frame = makeFrame(
99
+ UPGRADE_END_REQUEST_ID,
100
+ {
101
+ status: settings.upgradeEndStatus ?? Zcl.Status.SUCCESS,
102
+ manufacturerCode: image.header.manufacturerCode,
103
+ imageType: image.header.imageType,
104
+ fileVersion: image.header.fileVersion,
105
+ },
106
+ 250,
107
+ );
108
+
109
+ upgradeEndResolver?.(frame);
110
+ });
111
+ };
112
+
113
+ if (settings.failBlockResponse) {
114
+ endpoint.commandResponse = vi.fn((_clusterKey, commandKey, _payload, _options, _transactionSequenceNumber) => {
115
+ if (commandKey === "imageBlockResponse") {
116
+ if (blocksServed === 1) {
117
+ repeatLastBlock = true;
118
+ return Promise.reject(new Error("block-fail"));
119
+ }
120
+
121
+ return Promise.resolve();
122
+ }
123
+
124
+ return Promise.resolve();
125
+ });
126
+ }
127
+
128
+ const handleBlockRequest = (endpointId: number, transactionSequenceNumber: number | undefined, networkAddress: number) => {
129
+ if (repeatLastBlock && previousBlock) {
130
+ repeatLastBlock = false;
131
+ // revert offset to previous block
132
+ ({offset: nextOffset} = previousBlock);
133
+ }
134
+
135
+ if (settings.stopAfterBlocks !== undefined && blocksServed >= settings.stopAfterBlocks) {
136
+ return {
137
+ promise: Promise.reject(new Error("device stopped requesting blocks")),
138
+ cancel: () => {},
139
+ };
140
+ }
141
+
142
+ let fileOffset = nextOffset;
143
+
144
+ if (settings.shuffleOffsets) {
145
+ if (blocksServed === 1) {
146
+ fileOffset = settings.baseSize * 2;
147
+ } else if (blocksServed === 2) {
148
+ fileOffset = settings.baseSize;
149
+ }
150
+ } else if (settings.misalignedOffset) {
151
+ if (blocksServed === 1) {
152
+ fileOffset = settings.baseSize + settings.misalignedOffset;
153
+ }
154
+ }
155
+
156
+ const remaining = image.header.totalImageSize - fileOffset;
157
+
158
+ if (remaining <= 0) {
159
+ maybeScheduleUpgradeEnd();
160
+
161
+ return {
162
+ promise: Promise.reject(new Error("all blocks sent")),
163
+ cancel: () => {},
164
+ };
165
+ }
166
+
167
+ const payload: TClusterCommandPayload<"genOta", "imageBlockRequest"> = {
168
+ fieldControl: 0,
169
+ manufacturerCode: image.header.manufacturerCode,
170
+ imageType: image.header.imageType,
171
+ fileVersion: image.header.fileVersion,
172
+ fileOffset,
173
+ maximumDataSize: settings.useNonValueDataSize ? Number.NaN : settings.baseSize,
174
+ };
175
+
176
+ blocksServed += 1;
177
+ const nextTsn = transactionSequenceNumber ?? blockTsn;
178
+ blockTsn = (blockTsn + 1) & 0xff;
179
+ const frame = makeFrame(IMAGE_BLOCK_REQUEST_ID, payload, nextTsn);
180
+ nextOffset = Math.max(nextOffset, fileOffset + settings.baseSize);
181
+ previousBlock = {offset: fileOffset, size: settings.baseSize};
182
+
183
+ if (settings.upgradeEndAfterBlocks !== undefined && blocksServed >= settings.upgradeEndAfterBlocks) {
184
+ maybeScheduleUpgradeEnd();
185
+ } else if (nextOffset >= image.header.totalImageSize && settings.sendUpgradeEnd) {
186
+ maybeScheduleUpgradeEnd();
187
+ }
188
+
189
+ return {
190
+ promise: Promise.resolve({
191
+ clusterID: OTA_CLUSTER_ID,
192
+ header: frame.header,
193
+ data: frame.toBuffer(),
194
+ endpoint: endpointId,
195
+ linkquality: 0,
196
+ address: networkAddress,
197
+ groupID: 0,
198
+ wasBroadcast: false,
199
+ destinationEndpoint: endpointId,
200
+ }),
201
+ cancel: () => {},
202
+ };
203
+ };
204
+
205
+ const handlePageRequest = (endpointId: number, transactionSequenceNumber: number | undefined, networkAddress: number) => {
206
+ const remaining = image.header.totalImageSize - nextOffset;
207
+
208
+ if (remaining <= 0) {
209
+ maybeScheduleUpgradeEnd();
210
+
211
+ return {
212
+ promise: pendingPromise(),
213
+ cancel: () => {},
214
+ };
215
+ }
216
+
217
+ const pageSize = Math.min(settings.pageSize ?? settings.baseSize * 16, remaining);
218
+ const payload: TClusterCommandPayload<"genOta", "imagePageRequest"> = {
219
+ fieldControl: 0,
220
+ manufacturerCode: image.header.manufacturerCode,
221
+ imageType: image.header.imageType,
222
+ fileVersion: image.header.fileVersion,
223
+ fileOffset: nextOffset,
224
+ maximumDataSize: settings.baseSize,
225
+ pageSize,
226
+ responseSpacing: 0,
227
+ };
228
+
229
+ const nextTsn = transactionSequenceNumber ?? blockTsn;
230
+ blockTsn = (blockTsn + 1) & 0xff;
231
+ const frame = makeFrame(IMAGE_PAGE_REQUEST_ID, payload, nextTsn);
232
+ nextOffset = Math.max(nextOffset, payload.fileOffset + pageSize);
233
+ previousBlock = {offset: payload.fileOffset, size: pageSize};
234
+
235
+ if (nextOffset >= image.header.totalImageSize) {
236
+ maybeScheduleUpgradeEnd();
237
+ }
238
+
239
+ return {
240
+ promise: Promise.resolve({
241
+ clusterID: OTA_CLUSTER_ID,
242
+ header: frame.header,
243
+ data: frame.toBuffer(),
244
+ endpoint: endpointId,
245
+ linkquality: 0,
246
+ address: networkAddress,
247
+ groupID: 0,
248
+ wasBroadcast: false,
249
+ destinationEndpoint: endpointId,
250
+ }),
251
+ cancel: () => {},
252
+ };
253
+ };
254
+
255
+ const waitFor: Adapter["waitFor"] = (
256
+ networkAddress,
257
+ endpointId,
258
+ _frameType,
259
+ _direction,
260
+ transactionSequenceNumber,
261
+ clusterID,
262
+ commandId,
263
+ timeout,
264
+ ) => {
265
+ const fail = (message: string) => ({
266
+ promise: Promise.reject(new Error(message)),
267
+ cancel: () => {},
268
+ });
269
+
270
+ if (clusterID !== OTA_CLUSTER_ID) {
271
+ return fail("unexpected cluster");
272
+ }
273
+
274
+ if (commandId === QUERY_NEXT_IMAGE_REQUEST_ID) {
275
+ const frame = makeFrame(commandId, current, transactionSequenceNumber ?? 1);
276
+
277
+ return {
278
+ promise: Promise.resolve({
279
+ clusterID,
280
+ header: frame.header,
281
+ data: frame.toBuffer(),
282
+ endpoint: endpointId,
283
+ linkquality: 0,
284
+ address: networkAddress!,
285
+ groupID: 0,
286
+ wasBroadcast: false,
287
+ destinationEndpoint: endpointId,
288
+ }),
289
+ cancel: () => {},
290
+ };
291
+ }
292
+
293
+ if (commandId === UPGRADE_END_REQUEST_ID) {
294
+ const promise = new Promise<Zcl.Frame>((resolve, reject) => {
295
+ upgradeEndResolver = resolve;
296
+
297
+ if (timeout && timeout > 0) {
298
+ upgradeEndTimer = setTimeout(() => reject(new Error("timeout")), timeout);
299
+ }
300
+ });
301
+
302
+ return {
303
+ promise: promise.then((frame) => ({
304
+ clusterID,
305
+ header: frame.header,
306
+ data: frame.toBuffer(),
307
+ endpoint: endpointId,
308
+ linkquality: 0,
309
+ address: networkAddress!,
310
+ groupID: 0,
311
+ wasBroadcast: false,
312
+ destinationEndpoint: endpointId,
313
+ })),
314
+ cancel: () => {
315
+ if (upgradeEndTimer) {
316
+ clearTimeout(upgradeEndTimer);
317
+ upgradeEndTimer = undefined;
318
+ }
319
+ upgradeEndResolver = undefined;
320
+ },
321
+ };
322
+ }
323
+
324
+ if (commandId === IMAGE_PAGE_REQUEST_ID) {
325
+ if (!settings.usePageRequests) {
326
+ return {
327
+ promise: pendingPromise(),
328
+ cancel: () => {},
329
+ };
330
+ }
331
+
332
+ return handlePageRequest(endpointId, transactionSequenceNumber, networkAddress!);
333
+ }
334
+
335
+ if (commandId === IMAGE_BLOCK_REQUEST_ID) {
336
+ if (settings.usePageRequests) {
337
+ return {
338
+ promise: pendingPromise(),
339
+ cancel: () => {},
340
+ };
341
+ }
342
+
343
+ return handleBlockRequest(endpointId, transactionSequenceNumber, networkAddress!);
344
+ }
345
+
346
+ return fail("unsupported commandId");
347
+ };
348
+
349
+ return waitFor;
350
+ };
351
+
352
+ const OTA_FILES = [
353
+ "zbminir2_v1.0.8.ota",
354
+ "telink-aes-A60_RGBW_T-0x00B6-0x03483712-MF_DIS.OTA",
355
+ "integrity-code-1166-012B-24031511-upgradeMe-RB 249 T.zigbee",
356
+ "manuf-tags-tradfri-cv-cct-unified_release_prod_v587757105_33e34452-9267-4665-bc5a-844c8f61f063.ota",
357
+ "padded-tretakt_smart_plug_soc-0x1100-2.4.25-prod.ota.ota.signed",
358
+ ];
359
+ const OTA_FILES_SHA512 = [
360
+ "051912851dffed4b49d3caece8d67b0fec9987dcbdf4d6db45c4e23f4ea0c9be867491758af12f153bc9672d4205129a9270be79e781d150734be295c44971bf",
361
+ "6696fe6707686d9586cdfa045bb3ce24ef960b6cb664a09096e6e11025143373528760f525f5bd2c715d590a2891bd1a69f24227dfe62290aa80e3fcc7e1949d",
362
+ "d3c86737e3a9de6102c013b00b69ea37c551debb898c25f5ce67bfdab4c52b8acca5932e34953d06087dd078d77d83d5d8654c695af6a18440fc2acea0c07f5f",
363
+ "98d8be5b4dcd9692cb6ff87531513023c0116cb9137c5690105ce2077765f35ae2651bdd2aa80ce0363f6db4ef3b9795449252c6c0ff8a8b0e6e05789fdb03be",
364
+ "cd2f88f3f47f459218dd23d1317e76f413cea8a4eedee047a7a6627a595e742f47ec2783eeb2b33e634435cda3f219fa7b540dde6d67876f05d3fdf6feb636d2",
365
+ ];
366
+
367
+ const getResponses = (endpoint: Endpoint, command: string) => {
368
+ const mock = endpoint.commandResponse as unknown as ReturnType<typeof vi.fn>;
369
+
370
+ return mock.mock.calls.filter((call) => call[1] === command);
371
+ };
372
+
373
+ const loadImage = async (fileName: string): Promise<[image: OtaImage, path: string]> => {
374
+ const filePath = path.join(__dirname, "data", fileName);
375
+ const buffer = await otaHelpers.getOtaFirmware(filePath, undefined);
376
+
377
+ return [otaHelpers.parseOtaImage(buffer), filePath];
378
+ };
379
+
380
+ const mockImage = (
381
+ totalImageSize: number,
382
+ manufacturerCode = 1,
383
+ imageType = 2,
384
+ fileVersion = 3,
385
+ securityCredentialVersion?: number,
386
+ upgradeFileDestination?: Buffer,
387
+ minimumHardwareVersion?: number,
388
+ maximumHardwareVersion?: number,
389
+ ): OtaImage => {
390
+ if (totalImageSize < 56) {
391
+ throw new Error("Mock image totalImageSize too small");
392
+ }
393
+
394
+ const header: OtaImage["header"] = {
395
+ otaUpgradeFileIdentifier: otaHelpers.UPGRADE_FILE_IDENTIFIER,
396
+ otaHeaderVersion: 0x0100, // OTA_HEADER_VERSION_ZIGBEE
397
+ otaHeaderLength: 56,
398
+ otaHeaderFieldControl: 0,
399
+ manufacturerCode,
400
+ imageType,
401
+ fileVersion,
402
+ zigbeeStackVersion: 2, // ZIGBEE_PRO_STACK_VERSION
403
+ otaHeaderString: "test",
404
+ totalImageSize,
405
+ };
406
+ const raw = Buffer.alloc(totalImageSize, 0xf1);
407
+ let offset = 0;
408
+ offset = raw.writeUInt32LE(header.otaUpgradeFileIdentifier, offset);
409
+ offset = raw.writeUInt16LE(header.otaHeaderVersion, offset);
410
+ offset = raw.writeUInt16LE(header.otaHeaderLength, offset);
411
+ const otaHeaderFieldControlOffset = offset; // will write at end
412
+ offset += 2;
413
+ offset = raw.writeUInt16LE(header.manufacturerCode, offset);
414
+ offset = raw.writeUInt16LE(header.imageType, offset);
415
+ offset = raw.writeUInt32LE(header.fileVersion, offset);
416
+ offset = raw.writeUInt16LE(header.zigbeeStackVersion, offset);
417
+ offset += raw.write(header.otaHeaderString.padEnd(32, "\u0000"), offset, "utf8");
418
+ offset = raw.writeUInt32LE(header.totalImageSize, offset);
419
+
420
+ if (securityCredentialVersion !== undefined) {
421
+ header.otaHeaderFieldControl |= 1;
422
+ header.otaHeaderLength += 1;
423
+ header.securityCredentialVersion = securityCredentialVersion;
424
+ offset = raw.writeUInt8(securityCredentialVersion, offset);
425
+ }
426
+
427
+ if (upgradeFileDestination !== undefined) {
428
+ if (upgradeFileDestination.byteLength !== 8) {
429
+ throw new Error("Mock image invalid upgradeFileDestination");
430
+ }
431
+
432
+ header.otaHeaderFieldControl |= 2;
433
+ header.otaHeaderLength += 8;
434
+ header.upgradeFileDestination = upgradeFileDestination;
435
+ offset += upgradeFileDestination.copy(raw, offset);
436
+ }
437
+
438
+ if (minimumHardwareVersion !== undefined && maximumHardwareVersion !== undefined) {
439
+ header.otaHeaderFieldControl |= 4;
440
+ header.otaHeaderLength += 4;
441
+ header.minimumHardwareVersion = minimumHardwareVersion;
442
+ offset = raw.writeUInt16LE(minimumHardwareVersion, offset);
443
+ header.maximumHardwareVersion = maximumHardwareVersion;
444
+ offset = raw.writeUInt16LE(maximumHardwareVersion, offset);
445
+ }
446
+
447
+ raw.writeUInt16LE(header.otaHeaderFieldControl, otaHeaderFieldControlOffset);
448
+
449
+ // enough to write UpgradeImage tag
450
+ if (totalImageSize < header.otaHeaderLength + 7) {
451
+ throw new Error("Mock image totalImageSize too small");
452
+ }
453
+
454
+ offset = raw.writeUInt16LE(otaHelpers.OtaTagId.UpgradeImage, offset);
455
+ offset = raw.writeUInt32LE(totalImageSize - header.otaHeaderLength - 6, offset);
456
+
457
+ return {
458
+ header,
459
+ elements: [],
460
+ raw,
461
+ };
462
+ };
463
+
464
+ const writeMockImage = (image: OtaImage, fileName: string, tempDir?: string) => {
465
+ if (!tempDir) {
466
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "zh-ota-"));
467
+ }
468
+
469
+ tempDirs.push(tempDir);
470
+
471
+ const filePath = path.join(tempDir, fileName);
472
+
473
+ fs.writeFileSync(filePath, image.raw);
474
+
475
+ return filePath;
476
+ };
477
+
478
+ type CreateDeviceOptions = {
479
+ image: OtaImage;
480
+ behavior?: Partial<OtaDeviceBehavior>;
481
+ source: OtaSource | undefined;
482
+ requestPayload?: TClusterCommandPayload<"genOta", "queryNextImageRequest">;
483
+ dataSettings?: OtaDataSettings;
484
+ onProgress?: (progress: number, remaining?: number) => void;
485
+ modelID?: string;
486
+ manufacturerName?: string;
487
+ meta?: Record<string, unknown>;
488
+ autoAnnounce?: boolean;
489
+ };
490
+
491
+ const createSimpleDevice = ({modelID, manufacturerName, meta}: Pick<CreateDeviceOptions, "modelID" | "manufacturerName" | "meta">) => {
492
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "zh-ota-"));
493
+
494
+ tempDirs.push(tempDir);
495
+
496
+ const database = Database.open(path.join(tempDir, "database.db"));
497
+ database.write = () => {};
498
+ Entity.injectDatabase(database);
499
+
500
+ const device = Device.create("Router", "0x1", 0x1001, 1, manufacturerName, "Mains", modelID, InterviewState.Successful, undefined);
501
+
502
+ if (meta) {
503
+ device.meta = meta;
504
+ }
505
+
506
+ const endpoint = createEndpointStub();
507
+ device.endpoints.push(endpoint);
508
+
509
+ return {
510
+ device,
511
+ endpoint,
512
+ tempDir,
513
+ };
514
+ };
515
+
516
+ const createDevice = ({
517
+ image,
518
+ behavior,
519
+ source,
520
+ requestPayload,
521
+ dataSettings = {requestTimeout: 100, responseDelay: 0, baseSize: 64},
522
+ onProgress = vi.fn(),
523
+ modelID = "ModelX",
524
+ manufacturerName = "TestCo",
525
+ meta,
526
+ autoAnnounce = true,
527
+ }: CreateDeviceOptions) => {
528
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "zh-ota-"));
529
+
530
+ tempDirs.push(tempDir);
531
+
532
+ const database = Database.open(path.join(tempDir, "database.db"));
533
+ database.write = () => {};
534
+ Entity.injectDatabase(database);
535
+
536
+ const endpoint = createEndpointStub();
537
+ const currentPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = requestPayload ?? {
538
+ fieldControl: 0,
539
+ manufacturerCode: image.header.manufacturerCode,
540
+ imageType: image.header.imageType,
541
+ fileVersion: image.header.fileVersion + (source?.downgrade ? 1 : -1),
542
+ };
543
+ const waitFor = createOtaDeviceWaitFor(endpoint, image, currentPayload, {baseSize: dataSettings.baseSize, ...behavior});
544
+ const adapter = {waitFor, hasZdoMessageOverhead: false} as unknown as Adapter;
545
+ Entity.injectAdapter(adapter);
546
+
547
+ const device = Device.create("Router", "0x1", 0x1001, 1, manufacturerName, "Mains", modelID, InterviewState.Successful, undefined);
548
+
549
+ if (autoAnnounce) {
550
+ const originalOnce = device.once.bind(device);
551
+
552
+ device.once = ((event: Parameters<typeof device.once>[0], listener: Parameters<typeof device.once>[1]) => {
553
+ if (event === "deviceAnnounce") {
554
+ queueMicrotask(() => device.emit("deviceAnnounce", {} as never));
555
+ }
556
+
557
+ return originalOnce(event, listener);
558
+ }) as typeof device.once;
559
+ }
560
+
561
+ if (meta) {
562
+ device.meta = meta;
563
+ }
564
+
565
+ device.endpoints.push(endpoint);
566
+
567
+ return {
568
+ device,
569
+ endpoint,
570
+ onProgress,
571
+ run: async () => {
572
+ if (requestPayload === undefined) {
573
+ return await device.updateOta(source, undefined, undefined, {}, onProgress, dataSettings, endpoint);
574
+ }
575
+
576
+ return await device.updateOta(source, requestPayload, 1, {}, onProgress, dataSettings, endpoint);
577
+ },
578
+ tempDir,
579
+ };
580
+ };
581
+
582
+ const buildIndexEntry = (fileName: string, image: OtaImage, index?: number, url?: string) => ({
583
+ fileName,
584
+ url: url ?? path.join(__dirname, "data", fileName),
585
+ manufacturerCode: image.header.manufacturerCode,
586
+ imageType: image.header.imageType,
587
+ fileVersion: image.header.fileVersion,
588
+ sha512: index === undefined ? undefined : OTA_FILES_SHA512[index],
589
+ });
590
+
591
+ describe("Device OTA", () => {
592
+ let fetchIndexEntries: ZigbeeOtaImageMeta[] = [];
593
+ let firmwareBuffer: Buffer | undefined;
594
+ const fetchMockFail = {ok: false, body: null, status: 403};
595
+ const fetchMockIndex = {
596
+ ok: true,
597
+ body: {},
598
+ status: 200,
599
+ json: async () => fetchIndexEntries,
600
+ };
601
+ const fetchMockFirmware = {
602
+ ok: true,
603
+ body: {},
604
+ status: 200,
605
+ arrayBuffer: async () => firmwareBuffer,
606
+ };
607
+ const fetchMock = vi.fn((input: string | URL): typeof fetchMockFail | typeof fetchMockIndex | typeof fetchMockFirmware => {
608
+ const url = typeof input === "string" ? input : input.toString();
609
+
610
+ if (url.endsWith(".json")) {
611
+ return fetchMockIndex;
612
+ }
613
+
614
+ if (!firmwareBuffer) {
615
+ throw new Error("firmware buffer not provided");
616
+ }
617
+
618
+ return fetchMockFirmware;
619
+ });
620
+
621
+ beforeEach(async () => {
622
+ Device.resetCache();
623
+ vi.stubGlobal("fetch", fetchMock);
624
+
625
+ fetchIndexEntries = [];
626
+
627
+ for (let i = 0; i < OTA_FILES.length; i++) {
628
+ const fileName = OTA_FILES[i];
629
+ const [image] = await loadImage(fileName);
630
+
631
+ fetchIndexEntries.push(buildIndexEntry(fileName, image, i));
632
+ }
633
+
634
+ firmwareBuffer = undefined;
635
+
636
+ // @ts-expect-error not proper API, but here we just want to reset to default
637
+ otaHelpers.setOtaConfiguration(undefined, undefined);
638
+ });
639
+
640
+ afterEach(() => {
641
+ vi.restoreAllMocks();
642
+ vi.unstubAllGlobals();
643
+
644
+ for (const dir of tempDirs.splice(0)) {
645
+ fs.rmSync(dir, {recursive: true, force: true});
646
+ }
647
+ });
648
+
649
+ describe("Checks", () => {
650
+ it("returns no image when index is empty", async () => {
651
+ fetchIndexEntries = [];
652
+ const {device} = createSimpleDevice({});
653
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
654
+ fieldControl: 0,
655
+ manufacturerCode: 1234,
656
+ imageType: 55,
657
+ fileVersion: 10,
658
+ };
659
+ const result = await device.checkOta({}, current, {});
660
+
661
+ expect(result).toStrictEqual({available: false, current});
662
+ expect(fetchMock).toHaveBeenCalledTimes(1);
663
+ });
664
+
665
+ it("performs notify flow when current payload is missing", async () => {
666
+ const fileName = OTA_FILES[1];
667
+ const [image, filePath] = await loadImage(fileName);
668
+ const {device, endpoint} = createDevice({image, source: {}, requestPayload: undefined});
669
+ const result = await device.checkOta({}, undefined, {});
670
+
671
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
672
+ "genOta",
673
+ "imageNotify",
674
+ {payloadType: 0, queryJitter: 100},
675
+ {sendPolicy: "immediate"},
676
+ );
677
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
678
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
679
+ "genOta",
680
+ "queryNextImageResponse",
681
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
682
+ undefined,
683
+ 1,
684
+ );
685
+ expect(result).toStrictEqual({
686
+ available: true,
687
+ availableMeta: expect.objectContaining({url: filePath}),
688
+ current: {
689
+ fieldControl: 0,
690
+ manufacturerCode: image.header.manufacturerCode,
691
+ imageType: image.header.imageType,
692
+ fileVersion: image.header.fileVersion - 1,
693
+ },
694
+ });
695
+ });
696
+
697
+ it("matches OTA images using extra metadata overrides", async () => {
698
+ const image = mockImage(512);
699
+ const meta: ZigbeeOtaImageMeta = {
700
+ fileName: "custom.ota",
701
+ url: path.join(os.tmpdir(), "custom.ota"),
702
+ manufacturerCode: image.header.manufacturerCode,
703
+ imageType: image.header.imageType,
704
+ fileVersion: image.header.fileVersion,
705
+ modelId: "MatchedModel",
706
+ };
707
+ fetchIndexEntries = [meta];
708
+ const {device} = createSimpleDevice({modelID: "OtherModel", manufacturerName: "OtherCo"});
709
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
710
+ fieldControl: 0,
711
+ manufacturerCode: meta.manufacturerCode,
712
+ imageType: meta.imageType,
713
+ fileVersion: meta.fileVersion - 1,
714
+ };
715
+ const result = await device.checkOta({}, current, {modelId: "MatchedModel"});
716
+
717
+ expect(result.available).toStrictEqual(true);
718
+ expect(result.availableMeta?.fileName).toStrictEqual(meta.fileName);
719
+ });
720
+
721
+ it("matches OTA images using manufacturer name and hardware ranges", async () => {
722
+ const image = mockImage(128);
723
+ const matchingMeta: ZigbeeOtaImageMeta = {
724
+ fileName: "match.ota",
725
+ url: path.join(os.tmpdir(), "match.ota"),
726
+ manufacturerCode: image.header.manufacturerCode,
727
+ imageType: image.header.imageType,
728
+ fileVersion: image.header.fileVersion,
729
+ manufacturerName: ["BrandA device"],
730
+ hardwareVersionMin: 2,
731
+ hardwareVersionMax: 5,
732
+ };
733
+ fetchIndexEntries = [matchingMeta];
734
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
735
+ fieldControl: 1,
736
+ manufacturerCode: matchingMeta.manufacturerCode,
737
+ imageType: matchingMeta.imageType,
738
+ fileVersion: matchingMeta.fileVersion - 1,
739
+ hardwareVersion: 3,
740
+ };
741
+ const {device} = createSimpleDevice({manufacturerName: "BrandA device", modelID: "ModelY"});
742
+ const result = await device.checkOta({}, current, {});
743
+
744
+ expect(result.available).toStrictEqual(true);
745
+ expect(result.availableMeta?.fileName).toStrictEqual("match.ota");
746
+ });
747
+
748
+ it("matches OTA images using extra manufacturer metadata when device manufacturer name mismatches", async () => {
749
+ const image = mockImage(256);
750
+ const matchingMeta: ZigbeeOtaImageMeta = {
751
+ fileName: "extra-match.ota",
752
+ url: path.join(os.tmpdir(), "extra-match.ota"),
753
+ manufacturerCode: image.header.manufacturerCode,
754
+ imageType: image.header.imageType,
755
+ fileVersion: image.header.fileVersion,
756
+ manufacturerName: ["AltBrand"],
757
+ hardwareVersionMin: 10,
758
+ hardwareVersionMax: 20,
759
+ };
760
+ fetchIndexEntries = [matchingMeta];
761
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
762
+ fieldControl: 0,
763
+ manufacturerCode: matchingMeta.manufacturerCode,
764
+ imageType: matchingMeta.imageType,
765
+ fileVersion: matchingMeta.fileVersion - 2,
766
+ };
767
+ const {device} = createSimpleDevice({manufacturerName: "Mismatch", modelID: "ModelZ"});
768
+ const result = await device.checkOta({}, current, {manufacturerName: "AltBrand", hardwareVersionMin: 12, hardwareVersionMax: 18});
769
+
770
+ expect(result.available).toStrictEqual(true);
771
+ expect(result.availableMeta?.fileName).toStrictEqual("extra-match.ota");
772
+ });
773
+
774
+ it("handles custom index as absolute path", async () => {
775
+ const fileName = OTA_FILES[0];
776
+ const [image] = await loadImage(fileName);
777
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
778
+ fieldControl: 0,
779
+ manufacturerCode: image.header.manufacturerCode,
780
+ imageType: image.header.imageType,
781
+ fileVersion: image.header.fileVersion - 1,
782
+ };
783
+ const {device, tempDir} = createDevice({image, source: {}, requestPayload: undefined});
784
+ const indexMeta = buildIndexEntry(fileName, image, undefined, path.join(tempDir, fileName));
785
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
786
+
787
+ fs.writeFileSync(path.join(tempDir, "my_index.json"), JSON.stringify([indexMeta]), "utf8");
788
+
789
+ const result = await device.checkOta({url: path.resolve(tempDir, "my_index.json")}, undefined, {});
790
+
791
+ expect(result.available).toStrictEqual(true);
792
+ expect(result.availableMeta?.fileVersion).toStrictEqual(requestPayload.fileVersion + 1);
793
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta]);
794
+ });
795
+
796
+ it("handles custom index as relative path", async () => {
797
+ const fileName = OTA_FILES[0];
798
+ const [image] = await loadImage(fileName);
799
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
800
+ fieldControl: 0,
801
+ manufacturerCode: image.header.manufacturerCode,
802
+ imageType: image.header.imageType,
803
+ fileVersion: image.header.fileVersion - 1,
804
+ };
805
+ const {device, tempDir} = createDevice({image, source: {}, requestPayload: undefined});
806
+ const indexMeta = buildIndexEntry(fileName, image, undefined, path.join(tempDir, fileName));
807
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
808
+
809
+ fs.writeFileSync(path.join(tempDir, "my_index.json"), JSON.stringify([indexMeta]), "utf8");
810
+ otaHelpers.setOtaConfiguration(tempDir, undefined);
811
+
812
+ const result = await device.checkOta({url: "my_index.json"}, undefined, {});
813
+
814
+ expect(result.available).toStrictEqual(true);
815
+ expect(result.availableMeta?.fileVersion).toStrictEqual(requestPayload.fileVersion + 1);
816
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta]);
817
+ });
818
+
819
+ it("throws with custom index as relative path when OTA not configured properly", async () => {
820
+ const [image] = await loadImage(OTA_FILES[0]);
821
+ const {device} = createDevice({image, source: {}, requestPayload: undefined});
822
+
823
+ await expect(device.checkOta({url: "my_index.json"}, undefined, {})).rejects.toThrow(/Invalid OTA configuration/);
824
+ });
825
+
826
+ it("handles failures when waiting for OTA notify response", async () => {
827
+ const [image] = await loadImage(OTA_FILES[0]);
828
+ const {device} = createDevice({image, source: {}, requestPayload: undefined});
829
+ // just need to trigger something called inside the right code path
830
+ const frameSpy = vi.spyOn(Zcl.Frame, "fromBuffer").mockImplementationOnce(() => {
831
+ throw new Error("corrupt-frame");
832
+ });
833
+
834
+ await expect(device.checkOta({}, undefined, {})).rejects.toThrow(/didn't respond to OTA request/);
835
+ expect(frameSpy).toHaveBeenCalled();
836
+ });
837
+
838
+ it("fails when remote index fetching fails", async () => {
839
+ fetchMock.mockResolvedValueOnce(fetchMockFail);
840
+
841
+ const [image] = await loadImage(OTA_FILES[0]);
842
+ firmwareBuffer = image.raw;
843
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
844
+ fieldControl: 0,
845
+ manufacturerCode: image.header.manufacturerCode,
846
+ imageType: image.header.imageType,
847
+ fileVersion: image.header.fileVersion - 1,
848
+ };
849
+ const {device, endpoint} = createSimpleDevice({});
850
+
851
+ await expect(device.checkOta({}, requestPayload, {}, endpoint)).rejects.toThrow(/Invalid response from/);
852
+ });
853
+
854
+ it("merges override index (prio) with URL index", async () => {
855
+ const fileName = "custom.ota";
856
+ const image = mockImage(400, undefined, undefined, 333);
857
+ firmwareBuffer = image.raw;
858
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
859
+ fieldControl: 0,
860
+ manufacturerCode: image.header.manufacturerCode,
861
+ imageType: image.header.imageType,
862
+ fileVersion: image.header.fileVersion - 1,
863
+ };
864
+ const {device, endpoint, tempDir} = createSimpleDevice({});
865
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
866
+ const filePath = writeMockImage(image, fileName, tempDir);
867
+ const indexMeta = buildIndexEntry(fileName, image, undefined, filePath);
868
+
869
+ fs.writeFileSync(path.join(tempDir, "my_override_index.json"), JSON.stringify([indexMeta]), "utf8");
870
+ otaHelpers.setOtaConfiguration(tempDir, "my_override_index.json");
871
+
872
+ const result = await device.checkOta({}, requestPayload, {}, endpoint);
873
+
874
+ expect(result.available).toStrictEqual(true);
875
+ expect(result.availableMeta?.fileVersion).toStrictEqual(image.header.fileVersion);
876
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta, ...fetchIndexEntries]);
877
+ });
878
+
879
+ it("auto-adds meta for override index local firmware when unspecified", async () => {
880
+ const fileName = "custom.ota";
881
+ const image = mockImage(512, undefined, undefined, 124);
882
+ firmwareBuffer = image.raw;
883
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
884
+ fieldControl: 0,
885
+ manufacturerCode: image.header.manufacturerCode,
886
+ imageType: image.header.imageType,
887
+ fileVersion: image.header.fileVersion - 1,
888
+ };
889
+ const {device, endpoint, tempDir} = createSimpleDevice({});
890
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
891
+ const indexMeta = {url: fileName};
892
+
893
+ writeMockImage(image, fileName, tempDir);
894
+ fs.writeFileSync(path.join(tempDir, "my_override_index.json"), JSON.stringify([indexMeta]), "utf8");
895
+ otaHelpers.setOtaConfiguration(tempDir, "my_override_index.json");
896
+
897
+ const result = await device.checkOta({}, requestPayload, {}, endpoint);
898
+
899
+ expect(result.available).toStrictEqual(true);
900
+ expect(result.availableMeta?.fileVersion).toStrictEqual(image.header.fileVersion);
901
+ expect(getOtaIndexSpy).toHaveResolvedWith([
902
+ {
903
+ url: fileName,
904
+ fileVersion: image.header.fileVersion,
905
+ imageType: image.header.imageType,
906
+ manufacturerCode: image.header.manufacturerCode,
907
+ },
908
+ ...fetchIndexEntries,
909
+ ]);
910
+ });
911
+
912
+ it("allows use of remote override index even if URL index fetching fails", async () => {
913
+ fetchMock.mockResolvedValueOnce(fetchMockIndex).mockResolvedValueOnce(fetchMockFail);
914
+ const [image] = await loadImage(OTA_FILES[0]);
915
+ firmwareBuffer = image.raw;
916
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
917
+ fieldControl: 0,
918
+ manufacturerCode: image.header.manufacturerCode,
919
+ imageType: image.header.imageType,
920
+ fileVersion: image.header.fileVersion - 1,
921
+ };
922
+ const {device, endpoint, tempDir} = createSimpleDevice({});
923
+ const infoSpy = vi.spyOn(logger, "info");
924
+
925
+ otaHelpers.setOtaConfiguration(tempDir, "https://example.com/index.json");
926
+
927
+ const result = await device.checkOta({}, requestPayload, {}, endpoint);
928
+
929
+ expect(result.available).toStrictEqual(true);
930
+ expect(result.availableMeta?.fileVersion).toStrictEqual(requestPayload.fileVersion + 1);
931
+ expect(infoSpy).toHaveBeenCalledWith("Failed to download main index, only override index is loaded", "zh:controller:ota");
932
+ });
933
+
934
+ it("allows use of local override index even if default URL index fetching fails", async () => {
935
+ fetchMock.mockResolvedValueOnce(fetchMockFail);
936
+
937
+ const fileName = OTA_FILES[0];
938
+ const [image] = await loadImage(fileName);
939
+ firmwareBuffer = image.raw;
940
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
941
+ fieldControl: 0,
942
+ manufacturerCode: image.header.manufacturerCode,
943
+ imageType: image.header.imageType,
944
+ fileVersion: image.header.fileVersion - 1,
945
+ };
946
+ const {device, endpoint, tempDir} = createSimpleDevice({});
947
+ const infoSpy = vi.spyOn(logger, "info");
948
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
949
+ const indexMeta = buildIndexEntry(fileName, image, undefined, path.join(tempDir, fileName));
950
+
951
+ fs.writeFileSync(path.join(tempDir, "my_index.json"), JSON.stringify([indexMeta]), "utf8");
952
+ otaHelpers.setOtaConfiguration(tempDir, "my_index.json");
953
+
954
+ const result = await device.checkOta({}, requestPayload, {}, endpoint);
955
+
956
+ expect(result.available).toStrictEqual(true);
957
+ expect(result.availableMeta?.fileVersion).toStrictEqual(requestPayload.fileVersion + 1);
958
+ expect(fetchMock).toHaveBeenCalledWith("https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/index.json");
959
+ expect(infoSpy).toHaveBeenCalledWith("Failed to download main index, only override index is loaded", "zh:controller:ota");
960
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta]);
961
+ });
962
+
963
+ it("allows use of local override index even if custom URL index fetching fails", async () => {
964
+ fetchMock.mockResolvedValueOnce(fetchMockFail);
965
+
966
+ const fileName = OTA_FILES[1];
967
+ const [image] = await loadImage(fileName);
968
+ firmwareBuffer = image.raw;
969
+ const {device, endpoint, tempDir} = createDevice({image, source: {}, requestPayload: undefined});
970
+ const infoSpy = vi.spyOn(logger, "info");
971
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
972
+ const indexMeta = buildIndexEntry(fileName, image, undefined, path.join(tempDir, fileName));
973
+
974
+ fs.writeFileSync(path.join(tempDir, "my_index.json"), JSON.stringify([indexMeta]), "utf8");
975
+ otaHelpers.setOtaConfiguration(tempDir, "my_index.json");
976
+
977
+ const result = await device.checkOta({url: "https://example.com/index.json"}, undefined, {}, endpoint);
978
+
979
+ expect(result.available).toStrictEqual(true);
980
+ expect(result.availableMeta?.fileVersion).toStrictEqual(image.header.fileVersion);
981
+ expect(fetchMock).toHaveBeenCalledWith("https://example.com/index.json");
982
+ expect(infoSpy).toHaveBeenCalledWith("Failed to download main index, only override index is loaded", "zh:controller:ota");
983
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta]);
984
+ });
985
+
986
+ it.each([
987
+ "lumi.airrtc.agl001",
988
+ "lumi.curtain.acn003",
989
+ "lumi.curtain.agl001",
990
+ ])("overrides %s file version with meta and reports upgrade availability", async (modelID) => {
991
+ const fileName = OTA_FILES[0];
992
+ const [image, filePath] = await loadImage(fileName);
993
+ const {device} = createSimpleDevice({
994
+ modelID,
995
+ manufacturerName: "Aqara",
996
+ meta: {lumiFileVersion: image.header.fileVersion - 1},
997
+ });
998
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
999
+ fieldControl: 0,
1000
+ manufacturerCode: image.header.manufacturerCode,
1001
+ imageType: image.header.imageType,
1002
+ fileVersion: 1,
1003
+ };
1004
+ const result = await device.checkOta({}, current, {});
1005
+
1006
+ expect(result).toStrictEqual({
1007
+ current: {...current, fileVersion: image.header.fileVersion - 1},
1008
+ available: true,
1009
+ availableMeta: {
1010
+ fileName,
1011
+ fileVersion: image.header.fileVersion,
1012
+ imageType: current.imageType,
1013
+ manufacturerCode: current.manufacturerCode,
1014
+ url: filePath,
1015
+ sha512: OTA_FILES_SHA512[0],
1016
+ },
1017
+ });
1018
+ expect(fetchMock).toHaveBeenCalledTimes(1);
1019
+ });
1020
+
1021
+ it("writes scenes group before checking OTA for PP-WHT-US", async () => {
1022
+ const fileName = OTA_FILES[2];
1023
+ const [image, filePath] = await loadImage(fileName);
1024
+ const {device} = createSimpleDevice({modelID: "PP-WHT-US", manufacturerName: "Lutron"});
1025
+ const scenesEndpoint = {
1026
+ ID: 2,
1027
+ supportsOutputCluster: vi.fn((clusterKey: number | string) => clusterKey === "genScenes"),
1028
+ write: vi.fn().mockResolvedValue(undefined),
1029
+ } as unknown as Endpoint;
1030
+
1031
+ device.endpoints.push(scenesEndpoint);
1032
+
1033
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1034
+ fieldControl: 0,
1035
+ manufacturerCode: image.header.manufacturerCode,
1036
+ imageType: image.header.imageType,
1037
+ fileVersion: image.header.fileVersion - 1,
1038
+ };
1039
+ const result = await device.checkOta({}, current, {});
1040
+
1041
+ expect(scenesEndpoint.write).toHaveBeenCalledWith("genScenes", {currentGroup: 49502});
1042
+ expect(result.availableMeta?.url).toStrictEqual(filePath);
1043
+ });
1044
+ });
1045
+
1046
+ describe("Updates", () => {
1047
+ let debugSpy: ReturnType<typeof vi.spyOn> | undefined;
1048
+
1049
+ beforeEach(() => {
1050
+ debugSpy = vi.spyOn(logger, "debug").mockImplementation(() => {});
1051
+ });
1052
+
1053
+ afterEach(() => {
1054
+ debugSpy?.mockRestore();
1055
+ debugSpy = undefined;
1056
+ });
1057
+
1058
+ it.each(OTA_FILES)("applies an upgrade image end-to-end (%s)", async (fileName) => {
1059
+ const [image] = await loadImage(fileName);
1060
+ firmwareBuffer = image.raw;
1061
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1062
+ fieldControl: 0,
1063
+ manufacturerCode: image.header.manufacturerCode,
1064
+ imageType: image.header.imageType,
1065
+ fileVersion: image.header.fileVersion - 1,
1066
+ };
1067
+ const baseSize = 64;
1068
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1069
+
1070
+ const {device, endpoint, run, onProgress} = createDevice({
1071
+ image,
1072
+ source: {},
1073
+ requestPayload,
1074
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1075
+ behavior: {baseSize, sendUpgradeEnd: true},
1076
+ });
1077
+
1078
+ const [from, to] = await run();
1079
+
1080
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1081
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1082
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
1083
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1084
+ "genOta",
1085
+ "queryNextImageResponse",
1086
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1087
+ undefined,
1088
+ 1,
1089
+ );
1090
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1091
+ const calls = getResponses(endpoint, "imageBlockResponse");
1092
+ const lastOffset = (expectedBlocks - 1) * baseSize;
1093
+ const lastSize = image.header.totalImageSize - lastOffset;
1094
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1095
+ expect(calls[1][2]).toEqual(
1096
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
1097
+ );
1098
+ expect(calls[2][2]).toEqual(
1099
+ expect.objectContaining({fileOffset: baseSize * 2, dataSize: baseSize, data: image.raw.subarray(baseSize * 2, baseSize * 3)}),
1100
+ );
1101
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1102
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1103
+ );
1104
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1105
+ expect(onProgress).toHaveBeenCalled();
1106
+ expect(device.otaInProgress).toStrictEqual(false);
1107
+ });
1108
+
1109
+ it("applies a downgrade image end-to-end", async () => {
1110
+ const fileName = OTA_FILES[0];
1111
+ const [image, filePath] = await loadImage(fileName);
1112
+ firmwareBuffer = image.raw;
1113
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1114
+ fieldControl: 0,
1115
+ manufacturerCode: image.header.manufacturerCode,
1116
+ imageType: image.header.imageType,
1117
+ fileVersion: image.header.fileVersion + 1,
1118
+ };
1119
+ const baseSize = 64;
1120
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1121
+ const logDebugSpy = vi.spyOn(logger, "debug");
1122
+
1123
+ const {device, endpoint, run, onProgress} = createDevice({
1124
+ image,
1125
+ source: {downgrade: true},
1126
+ requestPayload,
1127
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1128
+ behavior: {baseSize, sendUpgradeEnd: true},
1129
+ });
1130
+
1131
+ const [from, to] = await run();
1132
+
1133
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1134
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1135
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
1136
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1137
+ "genOta",
1138
+ "queryNextImageResponse",
1139
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1140
+ undefined,
1141
+ 1,
1142
+ );
1143
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1144
+ const calls = getResponses(endpoint, "imageBlockResponse");
1145
+ const lastOffset = (expectedBlocks - 1) * baseSize;
1146
+ const lastSize = image.header.totalImageSize - lastOffset;
1147
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1148
+ expect(calls[1][2]).toEqual(
1149
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
1150
+ );
1151
+ expect(calls[2][2]).toEqual(
1152
+ expect.objectContaining({fileOffset: baseSize * 2, dataSize: baseSize, data: image.raw.subarray(baseSize * 2, baseSize * 3)}),
1153
+ );
1154
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1155
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1156
+ );
1157
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1158
+ expect(onProgress).toHaveBeenCalled();
1159
+
1160
+ const debugCalls = logDebugSpy.mock.calls.map((c) => (typeof c[0] === "string" ? c[0] : c[0]()));
1161
+ expect(debugCalls[0]).toMatch("Checking OTA 0x1 downgrade image availability");
1162
+ expect(debugCalls[1]).toMatch("Getting image metadata for 0x1...");
1163
+ expect(debugCalls[2]).toMatch("OTA downgrade image availability for 0x1");
1164
+ expect(debugCalls[3]).toMatch(`Reading firmware image from '${filePath}'`);
1165
+ expect(debugCalls[4]).toMatch(`Parsed image from '${filePath}' for 0x1`);
1166
+ expect(device.otaInProgress).toStrictEqual(false);
1167
+ });
1168
+
1169
+ it("considers an upgrade successful even if no device announce", async () => {
1170
+ const fileName = OTA_FILES[0];
1171
+ const [image] = await loadImage(fileName);
1172
+ firmwareBuffer = image.raw;
1173
+ const baseSize = 64;
1174
+
1175
+ vi.useFakeTimers();
1176
+
1177
+ try {
1178
+ const {run, device, endpoint} = createDevice({
1179
+ image,
1180
+ source: {},
1181
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1182
+ behavior: {baseSize, sendUpgradeEnd: true},
1183
+ autoAnnounce: false,
1184
+ });
1185
+ const promise = run();
1186
+
1187
+ await vi.advanceTimersByTimeAsync(120000);
1188
+
1189
+ const [from, to] = await promise;
1190
+
1191
+ expect(from.fileVersion).toStrictEqual(image.header.fileVersion - 1);
1192
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1193
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
1194
+ expect(device.otaInProgress).toStrictEqual(false);
1195
+ } finally {
1196
+ vi.useRealTimers();
1197
+ }
1198
+ });
1199
+
1200
+ it("handles receiving non-value for data size", async () => {
1201
+ const fileName = OTA_FILES[0];
1202
+ const [image] = await loadImage(fileName);
1203
+ firmwareBuffer = image.raw;
1204
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1205
+ fieldControl: 0,
1206
+ manufacturerCode: image.header.manufacturerCode,
1207
+ imageType: image.header.imageType,
1208
+ fileVersion: image.header.fileVersion - 1,
1209
+ };
1210
+ const baseSize = 55;
1211
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1212
+
1213
+ const {device, endpoint, run} = createDevice({
1214
+ image,
1215
+ source: {},
1216
+ requestPayload,
1217
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1218
+ behavior: {baseSize, sendUpgradeEnd: true, useNonValueDataSize: true},
1219
+ });
1220
+
1221
+ const [from, to] = await run();
1222
+
1223
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1224
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1225
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1226
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1227
+ expect(device.otaInProgress).toStrictEqual(false);
1228
+ });
1229
+
1230
+ it("handles out-of-order block offsets", async () => {
1231
+ const fileName = OTA_FILES[0];
1232
+ const [image] = await loadImage(fileName);
1233
+ firmwareBuffer = image.raw;
1234
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1235
+ fieldControl: 0,
1236
+ manufacturerCode: image.header.manufacturerCode,
1237
+ imageType: image.header.imageType,
1238
+ fileVersion: image.header.fileVersion - 1,
1239
+ };
1240
+ const baseSize = 50;
1241
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1242
+
1243
+ const {device, endpoint, run, onProgress} = createDevice({
1244
+ image,
1245
+ source: {},
1246
+ requestPayload,
1247
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1248
+ behavior: {baseSize, sendUpgradeEnd: true, shuffleOffsets: true},
1249
+ });
1250
+
1251
+ const [from, to] = await run();
1252
+
1253
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1254
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1255
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1256
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1257
+ "genOta",
1258
+ "queryNextImageResponse",
1259
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1260
+ undefined,
1261
+ 1,
1262
+ );
1263
+ const calls = getResponses(endpoint, "imageBlockResponse");
1264
+ const lastOffset = (expectedBlocks - 1) * baseSize;
1265
+ const lastSize = image.header.totalImageSize - lastOffset;
1266
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1267
+ expect(calls[1][2]).toEqual(
1268
+ expect.objectContaining({
1269
+ fileOffset: baseSize * 2,
1270
+ dataSize: baseSize,
1271
+ data: image.raw.subarray(baseSize * 2, baseSize * 3),
1272
+ }),
1273
+ );
1274
+ expect(calls[2][2]).toEqual(
1275
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
1276
+ );
1277
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1278
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1279
+ );
1280
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1281
+ expect(onProgress).toHaveBeenCalled();
1282
+ expect(device.otaInProgress).toStrictEqual(false);
1283
+ });
1284
+
1285
+ it("handles negative misaligned block offset - resends portion already sent", async () => {
1286
+ const fileName = OTA_FILES[0];
1287
+ const [image] = await loadImage(fileName);
1288
+ firmwareBuffer = image.raw;
1289
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1290
+ fieldControl: 0,
1291
+ manufacturerCode: image.header.manufacturerCode,
1292
+ imageType: image.header.imageType,
1293
+ fileVersion: image.header.fileVersion - 1,
1294
+ };
1295
+ const baseSize = 64;
1296
+ const misalignedOffset = -15;
1297
+ const expectedBlocks = Math.ceil((image.header.totalImageSize - misalignedOffset) / baseSize);
1298
+
1299
+ const {device, endpoint, run, onProgress} = createDevice({
1300
+ image,
1301
+ source: {},
1302
+ requestPayload,
1303
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1304
+ behavior: {baseSize, sendUpgradeEnd: true, misalignedOffset},
1305
+ });
1306
+
1307
+ const [from, to] = await run();
1308
+
1309
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1310
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1311
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1312
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1313
+ "genOta",
1314
+ "queryNextImageResponse",
1315
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1316
+ undefined,
1317
+ 1,
1318
+ );
1319
+ const calls = getResponses(endpoint, "imageBlockResponse");
1320
+ const lastOffset = (expectedBlocks - 1) * baseSize + misalignedOffset;
1321
+ const lastSize = image.header.totalImageSize - lastOffset;
1322
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1323
+ expect(calls[1][2]).toEqual(
1324
+ expect.objectContaining({
1325
+ fileOffset: baseSize + misalignedOffset,
1326
+ dataSize: baseSize,
1327
+ data: image.raw.subarray(baseSize + misalignedOffset, baseSize * 2 + misalignedOffset),
1328
+ }),
1329
+ );
1330
+ expect(calls[2][2]).toEqual(
1331
+ expect.objectContaining({
1332
+ fileOffset: baseSize * 2 + misalignedOffset,
1333
+ dataSize: baseSize,
1334
+ data: image.raw.subarray(baseSize * 2 + misalignedOffset, baseSize * 3 + misalignedOffset),
1335
+ }),
1336
+ );
1337
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1338
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1339
+ );
1340
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1341
+ expect(onProgress).toHaveBeenCalled();
1342
+ expect(device.otaInProgress).toStrictEqual(false);
1343
+ });
1344
+
1345
+ it("handles positive misaligned block offset - skips portion", async () => {
1346
+ const fileName = OTA_FILES[0];
1347
+ const [image] = await loadImage(fileName);
1348
+ firmwareBuffer = image.raw;
1349
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1350
+ fieldControl: 0,
1351
+ manufacturerCode: image.header.manufacturerCode,
1352
+ imageType: image.header.imageType,
1353
+ fileVersion: image.header.fileVersion - 1,
1354
+ };
1355
+ const baseSize = 50;
1356
+ const misalignedOffset = 17;
1357
+ const expectedBlocks = Math.ceil((image.header.totalImageSize - misalignedOffset) / baseSize);
1358
+
1359
+ const {device, endpoint, run, onProgress} = createDevice({
1360
+ image,
1361
+ source: {},
1362
+ requestPayload,
1363
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1364
+ behavior: {baseSize, sendUpgradeEnd: true, misalignedOffset},
1365
+ });
1366
+
1367
+ const [from, to] = await run();
1368
+
1369
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1370
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1371
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1372
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1373
+ "genOta",
1374
+ "queryNextImageResponse",
1375
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1376
+ undefined,
1377
+ 1,
1378
+ );
1379
+ const calls = getResponses(endpoint, "imageBlockResponse");
1380
+ const lastOffset = (expectedBlocks - 1) * baseSize + misalignedOffset;
1381
+ const lastSize = image.header.totalImageSize - lastOffset;
1382
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1383
+ expect(calls[1][2]).toEqual(
1384
+ expect.objectContaining({
1385
+ fileOffset: baseSize + misalignedOffset,
1386
+ dataSize: baseSize,
1387
+ data: image.raw.subarray(baseSize + misalignedOffset, baseSize * 2 + misalignedOffset),
1388
+ }),
1389
+ );
1390
+ expect(calls[2][2]).toEqual(
1391
+ expect.objectContaining({
1392
+ fileOffset: baseSize * 2 + misalignedOffset,
1393
+ dataSize: baseSize,
1394
+ data: image.raw.subarray(baseSize * 2 + misalignedOffset, baseSize * 3 + misalignedOffset),
1395
+ }),
1396
+ );
1397
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1398
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1399
+ );
1400
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1401
+ expect(onProgress).toHaveBeenCalled();
1402
+ expect(device.otaInProgress).toStrictEqual(false);
1403
+ });
1404
+
1405
+ it("handles page requests", async () => {
1406
+ const fileName = OTA_FILES[0];
1407
+ const [image] = await loadImage(fileName);
1408
+ firmwareBuffer = image.raw;
1409
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1410
+ fieldControl: 0,
1411
+ manufacturerCode: image.header.manufacturerCode,
1412
+ imageType: image.header.imageType,
1413
+ fileVersion: image.header.fileVersion - 1,
1414
+ };
1415
+ const baseSize = 52;
1416
+ const pageSize = 128;
1417
+ const expectedBlocks = (() => {
1418
+ let remaining = image.header.totalImageSize;
1419
+ let blocks = 0;
1420
+
1421
+ while (remaining > 0) {
1422
+ const page = Math.min(pageSize, remaining);
1423
+ blocks += Math.ceil(page / baseSize);
1424
+ remaining -= page;
1425
+ }
1426
+
1427
+ return blocks;
1428
+ })();
1429
+
1430
+ const {device, endpoint, run, onProgress} = createDevice({
1431
+ image,
1432
+ source: {},
1433
+ requestPayload,
1434
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1435
+ behavior: {baseSize, sendUpgradeEnd: true, usePageRequests: true, pageSize},
1436
+ });
1437
+
1438
+ const [from, to] = await run();
1439
+
1440
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1441
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1442
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1443
+ "genOta",
1444
+ "queryNextImageResponse",
1445
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1446
+ undefined,
1447
+ 1,
1448
+ );
1449
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1450
+ const calls = getResponses(endpoint, "imageBlockResponse");
1451
+ const firstPageRemainder = Math.min(pageSize, image.header.totalImageSize) - baseSize * 2;
1452
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1453
+ expect(calls[1][2]).toEqual(
1454
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
1455
+ );
1456
+ expect(calls[2][2]).toEqual(
1457
+ expect.objectContaining({
1458
+ fileOffset: baseSize * 2,
1459
+ dataSize: firstPageRemainder,
1460
+ data: image.raw.subarray(baseSize * 2, baseSize * 2 + firstPageRemainder),
1461
+ }),
1462
+ );
1463
+ const lastCallPayload = calls[calls.length - 1][2] as unknown as {
1464
+ fileOffset: number;
1465
+ dataSize: number;
1466
+ data: Buffer;
1467
+ };
1468
+ expect(lastCallPayload.data).toStrictEqual(
1469
+ image.raw.subarray(lastCallPayload.fileOffset, lastCallPayload.fileOffset + lastCallPayload.dataSize),
1470
+ );
1471
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1472
+ expect(onProgress).toHaveBeenCalled();
1473
+ expect(device.otaInProgress).toStrictEqual(false);
1474
+ });
1475
+
1476
+ it("applies manufacturer-specific timeout", async () => {
1477
+ const fileName = "custom.ota";
1478
+ const image = mockImage(400, Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, 33);
1479
+ const filePath = writeMockImage(image, fileName);
1480
+ const meta: ZigbeeOtaImageMeta = {
1481
+ fileName,
1482
+ url: filePath,
1483
+ manufacturerCode: image.header.manufacturerCode,
1484
+ imageType: image.header.imageType,
1485
+ fileVersion: image.header.fileVersion,
1486
+ };
1487
+ fetchIndexEntries = [meta];
1488
+ firmwareBuffer = image.raw;
1489
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1490
+ fieldControl: 0,
1491
+ manufacturerCode: image.header.manufacturerCode,
1492
+ imageType: image.header.imageType,
1493
+ fileVersion: image.header.fileVersion - 1,
1494
+ };
1495
+ const baseSize = 50;
1496
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1497
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1498
+ const {run, device, endpoint} = createDevice({
1499
+ image,
1500
+ source: {},
1501
+ requestPayload,
1502
+ dataSettings,
1503
+ behavior: {baseSize, sendUpgradeEnd: true},
1504
+ });
1505
+ const [from, to] = await run();
1506
+
1507
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1508
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1509
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1510
+ "genOta",
1511
+ "queryNextImageResponse",
1512
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1513
+ undefined,
1514
+ 1,
1515
+ );
1516
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1517
+ expect(dataSettings.requestTimeout).toStrictEqual(60 * 60 * 1000);
1518
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1519
+ expect(device.otaInProgress).toStrictEqual(false);
1520
+ });
1521
+
1522
+ it("applies manufacturer-specific and imageType-specific timeout", async () => {
1523
+ const fileName = "custom.ota";
1524
+ const image = mockImage(311, Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD, 8199);
1525
+ const filePath = writeMockImage(image, fileName);
1526
+ const meta: ZigbeeOtaImageMeta = {
1527
+ fileName,
1528
+ url: filePath,
1529
+ manufacturerCode: image.header.manufacturerCode,
1530
+ imageType: image.header.imageType,
1531
+ fileVersion: image.header.fileVersion,
1532
+ };
1533
+ fetchIndexEntries = [meta];
1534
+ firmwareBuffer = image.raw;
1535
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1536
+ fieldControl: 0,
1537
+ manufacturerCode: image.header.manufacturerCode,
1538
+ imageType: image.header.imageType,
1539
+ fileVersion: image.header.fileVersion - 1,
1540
+ };
1541
+ const baseSize = 50;
1542
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1543
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1544
+ const {run, device, endpoint} = createDevice({
1545
+ image,
1546
+ source: {},
1547
+ requestPayload,
1548
+ dataSettings,
1549
+ behavior: {baseSize, sendUpgradeEnd: true},
1550
+ });
1551
+ const [from, to] = await run();
1552
+
1553
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1554
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1555
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1556
+ "genOta",
1557
+ "queryNextImageResponse",
1558
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1559
+ undefined,
1560
+ 1,
1561
+ );
1562
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1563
+ expect(dataSettings.requestTimeout).toStrictEqual(3600000);
1564
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1565
+ expect(device.otaInProgress).toStrictEqual(false);
1566
+ });
1567
+
1568
+ it("applies manufacturer-specific data size", async () => {
1569
+ const fileName = "custom.ota";
1570
+ const image = mockImage(400, Zcl.ManufacturerCode.INSTA_GMBH, 33);
1571
+ const filePath = writeMockImage(image, fileName);
1572
+ const meta: ZigbeeOtaImageMeta = {
1573
+ fileName,
1574
+ url: filePath,
1575
+ manufacturerCode: image.header.manufacturerCode,
1576
+ imageType: image.header.imageType,
1577
+ fileVersion: image.header.fileVersion,
1578
+ };
1579
+ fetchIndexEntries = [meta];
1580
+ firmwareBuffer = image.raw;
1581
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1582
+ fieldControl: 0,
1583
+ manufacturerCode: image.header.manufacturerCode,
1584
+ imageType: image.header.imageType,
1585
+ fileVersion: image.header.fileVersion - 1,
1586
+ };
1587
+ const baseSize = 64;
1588
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1589
+ const {run, device, endpoint} = createDevice({
1590
+ image,
1591
+ source: {},
1592
+ requestPayload,
1593
+ dataSettings,
1594
+ behavior: {baseSize, sendUpgradeEnd: true},
1595
+ });
1596
+ const [from, to] = await run();
1597
+
1598
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1599
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1600
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1601
+ "genOta",
1602
+ "queryNextImageResponse",
1603
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1604
+ undefined,
1605
+ 1,
1606
+ );
1607
+ const responses = getResponses(endpoint, "imageBlockResponse");
1608
+ expect(responses[0][2]).toStrictEqual(expect.objectContaining({dataSize: 40}));
1609
+ expect(responses[1][2]).toStrictEqual(expect.objectContaining({dataSize: 40}));
1610
+ expect(device.otaInProgress).toStrictEqual(false);
1611
+ });
1612
+
1613
+ it("handles URL as source URL", async () => {
1614
+ const fileName = "custom.ota";
1615
+ // coverage for more manufacturer-specific timeout
1616
+ const image = mockImage(400, Zcl.ManufacturerCode.LEGRAND_GROUP, 1000);
1617
+ const filePath = writeMockImage(image, fileName);
1618
+ const meta: ZigbeeOtaImageMeta = {
1619
+ fileName,
1620
+ url: filePath,
1621
+ manufacturerCode: image.header.manufacturerCode,
1622
+ imageType: image.header.imageType,
1623
+ fileVersion: image.header.fileVersion,
1624
+ };
1625
+ fetchIndexEntries = [meta];
1626
+ firmwareBuffer = image.raw;
1627
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1628
+ fieldControl: 0,
1629
+ manufacturerCode: image.header.manufacturerCode,
1630
+ imageType: image.header.imageType,
1631
+ fileVersion: image.header.fileVersion - 1,
1632
+ };
1633
+ const baseSize = 50;
1634
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1635
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1636
+ const {run, device, endpoint} = createDevice({
1637
+ image,
1638
+ source: {url: "https://example.com/fw.ota"},
1639
+ requestPayload,
1640
+ dataSettings,
1641
+ behavior: {baseSize, sendUpgradeEnd: true},
1642
+ });
1643
+ const [from, to] = await run();
1644
+
1645
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1646
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1647
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1648
+ "genOta",
1649
+ "queryNextImageResponse",
1650
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1651
+ undefined,
1652
+ 1,
1653
+ );
1654
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1655
+ expect(dataSettings.requestTimeout).toStrictEqual(30 * 60 * 1000);
1656
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1657
+ expect(device.otaInProgress).toStrictEqual(false);
1658
+ });
1659
+
1660
+ it("handles filesystem as source URL", async () => {
1661
+ const fileName = "custom.ota";
1662
+ const image = mockImage(400);
1663
+ const filePath = writeMockImage(image, fileName);
1664
+ const meta: ZigbeeOtaImageMeta = {
1665
+ fileName,
1666
+ url: filePath,
1667
+ manufacturerCode: image.header.manufacturerCode,
1668
+ imageType: image.header.imageType,
1669
+ fileVersion: image.header.fileVersion,
1670
+ };
1671
+ fetchIndexEntries = [meta];
1672
+ firmwareBuffer = image.raw;
1673
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1674
+ fieldControl: 0,
1675
+ manufacturerCode: image.header.manufacturerCode,
1676
+ imageType: image.header.imageType,
1677
+ fileVersion: image.header.fileVersion + 1,
1678
+ };
1679
+ const baseSize = 60;
1680
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1681
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1682
+ const {run, device, endpoint} = createDevice({
1683
+ image,
1684
+ source: {url: filePath, downgrade: true},
1685
+ requestPayload,
1686
+ dataSettings,
1687
+ behavior: {baseSize, sendUpgradeEnd: true},
1688
+ });
1689
+ const [from, to] = await run();
1690
+
1691
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1692
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1693
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1694
+ "genOta",
1695
+ "queryNextImageResponse",
1696
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1697
+ undefined,
1698
+ 1,
1699
+ );
1700
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1701
+ expect(dataSettings.requestTimeout).toStrictEqual(150000);
1702
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1703
+ expect(device.otaInProgress).toStrictEqual(false);
1704
+ });
1705
+
1706
+ it("applies fallback timeouts when manufacturer is unknown", async () => {
1707
+ const image = mockImage(240, 0x1234);
1708
+ firmwareBuffer = image.raw;
1709
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1710
+ fieldControl: 0,
1711
+ manufacturerCode: image.header.manufacturerCode,
1712
+ imageType: image.header.imageType,
1713
+ fileVersion: image.header.fileVersion - 1,
1714
+ };
1715
+ const dataSettings: OtaDataSettings = {requestTimeout: 0, responseDelay: 0, baseSize: 0};
1716
+ const {run, device, endpoint} = createDevice({
1717
+ image,
1718
+ source: {url: "https://example.com/fw.ota"},
1719
+ requestPayload,
1720
+ dataSettings,
1721
+ behavior: {baseSize: 50, sendUpgradeEnd: true},
1722
+ });
1723
+ const [from, to] = await run();
1724
+
1725
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1726
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1727
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1728
+ "genOta",
1729
+ "queryNextImageResponse",
1730
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1731
+ undefined,
1732
+ 1,
1733
+ );
1734
+ expect(dataSettings.requestTimeout).toStrictEqual(150000);
1735
+ expect(dataSettings.baseSize).toStrictEqual(50);
1736
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1737
+ expect(device.otaInProgress).toStrictEqual(false);
1738
+ });
1739
+
1740
+ it("calls onProgress with round blocks", async () => {
1741
+ const fileName = "custom.ota";
1742
+ const image = mockImage(320);
1743
+ const filePath = writeMockImage(image, fileName);
1744
+ const meta: ZigbeeOtaImageMeta = {
1745
+ fileName,
1746
+ url: filePath,
1747
+ manufacturerCode: image.header.manufacturerCode,
1748
+ imageType: image.header.imageType,
1749
+ fileVersion: image.header.fileVersion,
1750
+ };
1751
+ fetchIndexEntries = [meta];
1752
+ firmwareBuffer = image.raw;
1753
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1754
+ fieldControl: 0,
1755
+ manufacturerCode: image.header.manufacturerCode,
1756
+ imageType: image.header.imageType,
1757
+ fileVersion: image.header.fileVersion - 1,
1758
+ };
1759
+ const baseSize = 40;
1760
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
1761
+ // Mock time to ensure progress is reported at regular intervals
1762
+ let currentTime = 0;
1763
+ const timeDelays = [
1764
+ 0, // constructor
1765
+ 100, // first block request (offset 0, no progress yet)
1766
+ 100,
1767
+ 30100, // second block request (offset 40, triggers 12.5% progress)
1768
+ 100,
1769
+ 30100, // third block request (offset 80, triggers 25% progress)
1770
+ 100,
1771
+ 30100, // fourth block request (offset 120, triggers 37.5% progress)
1772
+ 100,
1773
+ 30100, // fifth block request (offset 160, triggers 50% progress)
1774
+ 100,
1775
+ 30100, // sixth block request (offset 200, triggers 62.5% progress)
1776
+ 100,
1777
+ 30100, // seventh block request (offset 240, triggers 75% progress)
1778
+ 100,
1779
+ 30100, // eighth block request (offset 280, triggers 87.5% progress)
1780
+ 100,
1781
+ ];
1782
+ const nowSpy = vi.spyOn(performance, "now").mockImplementation(() => {
1783
+ const next = timeDelays.shift();
1784
+ // For any additional calls, add small time
1785
+ currentTime += next ?? 100;
1786
+
1787
+ return currentTime;
1788
+ });
1789
+
1790
+ const {run, device, onProgress} = createDevice({
1791
+ image,
1792
+ source: {url: "https://example.com/fw.ota"},
1793
+ requestPayload,
1794
+ dataSettings,
1795
+ behavior: {baseSize, sendUpgradeEnd: true},
1796
+ });
1797
+
1798
+ const [from, to] = await run();
1799
+ nowSpy.mockRestore();
1800
+
1801
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1802
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1803
+
1804
+ // With 320 bytes and 40-byte chunks, we have 8 blocks
1805
+ expect(onProgress).toHaveBeenNthCalledWith(1, 0, 0.4); // Initial call with estimate (non-mocked time, so tiny due to tiny # of blocks)
1806
+ expect(onProgress).toHaveBeenNthCalledWith(2, 12.5, 213); // After 1st block sent (40/320)
1807
+ expect(onProgress).toHaveBeenNthCalledWith(3, 25, 182); // After 2nd block sent (80/320)
1808
+ expect(onProgress).toHaveBeenNthCalledWith(4, 37.5, 151); // After 3rd block sent (120/320)
1809
+ expect(onProgress).toHaveBeenNthCalledWith(5, 50, 121); // After 4th block sent (160/320)
1810
+ expect(onProgress).toHaveBeenNthCalledWith(6, 62.5, 91); // After 5th block sent (200/320)
1811
+ expect(onProgress).toHaveBeenNthCalledWith(7, 75, 60); // After 6th block sent (240/320)
1812
+ expect(onProgress).toHaveBeenNthCalledWith(8, 87.5, 30); // After 7th block sent (280/320)
1813
+ expect(onProgress).toHaveBeenNthCalledWith(9, 100, 0); // Final completion
1814
+ expect(device.otaInProgress).toStrictEqual(false);
1815
+ });
1816
+
1817
+ it("calls onProgress with response delay (changes estimate)", async () => {
1818
+ const fileName = "custom.ota";
1819
+ const image = mockImage(320);
1820
+ const filePath = writeMockImage(image, fileName);
1821
+ const meta: ZigbeeOtaImageMeta = {
1822
+ fileName,
1823
+ url: filePath,
1824
+ manufacturerCode: image.header.manufacturerCode,
1825
+ imageType: image.header.imageType,
1826
+ fileVersion: image.header.fileVersion,
1827
+ };
1828
+ fetchIndexEntries = [meta];
1829
+ firmwareBuffer = image.raw;
1830
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1831
+ fieldControl: 0,
1832
+ manufacturerCode: image.header.manufacturerCode,
1833
+ imageType: image.header.imageType,
1834
+ fileVersion: image.header.fileVersion - 1,
1835
+ };
1836
+ const baseSize = 40;
1837
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 250, baseSize};
1838
+ // Mock time to ensure progress is reported at regular intervals
1839
+ let currentTime = 0;
1840
+ const timeDelays = [
1841
+ 0, // constructor
1842
+ 100, // first block request (offset 0, no progress yet)
1843
+ 100,
1844
+ 30100, // second block request (offset 40, triggers 12.5% progress)
1845
+ 100,
1846
+ 30100, // third block request (offset 80, triggers 25% progress)
1847
+ 100,
1848
+ 30100, // fourth block request (offset 120, triggers 37.5% progress)
1849
+ 100,
1850
+ 30100, // fifth block request (offset 160, triggers 50% progress)
1851
+ 100,
1852
+ 30100, // sixth block request (offset 200, triggers 62.5% progress)
1853
+ 100,
1854
+ 30100, // seventh block request (offset 240, triggers 75% progress)
1855
+ 100,
1856
+ 30100, // eighth block request (offset 280, triggers 87.5% progress)
1857
+ 100,
1858
+ ];
1859
+ const nowSpy = vi.spyOn(performance, "now").mockImplementation(() => {
1860
+ const next = timeDelays.shift();
1861
+ // For any additional calls, add small time
1862
+ currentTime += next ?? 100;
1863
+
1864
+ return currentTime;
1865
+ });
1866
+
1867
+ const {run, device, onProgress} = createDevice({
1868
+ image,
1869
+ source: {url: "https://example.com/fw.ota"},
1870
+ requestPayload,
1871
+ dataSettings,
1872
+ behavior: {baseSize, sendUpgradeEnd: true},
1873
+ });
1874
+
1875
+ const [from, to] = await run();
1876
+ nowSpy.mockRestore();
1877
+
1878
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1879
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1880
+
1881
+ // With 320 bytes and 40-byte chunks, we have 8 blocks
1882
+ expect(onProgress).toHaveBeenNthCalledWith(1, 0, 2); // Initial call with estimate (non-mocked time, so tiny due to tiny # of blocks)
1883
+ expect(onProgress).toHaveBeenNthCalledWith(2, 12.5, 213); // After 1st block sent (40/320)
1884
+ expect(onProgress).toHaveBeenNthCalledWith(3, 25, 182); // After 2nd block sent (80/320)
1885
+ expect(onProgress).toHaveBeenNthCalledWith(4, 37.5, 151); // After 3rd block sent (120/320)
1886
+ expect(onProgress).toHaveBeenNthCalledWith(5, 50, 121); // After 4th block sent (160/320)
1887
+ expect(onProgress).toHaveBeenNthCalledWith(6, 62.5, 91); // After 5th block sent (200/320)
1888
+ expect(onProgress).toHaveBeenNthCalledWith(7, 75, 60); // After 6th block sent (240/320)
1889
+ expect(onProgress).toHaveBeenNthCalledWith(8, 87.5, 30); // After 7th block sent (280/320)
1890
+ expect(onProgress).toHaveBeenNthCalledWith(9, 100, 0); // Final completion
1891
+ expect(device.otaInProgress).toStrictEqual(false);
1892
+ });
1893
+
1894
+ it("calls onProgress with non-round blocks", async () => {
1895
+ const fileName = "custom.ota";
1896
+ const image = mockImage(521);
1897
+ const filePath = writeMockImage(image, fileName);
1898
+ const meta: ZigbeeOtaImageMeta = {
1899
+ fileName,
1900
+ url: filePath,
1901
+ manufacturerCode: image.header.manufacturerCode,
1902
+ imageType: image.header.imageType,
1903
+ fileVersion: image.header.fileVersion,
1904
+ };
1905
+ fetchIndexEntries = [meta];
1906
+ firmwareBuffer = image.raw;
1907
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1908
+ fieldControl: 0,
1909
+ manufacturerCode: image.header.manufacturerCode,
1910
+ imageType: image.header.imageType,
1911
+ fileVersion: image.header.fileVersion - 1,
1912
+ };
1913
+ const baseSize = 64;
1914
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
1915
+ // Mock time to ensure progress is reported at regular intervals
1916
+ let currentTime = 0;
1917
+ const timeDelays = [
1918
+ 0, // constructor
1919
+ 100, // first block request (offset 0, no progress yet)
1920
+ 100,
1921
+ 30100, // second block request (offset 64, triggers 12.5% progress)
1922
+ 100,
1923
+ 30100, // third block request (offset 128, triggers 25% progress)
1924
+ 100,
1925
+ 30100, // fourth block request (offset 192, triggers 37.5% progress)
1926
+ 100,
1927
+ 30100, // fifth block request (offset 256, triggers 50% progress)
1928
+ 100,
1929
+ 30100, // sixth block request (offset 320, triggers 62.5% progress)
1930
+ 100,
1931
+ 30100, // seventh block request (offset 384, triggers 75% progress)
1932
+ 100,
1933
+ 30100, // eighth block request (offset 448, triggers 87.5% progress)
1934
+ 100,
1935
+ 30100, // ninth block request (offset 512, triggers 87.5% progress)
1936
+ 100,
1937
+ ];
1938
+ const nowSpy = vi.spyOn(performance, "now").mockImplementation(() => {
1939
+ const next = timeDelays.shift();
1940
+ // For any additional calls, add small time
1941
+ currentTime += next ?? 100;
1942
+
1943
+ return currentTime;
1944
+ });
1945
+
1946
+ const {run, device, onProgress} = createDevice({
1947
+ image,
1948
+ source: {url: "https://example.com/fw.ota"},
1949
+ requestPayload,
1950
+ dataSettings,
1951
+ behavior: {baseSize, sendUpgradeEnd: true},
1952
+ });
1953
+
1954
+ const [from, to] = await run();
1955
+ nowSpy.mockRestore();
1956
+
1957
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1958
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1959
+
1960
+ // With 521 bytes and 64-byte chunks, we have 8 blocks
1961
+ expect(onProgress).toHaveBeenNthCalledWith(1, 0, 0.45); // Initial call with estimate (non-mocked time, so tiny due to tiny # of blocks)
1962
+ expect(onProgress).toHaveBeenNthCalledWith(2, 12.28, 217); // After 1st block sent (64/521)
1963
+ expect(onProgress).toHaveBeenNthCalledWith(3, 24.57, 186); // After 2nd block sent (128/521)
1964
+ expect(onProgress).toHaveBeenNthCalledWith(4, 36.85, 156); // After 3rd block sent (192/521)
1965
+ expect(onProgress).toHaveBeenNthCalledWith(5, 49.14, 125); // After 4th block sent (256/521)
1966
+ expect(onProgress).toHaveBeenNthCalledWith(6, 61.42, 95); // After 5th block sent (320/521)
1967
+ expect(onProgress).toHaveBeenNthCalledWith(7, 73.7, 65); // After 6th block sent (384/521)
1968
+ expect(onProgress).toHaveBeenNthCalledWith(8, 85.99, 34); // After 7th block sent (448/521)
1969
+ expect(onProgress).toHaveBeenNthCalledWith(9, 98.27, 4); // After 7th block sent (512/521)
1970
+ expect(onProgress).toHaveBeenNthCalledWith(10, 100, 0); // Final completion
1971
+ expect(device.otaInProgress).toStrictEqual(false);
1972
+ });
1973
+
1974
+ it("throttles when response delay is configured", async () => {
1975
+ const fileName = "custom.ota";
1976
+ const image = mockImage(320);
1977
+ const filePath = writeMockImage(image, fileName);
1978
+ const meta: ZigbeeOtaImageMeta = {
1979
+ fileName,
1980
+ url: filePath,
1981
+ manufacturerCode: image.header.manufacturerCode,
1982
+ imageType: image.header.imageType,
1983
+ fileVersion: image.header.fileVersion,
1984
+ };
1985
+ fetchIndexEntries = [meta];
1986
+ firmwareBuffer = image.raw;
1987
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1988
+ fieldControl: 0,
1989
+ manufacturerCode: image.header.manufacturerCode,
1990
+ imageType: image.header.imageType,
1991
+ fileVersion: image.header.fileVersion - 1,
1992
+ };
1993
+ const baseSize = 40;
1994
+ const responseDelay = 25; // keep this low so we don't have to mock it
1995
+ const dataSettings: OtaDataSettings = {requestTimeout: 1000, responseDelay, baseSize};
1996
+ const startTime = performance.now();
1997
+ const {run, device} = createDevice({
1998
+ image,
1999
+ source: {url: "https://example.com/fw.ota"},
2000
+ requestPayload,
2001
+ dataSettings,
2002
+ behavior: {baseSize, sendUpgradeEnd: true},
2003
+ });
2004
+
2005
+ const [from, to] = await run();
2006
+ const elapsedTime = performance.now() - startTime;
2007
+
2008
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2009
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2010
+
2011
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
2012
+ const minimumExpectedTime = (expectedBlocks - 1) * responseDelay;
2013
+
2014
+ expect(elapsedTime).toBeGreaterThanOrEqual(minimumExpectedTime);
2015
+ expect(device.otaInProgress).toStrictEqual(false);
2016
+ });
2017
+
2018
+ it("cancels scheduled OTA when completed", async () => {
2019
+ const fileName = "custom.ota";
2020
+ const image = mockImage(320);
2021
+ const filePath = writeMockImage(image, fileName);
2022
+ const meta: ZigbeeOtaImageMeta = {
2023
+ fileName,
2024
+ url: filePath,
2025
+ manufacturerCode: image.header.manufacturerCode,
2026
+ imageType: image.header.imageType,
2027
+ fileVersion: image.header.fileVersion,
2028
+ };
2029
+ fetchIndexEntries = [meta];
2030
+ firmwareBuffer = image.raw;
2031
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2032
+ fieldControl: 0,
2033
+ manufacturerCode: image.header.manufacturerCode,
2034
+ imageType: image.header.imageType,
2035
+ fileVersion: image.header.fileVersion - 1,
2036
+ };
2037
+ const baseSize = 40;
2038
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
2039
+
2040
+ const {run, device} = createDevice({
2041
+ image,
2042
+ source: undefined,
2043
+ requestPayload,
2044
+ dataSettings,
2045
+ behavior: {baseSize, sendUpgradeEnd: true},
2046
+ });
2047
+
2048
+ device.scheduleOta({url: filePath});
2049
+
2050
+ expect(device.scheduledOta).toStrictEqual({url: filePath});
2051
+
2052
+ const [from, to] = await run();
2053
+
2054
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2055
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2056
+ expect(device.scheduledOta).toStrictEqual(undefined);
2057
+ expect(device.otaInProgress).toStrictEqual(false);
2058
+ });
2059
+
2060
+ it("keeps scheduled OTA on failure", async () => {
2061
+ const fileName = "custom.ota";
2062
+ const image = mockImage(320);
2063
+ const filePath = writeMockImage(image, fileName);
2064
+ const meta: ZigbeeOtaImageMeta = {
2065
+ fileName,
2066
+ url: filePath,
2067
+ manufacturerCode: image.header.manufacturerCode,
2068
+ imageType: image.header.imageType,
2069
+ fileVersion: image.header.fileVersion,
2070
+ };
2071
+ fetchIndexEntries = [meta];
2072
+ firmwareBuffer = image.raw;
2073
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2074
+ fieldControl: 0,
2075
+ manufacturerCode: image.header.manufacturerCode,
2076
+ imageType: image.header.imageType,
2077
+ fileVersion: image.header.fileVersion + 1,
2078
+ };
2079
+ const baseSize = 40;
2080
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
2081
+
2082
+ const {run, device} = createDevice({
2083
+ image,
2084
+ source: undefined,
2085
+ requestPayload,
2086
+ dataSettings,
2087
+ behavior: {baseSize, stopAfterBlocks: 1, sendUpgradeEnd: false},
2088
+ });
2089
+
2090
+ device.scheduleOta({url: filePath, downgrade: true});
2091
+
2092
+ expect(device.scheduledOta).toStrictEqual({url: filePath, downgrade: true});
2093
+
2094
+ await expect(run()).rejects.toThrow(/did not start\/finish firmware download/);
2095
+
2096
+ expect(device.scheduledOta).toStrictEqual({url: filePath, downgrade: true});
2097
+ expect(device.otaInProgress).toStrictEqual(false);
2098
+ });
2099
+
2100
+ it("uses custom firmware in dataDir", async () => {
2101
+ const fileName = "mycustom.ota";
2102
+ const image = mockImage(300, Zcl.ManufacturerCode.ABB);
2103
+ firmwareBuffer = image.raw;
2104
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2105
+ fieldControl: 0,
2106
+ manufacturerCode: image.header.manufacturerCode,
2107
+ imageType: image.header.imageType,
2108
+ fileVersion: image.header.fileVersion + 1,
2109
+ };
2110
+ const baseSize = 41;
2111
+ const {run, device} = createDevice({
2112
+ image,
2113
+ source: {url: "mycustom.ota", downgrade: true},
2114
+ requestPayload,
2115
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2116
+ behavior: {baseSize, sendUpgradeEnd: true},
2117
+ });
2118
+ const tempDataDir = fs.mkdtempSync("zh-ota");
2119
+
2120
+ tempDirs.push(tempDataDir);
2121
+ fs.writeFileSync(path.join(tempDataDir, fileName), image.raw);
2122
+ otaHelpers.setOtaConfiguration(tempDataDir, undefined);
2123
+
2124
+ const [from, to] = await run();
2125
+
2126
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2127
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2128
+ expect(device.otaInProgress).toStrictEqual(false);
2129
+ });
2130
+
2131
+ it("recovers from image block response failures", async () => {
2132
+ const [image] = await loadImage(OTA_FILES[2]);
2133
+ firmwareBuffer = image.raw;
2134
+ const baseSize = 64;
2135
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
2136
+ const dataSettings: OtaDataSettings = {requestTimeout: 100, responseDelay: 0, baseSize};
2137
+ const {run, device, endpoint, onProgress} = createDevice({
2138
+ image,
2139
+ source: {},
2140
+ dataSettings,
2141
+ behavior: {baseSize, sendUpgradeEnd: true, failBlockResponse: true},
2142
+ });
2143
+
2144
+ const [from, to] = await run();
2145
+
2146
+ expect(from.fileVersion).toStrictEqual(image.header.fileVersion - 1);
2147
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2148
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
2149
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2150
+ "genOta",
2151
+ "queryNextImageResponse",
2152
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
2153
+ undefined,
2154
+ 1,
2155
+ );
2156
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks + 1 /* 1 retried */);
2157
+ const calls = getResponses(endpoint, "imageBlockResponse");
2158
+ const lastOffset = (expectedBlocks - 1) * baseSize;
2159
+ const lastSize = image.header.totalImageSize - lastOffset;
2160
+ const retryBlock = {fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)};
2161
+ expect(calls[0][2]).toEqual(expect.objectContaining(retryBlock));
2162
+ expect(calls[1][2]).toEqual(expect.objectContaining(retryBlock));
2163
+ expect(calls[2][2]).toEqual(
2164
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
2165
+ );
2166
+ expect(calls[3][2]).toEqual(
2167
+ expect.objectContaining({fileOffset: baseSize * 2, dataSize: baseSize, data: image.raw.subarray(baseSize * 2, baseSize * 3)}),
2168
+ );
2169
+ expect(calls[expectedBlocks][2]).toEqual(
2170
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
2171
+ );
2172
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
2173
+ expect(onProgress).toHaveBeenCalled();
2174
+ expect(device.otaInProgress).toStrictEqual(false);
2175
+ });
2176
+
2177
+ it("prevents running two OTAs on same device", async () => {
2178
+ const fileName = "custom.ota";
2179
+ const image = mockImage(320);
2180
+ const filePath = writeMockImage(image, fileName);
2181
+ const meta: ZigbeeOtaImageMeta = {
2182
+ fileName,
2183
+ url: filePath,
2184
+ manufacturerCode: image.header.manufacturerCode,
2185
+ imageType: image.header.imageType,
2186
+ fileVersion: image.header.fileVersion,
2187
+ };
2188
+ fetchIndexEntries = [meta];
2189
+ firmwareBuffer = image.raw;
2190
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2191
+ fieldControl: 0,
2192
+ manufacturerCode: image.header.manufacturerCode,
2193
+ imageType: image.header.imageType,
2194
+ fileVersion: image.header.fileVersion - 1,
2195
+ };
2196
+ const baseSize = 40;
2197
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
2198
+
2199
+ const {run, device} = createDevice({
2200
+ image,
2201
+ source: {url: filePath},
2202
+ requestPayload,
2203
+ dataSettings,
2204
+ behavior: {baseSize, sendUpgradeEnd: true},
2205
+ });
2206
+
2207
+ const p1 = run();
2208
+
2209
+ expect(device.otaInProgress).toStrictEqual(true);
2210
+
2211
+ const p2 = run();
2212
+ const [from, to] = await p1;
2213
+
2214
+ await expect(p2).rejects.toThrow("OTA already in progress for 0x1");
2215
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2216
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2217
+ expect(device.otaInProgress).toStrictEqual(false);
2218
+ });
2219
+
2220
+ it("fails when check fails", async () => {
2221
+ const fileName = OTA_FILES[0];
2222
+ const [image] = await loadImage(fileName);
2223
+ firmwareBuffer = image.raw;
2224
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2225
+ fieldControl: 0,
2226
+ manufacturerCode: image.header.manufacturerCode,
2227
+ imageType: image.header.imageType,
2228
+ fileVersion: image.header.fileVersion + 1,
2229
+ };
2230
+ const baseSize = 64;
2231
+ const {device, run} = createDevice({
2232
+ image,
2233
+ source: {downgrade: true},
2234
+ requestPayload,
2235
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2236
+ behavior: {baseSize, sendUpgradeEnd: true},
2237
+ });
2238
+
2239
+ vi.spyOn(device, "checkOta").mockRejectedValueOnce(new Error("check-fail"));
2240
+
2241
+ await expect(run()).rejects.toThrow("check-fail");
2242
+ expect(device.otaInProgress).toStrictEqual(false);
2243
+ });
2244
+
2245
+ it("fails when device stops sending data requests", async () => {
2246
+ const fileName = OTA_FILES[0];
2247
+ const [image] = await loadImage(fileName);
2248
+ firmwareBuffer = image.raw;
2249
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2250
+ fieldControl: 0,
2251
+ manufacturerCode: image.header.manufacturerCode,
2252
+ imageType: image.header.imageType,
2253
+ fileVersion: image.header.fileVersion - 1,
2254
+ };
2255
+ const baseSize = 50;
2256
+ const expectedBlocks = 1;
2257
+ const {run, device, endpoint} = createDevice({
2258
+ image,
2259
+ source: {},
2260
+ requestPayload,
2261
+ dataSettings: {requestTimeout: 30, responseDelay: 0, baseSize},
2262
+ behavior: {baseSize, stopAfterBlocks: 1, sendUpgradeEnd: false},
2263
+ });
2264
+
2265
+ await expect(run()).rejects.toThrow(/did not start\/finish firmware download/);
2266
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
2267
+ expect(device.otaInProgress).toStrictEqual(false);
2268
+ });
2269
+
2270
+ it("fails when upgrade end never arrives", async () => {
2271
+ const fileName = OTA_FILES[0];
2272
+ const [image] = await loadImage(fileName);
2273
+ firmwareBuffer = image.raw;
2274
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2275
+ fieldControl: 0,
2276
+ manufacturerCode: image.header.manufacturerCode,
2277
+ imageType: image.header.imageType,
2278
+ fileVersion: image.header.fileVersion - 1,
2279
+ };
2280
+ const baseSize = 30;
2281
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
2282
+
2283
+ const {run, device, endpoint} = createDevice({
2284
+ image,
2285
+ source: {},
2286
+ requestPayload,
2287
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2288
+ behavior: {baseSize, sendUpgradeEnd: false},
2289
+ });
2290
+
2291
+ await expect(run()).rejects.toThrow(/did not start\/finish firmware download/);
2292
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
2293
+ expect(device.otaInProgress).toStrictEqual(false);
2294
+ });
2295
+
2296
+ it("fails when device sends upgrade end request with INVALID_IMAGE after image fully sent", async () => {
2297
+ const fileName = OTA_FILES[1];
2298
+ const [image] = await loadImage(fileName);
2299
+ firmwareBuffer = image.raw;
2300
+ const baseSize = 64;
2301
+ const {run, device, endpoint} = createDevice({
2302
+ image,
2303
+ source: {},
2304
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2305
+ behavior: {baseSize, sendUpgradeEnd: true, upgradeEndStatus: Zcl.Status.INVALID_IMAGE},
2306
+ });
2307
+
2308
+ await expect(run()).rejects.toThrow(/INVALID_IMAGE/);
2309
+ expect(endpoint.defaultResponse).toHaveBeenCalledWith(
2310
+ UPGRADE_END_REQUEST_ID,
2311
+ Zcl.Status.SUCCESS,
2312
+ Zcl.Clusters.genOta.ID,
2313
+ expect.any(Number),
2314
+ );
2315
+ expect(device.otaInProgress).toStrictEqual(false);
2316
+ });
2317
+
2318
+ it("fails when device sends upgrade end request with ABORT after a certain number of blocks", async () => {
2319
+ const fileName = OTA_FILES[0];
2320
+ const [image] = await loadImage(fileName);
2321
+ firmwareBuffer = image.raw;
2322
+ const baseSize = 64;
2323
+ const {run, device, endpoint} = createDevice({
2324
+ image,
2325
+ source: {},
2326
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2327
+ behavior: {
2328
+ baseSize,
2329
+ sendUpgradeEnd: true,
2330
+ upgradeEndStatus: Zcl.Status.ABORT,
2331
+ upgradeEndAfterBlocks: 2,
2332
+ },
2333
+ });
2334
+
2335
+ await expect(run()).rejects.toThrow(/ABORT/);
2336
+ expect(endpoint.defaultResponse).toHaveBeenCalledWith(
2337
+ UPGRADE_END_REQUEST_ID,
2338
+ Zcl.Status.SUCCESS,
2339
+ Zcl.Clusters.genOta.ID,
2340
+ expect.any(Number),
2341
+ );
2342
+ expect(device.otaInProgress).toStrictEqual(false);
2343
+ });
2344
+
2345
+ it("fails when image notify fails", async () => {
2346
+ const fileName = OTA_FILES[0];
2347
+ const [image] = await loadImage(fileName);
2348
+ firmwareBuffer = image.raw;
2349
+ const baseSize = 64;
2350
+ const {run, device, endpoint} = createDevice({
2351
+ image,
2352
+ source: {},
2353
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2354
+ behavior: {baseSize, sendUpgradeEnd: true},
2355
+ });
2356
+ endpoint.commandResponse = vi.fn((_cluster, command) => {
2357
+ if (command === "imageNotify") {
2358
+ return Promise.reject(new Error("image-notify-fail"));
2359
+ }
2360
+
2361
+ return Promise.resolve();
2362
+ });
2363
+
2364
+ await expect(run()).rejects.toThrow("Device didn't respond to OTA request");
2365
+ expect(device.otaInProgress).toStrictEqual(false);
2366
+ });
2367
+
2368
+ it("fails when upgrade end response fails", async () => {
2369
+ const fileName = OTA_FILES[0];
2370
+ const [image] = await loadImage(fileName);
2371
+ firmwareBuffer = image.raw;
2372
+ const baseSize = 64;
2373
+ const {run, device, endpoint} = createDevice({
2374
+ image,
2375
+ source: {},
2376
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2377
+ behavior: {baseSize, sendUpgradeEnd: true},
2378
+ });
2379
+ endpoint.commandResponse = vi.fn((_cluster, command) => {
2380
+ if (command === "upgradeEndResponse") {
2381
+ return Promise.reject(new Error("upgrade-end-fail"));
2382
+ }
2383
+
2384
+ return Promise.resolve();
2385
+ });
2386
+
2387
+ await expect(run()).rejects.toThrow(/upgrade end response failed/i);
2388
+ expect(device.otaInProgress).toStrictEqual(false);
2389
+ });
2390
+
2391
+ it("fails when query next image response fails", async () => {
2392
+ const fileName = OTA_FILES[0];
2393
+ const [image] = await loadImage(fileName);
2394
+ firmwareBuffer = image.raw;
2395
+ const baseSize = 50;
2396
+ const {run, device, endpoint} = createDevice({
2397
+ image,
2398
+ source: {},
2399
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2400
+ behavior: {baseSize, sendUpgradeEnd: true},
2401
+ });
2402
+ endpoint.commandResponse = vi.fn((_cluster, command) => {
2403
+ if (command === "queryNextImageResponse") {
2404
+ return Promise.reject(new Error("query-next-image-fail"));
2405
+ }
2406
+
2407
+ return Promise.resolve();
2408
+ });
2409
+
2410
+ await expect(run()).rejects.toThrow("query-next-image-fail");
2411
+ expect(device.otaInProgress).toStrictEqual(false);
2412
+ });
2413
+
2414
+ it("does not throw on failed default response after non-success upgrade end request", async () => {
2415
+ const fileName = OTA_FILES[0];
2416
+ const [image] = await loadImage(fileName);
2417
+ firmwareBuffer = image.raw;
2418
+ const baseSize = 64;
2419
+ const {run, device, endpoint} = createDevice({
2420
+ image,
2421
+ source: {},
2422
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2423
+ behavior: {baseSize, sendUpgradeEnd: true, upgradeEndStatus: Zcl.Status.ABORT},
2424
+ });
2425
+ const epDefaultResponse = endpoint.defaultResponse as unknown as ReturnType<typeof vi.fn>;
2426
+
2427
+ epDefaultResponse.mockRejectedValueOnce(new Error("default-response-failed"));
2428
+
2429
+ await expect(run()).rejects.toThrow(/ABORT/);
2430
+ expect(epDefaultResponse).toHaveBeenCalledTimes(1);
2431
+ expect(epDefaultResponse.mock.settledResults[0]).toStrictEqual({type: "rejected", value: new Error("default-response-failed")});
2432
+ expect(device.otaInProgress).toStrictEqual(false);
2433
+ });
2434
+
2435
+ it("allows bypassing version check with force meta", async () => {
2436
+ const fileName = "custom.ota";
2437
+ const image = mockImage(300);
2438
+ firmwareBuffer = image.raw;
2439
+ const filePath = writeMockImage(image, fileName);
2440
+ const meta: ZigbeeOtaImageMeta = {
2441
+ fileName,
2442
+ url: filePath,
2443
+ manufacturerCode: image.header.manufacturerCode,
2444
+ imageType: image.header.imageType,
2445
+ fileVersion: image.header.fileVersion,
2446
+ force: true,
2447
+ };
2448
+ fetchIndexEntries = [meta];
2449
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2450
+ fieldControl: 0,
2451
+ manufacturerCode: image.header.manufacturerCode,
2452
+ imageType: image.header.imageType,
2453
+ fileVersion: image.header.fileVersion,
2454
+ };
2455
+ const baseSize = 25;
2456
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2457
+ const {run, device} = createDevice({
2458
+ image,
2459
+ source: {},
2460
+ requestPayload,
2461
+ dataSettings,
2462
+ behavior: {baseSize, sendUpgradeEnd: true},
2463
+ });
2464
+ const [from, to] = await run();
2465
+
2466
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2467
+ expect(to?.fileVersion).toStrictEqual(requestPayload.fileVersion);
2468
+ expect(device.otaInProgress).toStrictEqual(false);
2469
+ });
2470
+
2471
+ it("returns NO_IMAGE_AVAILABLE when device version matches available in repo", async () => {
2472
+ const [image] = await loadImage(OTA_FILES[0]);
2473
+ firmwareBuffer = image.raw;
2474
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2475
+ fieldControl: 0,
2476
+ manufacturerCode: image.header.manufacturerCode,
2477
+ imageType: image.header.imageType,
2478
+ fileVersion: image.header.fileVersion,
2479
+ };
2480
+ const baseSize = 16;
2481
+ const logErrorSpy = vi.spyOn(logger, "error");
2482
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2483
+ const {run, device, endpoint} = createDevice({
2484
+ image,
2485
+ source: {},
2486
+ requestPayload,
2487
+ dataSettings,
2488
+ behavior: {baseSize, sendUpgradeEnd: true},
2489
+ });
2490
+ const [from, to] = await run();
2491
+
2492
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2493
+ expect(to).toBeUndefined();
2494
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2495
+ "genOta",
2496
+ "queryNextImageResponse",
2497
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2498
+ undefined,
2499
+ 1,
2500
+ );
2501
+ expect(logErrorSpy).not.toHaveBeenCalled();
2502
+ expect(device.otaInProgress).toStrictEqual(false);
2503
+ });
2504
+
2505
+ it("returns NO_IMAGE_AVAILABLE when device version is above available in repo (upgrade)", async () => {
2506
+ const [image] = await loadImage(OTA_FILES[0]);
2507
+ firmwareBuffer = image.raw;
2508
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2509
+ fieldControl: 0,
2510
+ manufacturerCode: image.header.manufacturerCode,
2511
+ imageType: image.header.imageType,
2512
+ fileVersion: image.header.fileVersion + 1,
2513
+ };
2514
+ const baseSize = 16;
2515
+ const logErrorSpy = vi.spyOn(logger, "error");
2516
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2517
+ const {run, device, endpoint} = createDevice({
2518
+ image,
2519
+ source: {},
2520
+ requestPayload,
2521
+ dataSettings,
2522
+ behavior: {baseSize, sendUpgradeEnd: true},
2523
+ });
2524
+ const [from, to] = await run();
2525
+
2526
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2527
+ expect(to).toBeUndefined();
2528
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2529
+ "genOta",
2530
+ "queryNextImageResponse",
2531
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2532
+ undefined,
2533
+ 1,
2534
+ );
2535
+ expect(logErrorSpy).not.toHaveBeenCalled();
2536
+ expect(device.otaInProgress).toStrictEqual(false);
2537
+ });
2538
+
2539
+ it("returns NO_IMAGE_AVAILABLE when device version is below available in repo (downgrade)", async () => {
2540
+ const [image] = await loadImage(OTA_FILES[0]);
2541
+ firmwareBuffer = image.raw;
2542
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2543
+ fieldControl: 0,
2544
+ manufacturerCode: image.header.manufacturerCode,
2545
+ imageType: image.header.imageType,
2546
+ fileVersion: image.header.fileVersion - 1,
2547
+ };
2548
+ const baseSize = 16;
2549
+ const logErrorSpy = vi.spyOn(logger, "error");
2550
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2551
+ const {run, device, endpoint} = createDevice({
2552
+ image,
2553
+ source: {downgrade: true},
2554
+ requestPayload,
2555
+ dataSettings,
2556
+ behavior: {baseSize, sendUpgradeEnd: true},
2557
+ });
2558
+ const [from, to] = await run();
2559
+
2560
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2561
+ expect(to).toBeUndefined();
2562
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2563
+ "genOta",
2564
+ "queryNextImageResponse",
2565
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2566
+ undefined,
2567
+ 1,
2568
+ );
2569
+ expect(logErrorSpy).not.toHaveBeenCalled();
2570
+ expect(device.otaInProgress).toStrictEqual(false);
2571
+ });
2572
+
2573
+ it("returns NO_IMAGE_AVAILABLE when device version matches available at given URL", async () => {
2574
+ const [image] = await loadImage(OTA_FILES[0]);
2575
+ firmwareBuffer = image.raw;
2576
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2577
+ fieldControl: 0,
2578
+ manufacturerCode: image.header.manufacturerCode,
2579
+ imageType: image.header.imageType,
2580
+ fileVersion: image.header.fileVersion,
2581
+ };
2582
+ const baseSize = 16;
2583
+ const logErrorSpy = vi.spyOn(logger, "error");
2584
+ const logDebugSpy = vi.spyOn(logger, "debug");
2585
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2586
+ const {run, device, endpoint} = createDevice({
2587
+ image,
2588
+ source: {url: "https://example.com/fw.ota"},
2589
+ requestPayload,
2590
+ dataSettings,
2591
+ behavior: {baseSize, sendUpgradeEnd: true},
2592
+ });
2593
+ const [from, to] = await run();
2594
+
2595
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2596
+ expect(to).toBeUndefined();
2597
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2598
+ "genOta",
2599
+ "queryNextImageResponse",
2600
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2601
+ undefined,
2602
+ 1,
2603
+ );
2604
+ expect(logErrorSpy).not.toHaveBeenCalled();
2605
+ const calls = logDebugSpy.mock.calls.map((c) => (typeof c[0] === "string" ? c[0] : c[0]()));
2606
+ expect(calls[0]).toMatch("Downloading firmware image from 'https://example.com/fw.ota'");
2607
+ expect(calls[1]).toMatch("Parsed image from 'https://example.com/fw.ota' for 0x1");
2608
+ expect(device.otaInProgress).toStrictEqual(false);
2609
+ });
2610
+
2611
+ it("returns NO_IMAGE_AVAILABLE when parsing repo firmware fails", async () => {
2612
+ const [image] = await loadImage(OTA_FILES[0]);
2613
+ firmwareBuffer = image.raw;
2614
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2615
+ fieldControl: 0,
2616
+ manufacturerCode: image.header.manufacturerCode,
2617
+ imageType: image.header.imageType,
2618
+ fileVersion: image.header.fileVersion - 1,
2619
+ };
2620
+ const baseSize = 16;
2621
+ const logErrorSpy = vi.spyOn(logger, "error");
2622
+ const parseOtaImageSpy = vi.spyOn(otaHelpers, "parseOtaImage").mockImplementation(() => {
2623
+ throw new Error("fail");
2624
+ });
2625
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2626
+ const {run, device, endpoint} = createDevice({
2627
+ image,
2628
+ source: {},
2629
+ requestPayload,
2630
+ dataSettings,
2631
+ behavior: {baseSize, sendUpgradeEnd: true},
2632
+ });
2633
+ const [from, to] = await run();
2634
+
2635
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2636
+ expect(to).toBeUndefined();
2637
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2638
+ "genOta",
2639
+ "queryNextImageResponse",
2640
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2641
+ undefined,
2642
+ 1,
2643
+ );
2644
+ expect(logErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse OTA image"), "zh:controller:device");
2645
+ expect(device.otaInProgress).toStrictEqual(false);
2646
+
2647
+ parseOtaImageSpy.mockRestore();
2648
+ });
2649
+
2650
+ it("returns NO_IMAGE_AVAILABLE when parsing custom firmware fails", async () => {
2651
+ const [image] = await loadImage(OTA_FILES[0]);
2652
+ firmwareBuffer = Buffer.from(image.raw);
2653
+ firmwareBuffer[0] = 0xff;
2654
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2655
+ fieldControl: 0,
2656
+ manufacturerCode: image.header.manufacturerCode,
2657
+ imageType: image.header.imageType,
2658
+ fileVersion: image.header.fileVersion - 1,
2659
+ };
2660
+ const baseSize = 16;
2661
+ const logErrorSpy = vi.spyOn(logger, "error");
2662
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2663
+ const {run, device, endpoint} = createDevice({
2664
+ image,
2665
+ source: {url: "https://example.com/fw.ota"},
2666
+ requestPayload,
2667
+ dataSettings,
2668
+ behavior: {baseSize, sendUpgradeEnd: true},
2669
+ });
2670
+ const [from, to] = await run();
2671
+
2672
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2673
+ expect(to).toBeUndefined();
2674
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2675
+ "genOta",
2676
+ "queryNextImageResponse",
2677
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2678
+ undefined,
2679
+ 1,
2680
+ );
2681
+ expect(logErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse OTA image"), "zh:controller:device");
2682
+ expect(device.otaInProgress).toStrictEqual(false);
2683
+ });
2684
+
2685
+ it("returns NO_IMAGE_AVAILABLE when parsing custom firmware in dataDir fails", async () => {
2686
+ const fileName = "mycustom.ota";
2687
+ const image = mockImage(300, Zcl.ManufacturerCode.ABB);
2688
+ firmwareBuffer = image.raw;
2689
+ firmwareBuffer[0] = 0xff;
2690
+ const baseSize = 41;
2691
+ const logErrorSpy = vi.spyOn(logger, "error");
2692
+ const {run, device, endpoint} = createDevice({
2693
+ image,
2694
+ source: {url: "mycustom.ota", downgrade: true},
2695
+ requestPayload: undefined,
2696
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2697
+ behavior: {baseSize, sendUpgradeEnd: true},
2698
+ });
2699
+ const tempDataDir = fs.mkdtempSync("zh-ota");
2700
+
2701
+ tempDirs.push(tempDataDir);
2702
+ fs.writeFileSync(path.join(tempDataDir, fileName), image.raw);
2703
+ otaHelpers.setOtaConfiguration(tempDataDir, undefined);
2704
+
2705
+ const [from, to] = await run();
2706
+
2707
+ expect(from.fileVersion).toStrictEqual(image.header.fileVersion + 1);
2708
+ expect(to).toBeUndefined();
2709
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2710
+ "genOta",
2711
+ "queryNextImageResponse",
2712
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2713
+ undefined,
2714
+ 1,
2715
+ );
2716
+ expect(logErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse OTA image"), "zh:controller:device");
2717
+ expect(device.otaInProgress).toStrictEqual(false);
2718
+ });
2719
+
2720
+ it("returns NO_IMAGE_AVAILABLE when firmware fetching fails", async () => {
2721
+ fetchMock.mockResolvedValueOnce(fetchMockIndex).mockResolvedValueOnce(fetchMockFail);
2722
+
2723
+ const fileName = OTA_FILES[0];
2724
+ const [image] = await loadImage(fileName);
2725
+ const meta: ZigbeeOtaImageMeta = {
2726
+ fileName,
2727
+ url: "https://example.com/custom.ota",
2728
+ manufacturerCode: image.header.manufacturerCode,
2729
+ imageType: image.header.imageType,
2730
+ fileVersion: image.header.fileVersion,
2731
+ };
2732
+ fetchIndexEntries = [meta];
2733
+ firmwareBuffer = image.raw;
2734
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2735
+ fieldControl: 0,
2736
+ manufacturerCode: image.header.manufacturerCode,
2737
+ imageType: image.header.imageType,
2738
+ fileVersion: image.header.fileVersion - 1,
2739
+ };
2740
+ const baseSize = 16;
2741
+ const logErrorSpy = vi.spyOn(logger, "error");
2742
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2743
+ const {run, device, endpoint} = createDevice({
2744
+ image,
2745
+ source: {},
2746
+ requestPayload,
2747
+ dataSettings,
2748
+ behavior: {baseSize, sendUpgradeEnd: true},
2749
+ });
2750
+ const [from, to] = await run();
2751
+
2752
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2753
+ expect(to).toBeUndefined();
2754
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2755
+ "genOta",
2756
+ "queryNextImageResponse",
2757
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2758
+ undefined,
2759
+ 1,
2760
+ );
2761
+ expect(logErrorSpy).toHaveBeenCalledWith(
2762
+ expect.stringContaining("Invalid response from https://example.com/custom.ota status=403"),
2763
+ "zh:controller:device",
2764
+ );
2765
+ expect(device.otaInProgress).toStrictEqual(false);
2766
+ });
2767
+
2768
+ it("returns NO_IMAGE_AVAILABLE when custom file fails checksum", async () => {
2769
+ const fileName = OTA_FILES[0];
2770
+ const [image, filePath] = await loadImage(fileName);
2771
+ const meta: ZigbeeOtaImageMeta = {
2772
+ fileName,
2773
+ url: filePath,
2774
+ manufacturerCode: image.header.manufacturerCode,
2775
+ imageType: image.header.imageType,
2776
+ fileVersion: image.header.fileVersion,
2777
+ sha512: "invalid",
2778
+ };
2779
+ fetchIndexEntries = [meta];
2780
+ firmwareBuffer = image.raw;
2781
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2782
+ fieldControl: 0,
2783
+ manufacturerCode: image.header.manufacturerCode,
2784
+ imageType: image.header.imageType,
2785
+ fileVersion: image.header.fileVersion - 1,
2786
+ };
2787
+ const baseSize = 43;
2788
+ const logErrorSpy = vi.spyOn(logger, "error");
2789
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2790
+ const {run, device, endpoint} = createDevice({
2791
+ image,
2792
+ source: {},
2793
+ requestPayload,
2794
+ dataSettings,
2795
+ behavior: {baseSize, sendUpgradeEnd: true},
2796
+ });
2797
+ const [from, to] = await run();
2798
+
2799
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2800
+ expect(to).toBeUndefined();
2801
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2802
+ "genOta",
2803
+ "queryNextImageResponse",
2804
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2805
+ undefined,
2806
+ 1,
2807
+ );
2808
+ expect(logErrorSpy).toHaveBeenCalledWith(expect.stringContaining("File checksum validation failed"), "zh:controller:device");
2809
+ expect(device.otaInProgress).toStrictEqual(false);
2810
+ });
2811
+
2812
+ it("returns NO_IMAGE_AVAILABLE when index has no entries", async () => {
2813
+ fetchIndexEntries = [];
2814
+ const [image] = await loadImage(OTA_FILES[1]);
2815
+ firmwareBuffer = image.raw;
2816
+ const dataSettings: OtaDataSettings = {requestTimeout: 0, responseDelay: 0, baseSize: 32};
2817
+ const {run, device, endpoint} = createDevice({
2818
+ image,
2819
+ source: {},
2820
+ dataSettings,
2821
+ behavior: {baseSize: 32, sendUpgradeEnd: true},
2822
+ });
2823
+
2824
+ const [from, to] = await run();
2825
+
2826
+ expect(from.fileVersion).toStrictEqual(image.header.fileVersion - 1);
2827
+ expect(to).toBeUndefined();
2828
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2829
+ "genOta",
2830
+ "queryNextImageResponse",
2831
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2832
+ undefined,
2833
+ 1,
2834
+ );
2835
+ expect(device.otaInProgress).toStrictEqual(false);
2836
+ });
2837
+ });
2838
+
2839
+ describe("Schedules / Unschedules", () => {
2840
+ it("schedules OTA request", () => {
2841
+ const {device} = createSimpleDevice({});
2842
+
2843
+ device.scheduleOta({url: "first"});
2844
+
2845
+ expect(device.scheduledOta).toStrictEqual({url: "first"});
2846
+ });
2847
+
2848
+ it("replaces previously-scheduled OTA request", () => {
2849
+ const {device} = createSimpleDevice({});
2850
+
2851
+ device.scheduleOta({url: "first"});
2852
+
2853
+ expect(device.scheduledOta).toStrictEqual({url: "first"});
2854
+
2855
+ device.scheduleOta({url: "second", downgrade: true});
2856
+
2857
+ expect(device.scheduledOta).toStrictEqual({url: "second", downgrade: true});
2858
+ });
2859
+
2860
+ it("unschedules OTA when present", () => {
2861
+ const {device} = createSimpleDevice({});
2862
+
2863
+ device.scheduleOta({url: "upgrade"});
2864
+
2865
+ expect(device.scheduledOta).toStrictEqual({url: "upgrade"});
2866
+
2867
+ device.unscheduleOta();
2868
+
2869
+ expect(device.scheduledOta).toStrictEqual(undefined);
2870
+ });
2871
+
2872
+ it("handles unscheduling when scheduled not present", () => {
2873
+ const {device} = createSimpleDevice({});
2874
+
2875
+ device.unscheduleOta();
2876
+
2877
+ expect(device.scheduledOta).toStrictEqual(undefined);
2878
+ });
2879
+ });
2880
+
2881
+ describe("Finds image", () => {
2882
+ // tests for each possibility in `findMatchingOtaImage`
2883
+
2884
+ it.each(OTA_FILES)("finds match by spec (%s)", async (fileName) => {
2885
+ const [image] = await loadImage(fileName);
2886
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2887
+ fieldControl: 0,
2888
+ manufacturerCode: image.header.manufacturerCode,
2889
+ imageType: image.header.imageType,
2890
+ fileVersion: image.header.fileVersion,
2891
+ };
2892
+ const {device} = createSimpleDevice({});
2893
+
2894
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
2895
+ fileName,
2896
+ manufacturerCode: image.header.manufacturerCode,
2897
+ imageType: image.header.imageType,
2898
+ fileVersion: image.header.fileVersion,
2899
+ });
2900
+ });
2901
+
2902
+ it("within file version range", async () => {
2903
+ const fileName = "custom.ota";
2904
+ const image = mockImage(300, undefined, undefined, 4);
2905
+ const meta: ZigbeeOtaImageMeta = {
2906
+ fileName,
2907
+ url: fileName,
2908
+ manufacturerCode: image.header.manufacturerCode,
2909
+ imageType: image.header.imageType,
2910
+ fileVersion: image.header.fileVersion,
2911
+ minFileVersion: 3,
2912
+ maxFileVersion: 5,
2913
+ };
2914
+ fetchIndexEntries.push(
2915
+ {
2916
+ ...meta,
2917
+ minFileVersion: 2,
2918
+ maxFileVersion: 3,
2919
+ fileName: "not this",
2920
+ },
2921
+ meta,
2922
+ {
2923
+ ...meta,
2924
+ minFileVersion: 4,
2925
+ maxFileVersion: 6,
2926
+ fileName: "not that",
2927
+ },
2928
+ );
2929
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2930
+ fieldControl: 0,
2931
+ manufacturerCode: image.header.manufacturerCode,
2932
+ imageType: image.header.imageType,
2933
+ fileVersion: image.header.fileVersion,
2934
+ };
2935
+ const {device} = createSimpleDevice({modelID: "", manufacturerName: "", meta: {}});
2936
+
2937
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
2938
+ fileName,
2939
+ manufacturerCode: image.header.manufacturerCode,
2940
+ imageType: image.header.imageType,
2941
+ fileVersion: image.header.fileVersion,
2942
+ minFileVersion: 3,
2943
+ maxFileVersion: 5,
2944
+ });
2945
+ });
2946
+
2947
+ it("by minFileVersion", async () => {
2948
+ const fileName = "custom.ota";
2949
+ const image = mockImage(300, undefined, undefined, 4);
2950
+ const meta: ZigbeeOtaImageMeta = {
2951
+ fileName,
2952
+ url: fileName,
2953
+ manufacturerCode: image.header.manufacturerCode,
2954
+ imageType: image.header.imageType,
2955
+ fileVersion: image.header.fileVersion,
2956
+ minFileVersion: 3,
2957
+ };
2958
+ fetchIndexEntries.push({...meta, minFileVersion: 5, fileName: "not this"}, meta, {...meta, minFileVersion: 6, fileName: "not that"});
2959
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2960
+ fieldControl: 0,
2961
+ manufacturerCode: image.header.manufacturerCode,
2962
+ imageType: image.header.imageType,
2963
+ fileVersion: image.header.fileVersion,
2964
+ };
2965
+ const {device} = createSimpleDevice({modelID: "", manufacturerName: "", meta: {}});
2966
+
2967
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
2968
+ fileName,
2969
+ manufacturerCode: image.header.manufacturerCode,
2970
+ imageType: image.header.imageType,
2971
+ fileVersion: image.header.fileVersion,
2972
+ minFileVersion: 3,
2973
+ });
2974
+ });
2975
+
2976
+ it("by maxFileVersion", async () => {
2977
+ const fileName = "custom.ota";
2978
+ const image = mockImage(300, undefined, undefined, 4);
2979
+ const meta: ZigbeeOtaImageMeta = {
2980
+ fileName,
2981
+ url: fileName,
2982
+ manufacturerCode: image.header.manufacturerCode,
2983
+ imageType: image.header.imageType,
2984
+ fileVersion: image.header.fileVersion,
2985
+ maxFileVersion: 5,
2986
+ };
2987
+ fetchIndexEntries.push({...meta, maxFileVersion: 3, fileName: "not this"}, meta, {...meta, maxFileVersion: 9, fileName: "not that"});
2988
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2989
+ fieldControl: 0,
2990
+ manufacturerCode: image.header.manufacturerCode,
2991
+ imageType: image.header.imageType,
2992
+ fileVersion: image.header.fileVersion,
2993
+ };
2994
+ const {device} = createSimpleDevice({modelID: "", manufacturerName: "", meta: {}});
2995
+
2996
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
2997
+ fileName,
2998
+ manufacturerCode: image.header.manufacturerCode,
2999
+ imageType: image.header.imageType,
3000
+ fileVersion: image.header.fileVersion,
3001
+ maxFileVersion: 5,
3002
+ });
3003
+ });
3004
+
3005
+ it("by modelId", async () => {
3006
+ const fileName = "custom.ota";
3007
+ const image = mockImage(300, undefined, undefined, 4);
3008
+ const meta: ZigbeeOtaImageMeta = {
3009
+ fileName,
3010
+ url: fileName,
3011
+ manufacturerCode: image.header.manufacturerCode,
3012
+ imageType: image.header.imageType,
3013
+ fileVersion: image.header.fileVersion,
3014
+ modelId: "ZH",
3015
+ };
3016
+ fetchIndexEntries.push({...meta, modelId: "not this", fileName: "not this"}, meta); // non-match first to ensure not order-driven
3017
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3018
+ fieldControl: 0,
3019
+ manufacturerCode: image.header.manufacturerCode,
3020
+ imageType: image.header.imageType,
3021
+ fileVersion: image.header.fileVersion,
3022
+ };
3023
+ const {device} = createSimpleDevice({modelID: "ZH"});
3024
+
3025
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
3026
+ fileName,
3027
+ manufacturerCode: image.header.manufacturerCode,
3028
+ imageType: image.header.imageType,
3029
+ fileVersion: image.header.fileVersion,
3030
+ modelId: "ZH",
3031
+ });
3032
+ });
3033
+
3034
+ it("by extra meta modelId", async () => {
3035
+ const fileName = "custom.ota";
3036
+ const image = mockImage(300, undefined, undefined, 4);
3037
+ const meta: ZigbeeOtaImageMeta = {
3038
+ fileName,
3039
+ url: fileName,
3040
+ manufacturerCode: image.header.manufacturerCode,
3041
+ imageType: image.header.imageType,
3042
+ fileVersion: image.header.fileVersion,
3043
+ modelId: "ZH",
3044
+ };
3045
+ fetchIndexEntries.push({...meta, modelId: "not this", fileName: "not this"}, meta); // non-match first to ensure not order-driven
3046
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3047
+ fieldControl: 0,
3048
+ manufacturerCode: image.header.manufacturerCode,
3049
+ imageType: image.header.imageType,
3050
+ fileVersion: image.header.fileVersion,
3051
+ };
3052
+ const {device} = createSimpleDevice({modelID: "other"});
3053
+
3054
+ await expect(device.findMatchingOtaImage({}, current, {modelId: "ZH"})).resolves.toMatchObject({
3055
+ fileName,
3056
+ manufacturerCode: image.header.manufacturerCode,
3057
+ imageType: image.header.imageType,
3058
+ fileVersion: image.header.fileVersion,
3059
+ modelId: "ZH",
3060
+ });
3061
+ });
3062
+
3063
+ it("by manufacturerName", async () => {
3064
+ const fileName = "custom.ota";
3065
+ const image = mockImage(300, undefined, undefined, 4);
3066
+ const meta: ZigbeeOtaImageMeta = {
3067
+ fileName,
3068
+ url: fileName,
3069
+ manufacturerCode: image.header.manufacturerCode,
3070
+ imageType: image.header.imageType,
3071
+ fileVersion: image.header.fileVersion,
3072
+ manufacturerName: ["ZH"],
3073
+ };
3074
+ fetchIndexEntries.push({...meta, manufacturerName: ["not this"], fileName: "not this"}, meta); // non-match first to ensure not order-driven
3075
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3076
+ fieldControl: 0,
3077
+ manufacturerCode: image.header.manufacturerCode,
3078
+ imageType: image.header.imageType,
3079
+ fileVersion: image.header.fileVersion,
3080
+ };
3081
+ const {device} = createSimpleDevice({manufacturerName: "ZH"});
3082
+
3083
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
3084
+ fileName,
3085
+ manufacturerCode: image.header.manufacturerCode,
3086
+ imageType: image.header.imageType,
3087
+ fileVersion: image.header.fileVersion,
3088
+ manufacturerName: ["ZH"],
3089
+ });
3090
+ });
3091
+
3092
+ it("by extra meta manufacturerName", async () => {
3093
+ const fileName = "custom.ota";
3094
+ const image = mockImage(300, undefined, undefined, 4);
3095
+ const meta: ZigbeeOtaImageMeta = {
3096
+ fileName,
3097
+ url: fileName,
3098
+ manufacturerCode: image.header.manufacturerCode,
3099
+ imageType: image.header.imageType,
3100
+ fileVersion: image.header.fileVersion,
3101
+ manufacturerName: ["ZH"],
3102
+ };
3103
+ fetchIndexEntries.push({...meta, manufacturerName: ["not this"], fileName: "not this"}, meta); // non-match first to ensure not order-driven
3104
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3105
+ fieldControl: 0,
3106
+ manufacturerCode: image.header.manufacturerCode,
3107
+ imageType: image.header.imageType,
3108
+ fileVersion: image.header.fileVersion,
3109
+ };
3110
+ const {device} = createSimpleDevice({manufacturerName: "other"});
3111
+
3112
+ await expect(device.findMatchingOtaImage({}, current, {manufacturerName: "ZH"})).resolves.toMatchObject({
3113
+ fileName,
3114
+ manufacturerCode: image.header.manufacturerCode,
3115
+ imageType: image.header.imageType,
3116
+ fileVersion: image.header.fileVersion,
3117
+ manufacturerName: ["ZH"],
3118
+ });
3119
+ });
3120
+
3121
+ it("by extra meta otaHeaderString", async () => {
3122
+ const fileName = "custom.ota";
3123
+ const image = mockImage(300, undefined, undefined, 4);
3124
+ const meta: ZigbeeOtaImageMeta = {
3125
+ fileName,
3126
+ url: fileName,
3127
+ manufacturerCode: image.header.manufacturerCode,
3128
+ imageType: image.header.imageType,
3129
+ fileVersion: image.header.fileVersion,
3130
+ otaHeaderString: "ZH",
3131
+ };
3132
+ fetchIndexEntries.push({...meta, otaHeaderString: "not this", fileName: "not this"}, meta); // non-match first to ensure not order-driven
3133
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3134
+ fieldControl: 0,
3135
+ manufacturerCode: image.header.manufacturerCode,
3136
+ imageType: image.header.imageType,
3137
+ fileVersion: image.header.fileVersion,
3138
+ };
3139
+ const {device} = createSimpleDevice({});
3140
+
3141
+ await expect(device.findMatchingOtaImage({}, current, {otaHeaderString: "ZH"})).resolves.toMatchObject({
3142
+ fileName,
3143
+ manufacturerCode: image.header.manufacturerCode,
3144
+ imageType: image.header.imageType,
3145
+ fileVersion: image.header.fileVersion,
3146
+ otaHeaderString: "ZH",
3147
+ });
3148
+ });
3149
+
3150
+ it("by hardwareVersionMin", async () => {
3151
+ const fileName = "custom.ota";
3152
+ const image = mockImage(300, undefined, undefined, 56, undefined, undefined, 3, 0);
3153
+ const meta: ZigbeeOtaImageMeta = {
3154
+ fileName,
3155
+ url: fileName,
3156
+ manufacturerCode: image.header.manufacturerCode,
3157
+ imageType: image.header.imageType,
3158
+ fileVersion: image.header.fileVersion,
3159
+ hardwareVersionMin: image.header.minimumHardwareVersion,
3160
+ };
3161
+ const fileName2 = "custom2.ota";
3162
+ const image2 = mockImage(300, undefined, undefined, 58, undefined, undefined, 6, 0);
3163
+ const meta2: ZigbeeOtaImageMeta = {
3164
+ fileName: fileName2,
3165
+ url: fileName2,
3166
+ manufacturerCode: image2.header.manufacturerCode,
3167
+ imageType: image2.header.imageType,
3168
+ fileVersion: image2.header.fileVersion,
3169
+ hardwareVersionMin: image2.header.minimumHardwareVersion,
3170
+ };
3171
+ fetchIndexEntries.push(meta2, meta); // non-match first to ensure not order-driven
3172
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3173
+ fieldControl: 0,
3174
+ manufacturerCode: image.header.manufacturerCode,
3175
+ imageType: image.header.imageType,
3176
+ fileVersion: image.header.fileVersion,
3177
+ hardwareVersion: 4,
3178
+ };
3179
+ const {device} = createSimpleDevice({});
3180
+
3181
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
3182
+ fileName,
3183
+ manufacturerCode: image.header.manufacturerCode,
3184
+ imageType: image.header.imageType,
3185
+ fileVersion: image.header.fileVersion,
3186
+ hardwareVersionMin: 3,
3187
+ });
3188
+ });
3189
+
3190
+ it("by extra meta hardwareVersionMin", async () => {
3191
+ const fileName = "custom.ota";
3192
+ const image = mockImage(300, undefined, undefined, 56, undefined, undefined, 3, 0);
3193
+ const meta: ZigbeeOtaImageMeta = {
3194
+ fileName,
3195
+ url: fileName,
3196
+ manufacturerCode: image.header.manufacturerCode,
3197
+ imageType: image.header.imageType,
3198
+ fileVersion: image.header.fileVersion,
3199
+ hardwareVersionMin: image.header.minimumHardwareVersion,
3200
+ };
3201
+ const fileName2 = "custom2.ota";
3202
+ const image2 = mockImage(300, undefined, undefined, 58, undefined, undefined, 6, 0);
3203
+ const meta2: ZigbeeOtaImageMeta = {
3204
+ fileName: fileName2,
3205
+ url: fileName2,
3206
+ manufacturerCode: image2.header.manufacturerCode,
3207
+ imageType: image2.header.imageType,
3208
+ fileVersion: image2.header.fileVersion,
3209
+ hardwareVersionMin: image2.header.minimumHardwareVersion,
3210
+ };
3211
+ fetchIndexEntries.push(meta2, meta); // non-match first to ensure not order-driven
3212
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3213
+ fieldControl: 0,
3214
+ manufacturerCode: image.header.manufacturerCode,
3215
+ imageType: image.header.imageType,
3216
+ fileVersion: image.header.fileVersion,
3217
+ };
3218
+ const {device} = createSimpleDevice({});
3219
+
3220
+ await expect(device.findMatchingOtaImage({}, current, {hardwareVersionMin: 4})).resolves.toMatchObject({
3221
+ fileName,
3222
+ manufacturerCode: image.header.manufacturerCode,
3223
+ imageType: image.header.imageType,
3224
+ fileVersion: image.header.fileVersion,
3225
+ hardwareVersionMin: 3,
3226
+ });
3227
+ });
3228
+
3229
+ it("by hardwareVersionMax", async () => {
3230
+ const fileName = "custom.ota";
3231
+ const image = mockImage(300, undefined, undefined, 56, undefined, undefined, 0, 6);
3232
+ const meta: ZigbeeOtaImageMeta = {
3233
+ fileName,
3234
+ url: fileName,
3235
+ manufacturerCode: image.header.manufacturerCode,
3236
+ imageType: image.header.imageType,
3237
+ fileVersion: image.header.fileVersion,
3238
+ hardwareVersionMax: image.header.maximumHardwareVersion,
3239
+ };
3240
+ const fileName2 = "custom2.ota";
3241
+ const image2 = mockImage(300, undefined, undefined, 58, undefined, undefined, 0, 3);
3242
+ const meta2: ZigbeeOtaImageMeta = {
3243
+ fileName: fileName2,
3244
+ url: fileName2,
3245
+ manufacturerCode: image2.header.manufacturerCode,
3246
+ imageType: image2.header.imageType,
3247
+ fileVersion: image2.header.fileVersion,
3248
+ hardwareVersionMax: image2.header.maximumHardwareVersion,
3249
+ };
3250
+ fetchIndexEntries.push(meta2, meta); // non-match first to ensure not order-driven
3251
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3252
+ fieldControl: 0,
3253
+ manufacturerCode: image.header.manufacturerCode,
3254
+ imageType: image.header.imageType,
3255
+ fileVersion: image.header.fileVersion,
3256
+ hardwareVersion: 4,
3257
+ };
3258
+ const {device} = createSimpleDevice({});
3259
+
3260
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
3261
+ fileName,
3262
+ manufacturerCode: image.header.manufacturerCode,
3263
+ imageType: image.header.imageType,
3264
+ fileVersion: image.header.fileVersion,
3265
+ hardwareVersionMax: 6,
3266
+ });
3267
+ });
3268
+
3269
+ it("by extra meta hardwareVersionMax", async () => {
3270
+ const fileName = "custom.ota";
3271
+ const image = mockImage(300, undefined, undefined, 56, undefined, undefined, 0, 6);
3272
+ const meta: ZigbeeOtaImageMeta = {
3273
+ fileName,
3274
+ url: fileName,
3275
+ manufacturerCode: image.header.manufacturerCode,
3276
+ imageType: image.header.imageType,
3277
+ fileVersion: image.header.fileVersion,
3278
+ hardwareVersionMax: image.header.maximumHardwareVersion,
3279
+ };
3280
+ const fileName2 = "custom2.ota";
3281
+ const image2 = mockImage(300, undefined, undefined, 58, undefined, undefined, 0, 3);
3282
+ const meta2: ZigbeeOtaImageMeta = {
3283
+ fileName: fileName2,
3284
+ url: fileName2,
3285
+ manufacturerCode: image2.header.manufacturerCode,
3286
+ imageType: image2.header.imageType,
3287
+ fileVersion: image2.header.fileVersion,
3288
+ hardwareVersionMax: image2.header.maximumHardwareVersion,
3289
+ };
3290
+ fetchIndexEntries.push(meta2, meta); // non-match first to ensure not order-driven
3291
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3292
+ fieldControl: 0,
3293
+ manufacturerCode: image.header.manufacturerCode,
3294
+ imageType: image.header.imageType,
3295
+ fileVersion: image.header.fileVersion,
3296
+ };
3297
+ const {device} = createSimpleDevice({});
3298
+
3299
+ await expect(device.findMatchingOtaImage({}, current, {hardwareVersionMax: 4})).resolves.toMatchObject({
3300
+ fileName,
3301
+ manufacturerCode: image.header.manufacturerCode,
3302
+ imageType: image.header.imageType,
3303
+ fileVersion: image.header.fileVersion,
3304
+ hardwareVersionMax: 6,
3305
+ });
3306
+ });
3307
+ });
3308
+
3309
+ describe("Utils", () => {
3310
+ it("parses firmware with unusual header fields", () => {
3311
+ const upgradeFileDestination = Buffer.from([0x21, 0x23, 0x34, 0x32, 0x89, 0x98, 0x76, 0x67]);
3312
+ const image = mockImage(500, undefined, undefined, undefined, 123, upgradeFileDestination, 1, 3);
3313
+
3314
+ expect(otaHelpers.parseOtaHeader(image.raw)).toStrictEqual({
3315
+ otaUpgradeFileIdentifier: 0x0beef11e,
3316
+ otaHeaderVersion: 256,
3317
+ otaHeaderLength: 56,
3318
+ otaHeaderFieldControl: 7,
3319
+ manufacturerCode: 1,
3320
+ imageType: 2,
3321
+ fileVersion: 3,
3322
+ zigbeeStackVersion: 2,
3323
+ otaHeaderString: expect.stringContaining("test"),
3324
+ totalImageSize: 500,
3325
+ securityCredentialVersion: 123,
3326
+ upgradeFileDestination,
3327
+ minimumHardwareVersion: 1,
3328
+ maximumHardwareVersion: 3,
3329
+ });
3330
+ });
3331
+ });
3332
+ });