amaran-light-cli 1.10.0 → 1.12.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 (397) hide show
  1. package/README.md +154 -17
  2. package/dist/__tests__/autoCct.test.js +35 -4
  3. package/dist/__tests__/autoCct.test.js.map +1 -1
  4. package/dist/__tests__/autoCctMaxLux.test.js +11 -9
  5. package/dist/__tests__/autoCctMaxLux.test.js.map +1 -1
  6. package/dist/__tests__/autostart.test.js +18 -0
  7. package/dist/__tests__/autostart.test.js.map +1 -1
  8. package/dist/__tests__/bleHttpControl.test.d.ts +2 -0
  9. package/dist/__tests__/bleHttpControl.test.d.ts.map +1 -0
  10. package/dist/__tests__/bleHttpControl.test.js +230 -0
  11. package/dist/__tests__/bleHttpControl.test.js.map +1 -0
  12. package/dist/__tests__/bleStack.test.d.ts +2 -0
  13. package/dist/__tests__/bleStack.test.d.ts.map +1 -0
  14. package/dist/__tests__/bleStack.test.js +1239 -0
  15. package/dist/__tests__/bleStack.test.js.map +1 -0
  16. package/dist/__tests__/cctUtil.test.js +11 -5
  17. package/dist/__tests__/cctUtil.test.js.map +1 -1
  18. package/dist/__tests__/circadianDashboardSettings.test.d.ts +2 -0
  19. package/dist/__tests__/circadianDashboardSettings.test.d.ts.map +1 -0
  20. package/dist/__tests__/circadianDashboardSettings.test.js +82 -0
  21. package/dist/__tests__/circadianDashboardSettings.test.js.map +1 -0
  22. package/dist/__tests__/circadianIsolation.test.d.ts +2 -0
  23. package/dist/__tests__/circadianIsolation.test.d.ts.map +1 -0
  24. package/dist/__tests__/circadianIsolation.test.js +30 -0
  25. package/dist/__tests__/circadianIsolation.test.js.map +1 -0
  26. package/dist/__tests__/cli.config.test.d.ts +2 -0
  27. package/dist/__tests__/cli.config.test.d.ts.map +1 -0
  28. package/dist/__tests__/cli.config.test.js +114 -0
  29. package/dist/__tests__/cli.config.test.js.map +1 -0
  30. package/dist/__tests__/cli.smoke.test.js +70 -17
  31. package/dist/__tests__/cli.smoke.test.js.map +1 -1
  32. package/dist/__tests__/currentCct.test.d.ts +2 -0
  33. package/dist/__tests__/currentCct.test.d.ts.map +1 -0
  34. package/dist/__tests__/currentCct.test.js +29 -0
  35. package/dist/__tests__/currentCct.test.js.map +1 -0
  36. package/dist/__tests__/dashboard.test.d.ts +2 -0
  37. package/dist/__tests__/dashboard.test.d.ts.map +1 -0
  38. package/dist/__tests__/dashboard.test.js +126 -0
  39. package/dist/__tests__/dashboard.test.js.map +1 -0
  40. package/dist/__tests__/daylightSimulationParseUtils.test.d.ts +2 -0
  41. package/dist/__tests__/daylightSimulationParseUtils.test.d.ts.map +1 -0
  42. package/dist/__tests__/daylightSimulationParseUtils.test.js +16 -0
  43. package/dist/__tests__/daylightSimulationParseUtils.test.js.map +1 -0
  44. package/dist/__tests__/desktopImport.test.d.ts +2 -0
  45. package/dist/__tests__/desktopImport.test.d.ts.map +1 -0
  46. package/dist/__tests__/desktopImport.test.js +234 -0
  47. package/dist/__tests__/desktopImport.test.js.map +1 -0
  48. package/dist/__tests__/lightControl.basic.test.d.ts +2 -0
  49. package/dist/__tests__/lightControl.basic.test.d.ts.map +1 -0
  50. package/dist/__tests__/lightControl.basic.test.js +119 -0
  51. package/dist/__tests__/lightControl.basic.test.js.map +1 -0
  52. package/dist/__tests__/lightControl.colorEffects.test.d.ts +2 -0
  53. package/dist/__tests__/lightControl.colorEffects.test.d.ts.map +1 -0
  54. package/dist/__tests__/lightControl.colorEffects.test.js +170 -0
  55. package/dist/__tests__/lightControl.colorEffects.test.js.map +1 -0
  56. package/dist/__tests__/lightControl.management.test.d.ts +2 -0
  57. package/dist/__tests__/lightControl.management.test.d.ts.map +1 -0
  58. package/dist/__tests__/lightControl.management.test.js +90 -0
  59. package/dist/__tests__/lightControl.management.test.js.map +1 -0
  60. package/dist/__tests__/lightControlTestUtils.d.ts +10 -0
  61. package/dist/__tests__/lightControlTestUtils.d.ts.map +1 -0
  62. package/dist/__tests__/lightControlTestUtils.js +57 -0
  63. package/dist/__tests__/lightControlTestUtils.js.map +1 -0
  64. package/dist/__tests__/maxLuxInterpolation.test.js +4 -2
  65. package/dist/__tests__/maxLuxInterpolation.test.js.map +1 -1
  66. package/dist/__tests__/meshConfiguration.test.d.ts +2 -0
  67. package/dist/__tests__/meshConfiguration.test.d.ts.map +1 -0
  68. package/dist/__tests__/meshConfiguration.test.js +78 -0
  69. package/dist/__tests__/meshConfiguration.test.js.map +1 -0
  70. package/dist/__tests__/nativeGroups.test.d.ts +2 -0
  71. package/dist/__tests__/nativeGroups.test.d.ts.map +1 -0
  72. package/dist/__tests__/nativeGroups.test.js +104 -0
  73. package/dist/__tests__/nativeGroups.test.js.map +1 -0
  74. package/dist/__tests__/packageExports.test.d.ts +2 -0
  75. package/dist/__tests__/packageExports.test.d.ts.map +1 -0
  76. package/dist/__tests__/packageExports.test.js +33 -0
  77. package/dist/__tests__/packageExports.test.js.map +1 -0
  78. package/dist/__tests__/programApi.test.d.ts +2 -0
  79. package/dist/__tests__/programApi.test.d.ts.map +1 -0
  80. package/dist/__tests__/programApi.test.js +73 -0
  81. package/dist/__tests__/programApi.test.js.map +1 -0
  82. package/dist/__tests__/programs.test.d.ts +2 -0
  83. package/dist/__tests__/programs.test.d.ts.map +1 -0
  84. package/dist/__tests__/programs.test.js +176 -0
  85. package/dist/__tests__/programs.test.js.map +1 -0
  86. package/dist/__tests__/scheduleMaker.test.d.ts +2 -0
  87. package/dist/__tests__/scheduleMaker.test.d.ts.map +1 -0
  88. package/dist/__tests__/scheduleMaker.test.js +25 -0
  89. package/dist/__tests__/scheduleMaker.test.js.map +1 -0
  90. package/dist/__tests__/simulateSchedule.test.js +7 -3
  91. package/dist/__tests__/simulateSchedule.test.js.map +1 -1
  92. package/dist/ble/async.d.ts +2 -0
  93. package/dist/ble/async.d.ts.map +1 -0
  94. package/dist/ble/async.js +15 -0
  95. package/dist/ble/async.js.map +1 -0
  96. package/dist/ble/colors.d.ts +5 -0
  97. package/dist/ble/colors.d.ts.map +1 -0
  98. package/dist/ble/colors.js +44 -0
  99. package/dist/ble/colors.js.map +1 -0
  100. package/dist/ble/configuration.d.ts +22 -0
  101. package/dist/ble/configuration.d.ts.map +1 -0
  102. package/dist/ble/configuration.js +122 -0
  103. package/dist/ble/configuration.js.map +1 -0
  104. package/dist/ble/controller.d.ts +118 -0
  105. package/dist/ble/controller.d.ts.map +1 -0
  106. package/dist/ble/controller.js +931 -0
  107. package/dist/ble/controller.js.map +1 -0
  108. package/dist/ble/crypto.d.ts +34 -0
  109. package/dist/ble/crypto.d.ts.map +1 -0
  110. package/dist/ble/crypto.js +178 -0
  111. package/dist/ble/crypto.js.map +1 -0
  112. package/dist/ble/daemon.d.ts +2 -0
  113. package/dist/ble/daemon.d.ts.map +1 -0
  114. package/dist/ble/daemon.js +76 -0
  115. package/dist/ble/daemon.js.map +1 -0
  116. package/dist/ble/dashboard.d.ts +331 -0
  117. package/dist/ble/dashboard.d.ts.map +1 -0
  118. package/dist/ble/dashboard.js +280 -0
  119. package/dist/ble/dashboard.js.map +1 -0
  120. package/dist/ble/desktop.d.ts +4 -0
  121. package/dist/ble/desktop.d.ts.map +1 -0
  122. package/dist/ble/desktop.js +33 -0
  123. package/dist/ble/desktop.js.map +1 -0
  124. package/dist/ble/desktopLibrary.d.ts +12 -0
  125. package/dist/ble/desktopLibrary.d.ts.map +1 -0
  126. package/dist/ble/desktopLibrary.js +346 -0
  127. package/dist/ble/desktopLibrary.js.map +1 -0
  128. package/dist/ble/effects.d.ts +34 -0
  129. package/dist/ble/effects.d.ts.map +1 -0
  130. package/dist/ble/effects.js +138 -0
  131. package/dist/ble/effects.js.map +1 -0
  132. package/dist/ble/fan.d.ts +244 -0
  133. package/dist/ble/fan.d.ts.map +1 -0
  134. package/dist/ble/fan.js +46 -0
  135. package/dist/ble/fan.js.map +1 -0
  136. package/dist/ble/library.d.ts +341 -0
  137. package/dist/ble/library.d.ts.map +1 -0
  138. package/dist/ble/library.js +201 -0
  139. package/dist/ble/library.js.map +1 -0
  140. package/dist/ble/media.d.ts +133 -0
  141. package/dist/ble/media.d.ts.map +1 -0
  142. package/dist/ble/media.js +186 -0
  143. package/dist/ble/media.js.map +1 -0
  144. package/dist/ble/nativeGroups.d.ts +22 -0
  145. package/dist/ble/nativeGroups.d.ts.map +1 -0
  146. package/dist/ble/nativeGroups.js +114 -0
  147. package/dist/ble/nativeGroups.js.map +1 -0
  148. package/dist/ble/packets.d.ts +3 -0
  149. package/dist/ble/packets.d.ts.map +1 -0
  150. package/dist/ble/packets.js +15 -0
  151. package/dist/ble/packets.js.map +1 -0
  152. package/dist/ble/programs.d.ts +318 -0
  153. package/dist/ble/programs.d.ts.map +1 -0
  154. package/dist/ble/programs.js +329 -0
  155. package/dist/ble/programs.js.map +1 -0
  156. package/dist/ble/provisioning.d.ts +13 -0
  157. package/dist/ble/provisioning.d.ts.map +1 -0
  158. package/dist/ble/provisioning.js +39 -0
  159. package/dist/ble/provisioning.js.map +1 -0
  160. package/dist/ble/server.d.ts +18 -0
  161. package/dist/ble/server.d.ts.map +1 -0
  162. package/dist/ble/server.js +548 -0
  163. package/dist/ble/server.js.map +1 -0
  164. package/dist/ble/service.d.ts +2 -0
  165. package/dist/ble/service.d.ts.map +1 -0
  166. package/dist/ble/service.js +77 -0
  167. package/dist/ble/service.js.map +1 -0
  168. package/dist/ble/settings.d.ts +18 -0
  169. package/dist/ble/settings.d.ts.map +1 -0
  170. package/dist/ble/settings.js +25 -0
  171. package/dist/ble/settings.js.map +1 -0
  172. package/dist/ble/setup.d.ts +2 -0
  173. package/dist/ble/setup.d.ts.map +1 -0
  174. package/dist/ble/setup.js +31 -0
  175. package/dist/ble/setup.js.map +1 -0
  176. package/dist/ble/storage.d.ts +94 -0
  177. package/dist/ble/storage.d.ts.map +1 -0
  178. package/dist/ble/storage.js +132 -0
  179. package/dist/ble/storage.js.map +1 -0
  180. package/dist/ble/telink.d.ts +63 -0
  181. package/dist/ble/telink.d.ts.map +1 -0
  182. package/dist/ble/telink.js +108 -0
  183. package/dist/ble/telink.js.map +1 -0
  184. package/dist/ble/transitions.d.ts +3 -0
  185. package/dist/ble/transitions.d.ts.map +1 -0
  186. package/dist/ble/transitions.js +28 -0
  187. package/dist/ble/transitions.js.map +1 -0
  188. package/dist/ble/transport.d.ts +48 -0
  189. package/dist/ble/transport.d.ts.map +1 -0
  190. package/dist/ble/transport.js +370 -0
  191. package/dist/ble/transport.js.map +1 -0
  192. package/dist/circadian.d.ts +19 -0
  193. package/dist/circadian.d.ts.map +1 -0
  194. package/dist/circadian.js +13 -0
  195. package/dist/circadian.js.map +1 -0
  196. package/dist/cli.d.ts +5 -3
  197. package/dist/cli.d.ts.map +1 -1
  198. package/dist/cli.js +159 -33
  199. package/dist/cli.js.map +1 -1
  200. package/dist/commands/__tests__/integration.test.js +28 -0
  201. package/dist/commands/__tests__/integration.test.js.map +1 -1
  202. package/dist/commands/__tests__/newCommands.test.js +3 -4
  203. package/dist/commands/__tests__/newCommands.test.js.map +1 -1
  204. package/dist/commands/cmdUtils.d.ts +8 -2
  205. package/dist/commands/cmdUtils.d.ts.map +1 -1
  206. package/dist/commands/cmdUtils.js +40 -2
  207. package/dist/commands/cmdUtils.js.map +1 -1
  208. package/dist/commands/daylightSimulation/autoCct.d.ts.map +1 -1
  209. package/dist/commands/daylightSimulation/autoCct.js +74 -172
  210. package/dist/commands/daylightSimulation/autoCct.js.map +1 -1
  211. package/dist/commands/daylightSimulation/graphSchedule.d.ts +1 -1
  212. package/dist/commands/daylightSimulation/graphSchedule.d.ts.map +1 -1
  213. package/dist/commands/daylightSimulation/graphSchedule.js +2 -0
  214. package/dist/commands/daylightSimulation/graphSchedule.js.map +1 -1
  215. package/dist/commands/daylightSimulation/printSchedule.d.ts +1 -1
  216. package/dist/commands/daylightSimulation/printSchedule.d.ts.map +1 -1
  217. package/dist/commands/daylightSimulation/printSchedule.js +4 -2
  218. package/dist/commands/daylightSimulation/printSchedule.js.map +1 -1
  219. package/dist/commands/daylightSimulation/service.d.ts +1 -1
  220. package/dist/commands/daylightSimulation/service.d.ts.map +1 -1
  221. package/dist/commands/daylightSimulation/service.js +20 -10
  222. package/dist/commands/daylightSimulation/service.js.map +1 -1
  223. package/dist/commands/daylightSimulation/simulateSchedule.d.ts +1 -1
  224. package/dist/commands/daylightSimulation/simulateSchedule.d.ts.map +1 -1
  225. package/dist/commands/daylightSimulation/simulateSchedule.js +31 -13
  226. package/dist/commands/daylightSimulation/simulateSchedule.js.map +1 -1
  227. package/dist/commands/daylightSimulation/weather.d.ts.map +1 -1
  228. package/dist/commands/daylightSimulation/weather.js +19 -4
  229. package/dist/commands/daylightSimulation/weather.js.map +1 -1
  230. package/dist/commands/deviceControl/ble.d.ts +4 -0
  231. package/dist/commands/deviceControl/ble.d.ts.map +1 -0
  232. package/dist/commands/deviceControl/ble.js +214 -0
  233. package/dist/commands/deviceControl/ble.js.map +1 -0
  234. package/dist/commands/deviceControl/cct.d.ts.map +1 -1
  235. package/dist/commands/deviceControl/cct.js +47 -19
  236. package/dist/commands/deviceControl/cct.js.map +1 -1
  237. package/dist/commands/deviceControl/color.d.ts.map +1 -1
  238. package/dist/commands/deviceControl/color.js +16 -12
  239. package/dist/commands/deviceControl/color.js.map +1 -1
  240. package/dist/commands/deviceControl/config.d.ts.map +1 -1
  241. package/dist/commands/deviceControl/config.js +94 -58
  242. package/dist/commands/deviceControl/config.js.map +1 -1
  243. package/dist/commands/deviceControl/{discover.d.ts → desktop.d.ts} +3 -2
  244. package/dist/commands/deviceControl/desktop.d.ts.map +1 -0
  245. package/dist/commands/deviceControl/desktop.js +66 -0
  246. package/dist/commands/deviceControl/desktop.js.map +1 -0
  247. package/dist/commands/deviceControl/effect.d.ts.map +1 -1
  248. package/dist/commands/deviceControl/effect.js +90 -159
  249. package/dist/commands/deviceControl/effect.js.map +1 -1
  250. package/dist/commands/deviceControl/fan.d.ts.map +1 -1
  251. package/dist/commands/deviceControl/fan.js +77 -95
  252. package/dist/commands/deviceControl/fan.js.map +1 -1
  253. package/dist/commands/deviceControl/group.d.ts.map +1 -1
  254. package/dist/commands/deviceControl/group.js +43 -6
  255. package/dist/commands/deviceControl/group.js.map +1 -1
  256. package/dist/commands/deviceControl/hsi.d.ts.map +1 -1
  257. package/dist/commands/deviceControl/hsi.js +6 -10
  258. package/dist/commands/deviceControl/hsi.js.map +1 -1
  259. package/dist/commands/deviceControl/info.d.ts.map +1 -1
  260. package/dist/commands/deviceControl/info.js +0 -50
  261. package/dist/commands/deviceControl/info.js.map +1 -1
  262. package/dist/commands/deviceControl/intensity.d.ts.map +1 -1
  263. package/dist/commands/deviceControl/intensity.js +38 -16
  264. package/dist/commands/deviceControl/intensity.js.map +1 -1
  265. package/dist/commands/deviceControl/list.d.ts.map +1 -1
  266. package/dist/commands/deviceControl/list.js +21 -15
  267. package/dist/commands/deviceControl/list.js.map +1 -1
  268. package/dist/commands/deviceControl/power.d.ts.map +1 -1
  269. package/dist/commands/deviceControl/power.js +19 -28
  270. package/dist/commands/deviceControl/power.js.map +1 -1
  271. package/dist/commands/deviceControl/preset.d.ts.map +1 -1
  272. package/dist/commands/deviceControl/preset.js +56 -19
  273. package/dist/commands/deviceControl/preset.js.map +1 -1
  274. package/dist/commands/deviceControl/program.d.ts +4 -0
  275. package/dist/commands/deviceControl/program.d.ts.map +1 -0
  276. package/dist/commands/deviceControl/program.js +199 -0
  277. package/dist/commands/deviceControl/program.js.map +1 -0
  278. package/dist/commands/deviceControl/quickshot.d.ts.map +1 -1
  279. package/dist/commands/deviceControl/quickshot.js +75 -13
  280. package/dist/commands/deviceControl/quickshot.js.map +1 -1
  281. package/dist/commands/deviceControl/scene.d.ts.map +1 -1
  282. package/dist/commands/deviceControl/scene.js +72 -9
  283. package/dist/commands/deviceControl/scene.js.map +1 -1
  284. package/dist/commands/deviceControl/status.d.ts.map +1 -1
  285. package/dist/commands/deviceControl/status.js +29 -6
  286. package/dist/commands/deviceControl/status.js.map +1 -1
  287. package/dist/commands/parseUtils.d.ts +6 -0
  288. package/dist/commands/parseUtils.d.ts.map +1 -0
  289. package/dist/commands/parseUtils.js +32 -0
  290. package/dist/commands/parseUtils.js.map +1 -0
  291. package/dist/commands-api.d.ts +28 -0
  292. package/dist/commands-api.d.ts.map +1 -0
  293. package/dist/commands-api.js +27 -0
  294. package/dist/commands-api.js.map +1 -0
  295. package/dist/commands.d.ts.map +1 -1
  296. package/dist/commands.js +4 -2
  297. package/dist/commands.js.map +1 -1
  298. package/dist/config.d.ts +165 -0
  299. package/dist/config.d.ts.map +1 -0
  300. package/dist/config.js +125 -0
  301. package/dist/config.js.map +1 -0
  302. package/dist/daylightSimulation/cctUtil.d.ts.map +1 -1
  303. package/dist/daylightSimulation/cctUtil.js +23 -22
  304. package/dist/daylightSimulation/cctUtil.js.map +1 -1
  305. package/dist/daylightSimulation/constants.d.ts +10 -0
  306. package/dist/daylightSimulation/constants.d.ts.map +1 -1
  307. package/dist/daylightSimulation/constants.js +4 -0
  308. package/dist/daylightSimulation/constants.js.map +1 -1
  309. package/dist/daylightSimulation/currentCct.d.ts +42 -0
  310. package/dist/daylightSimulation/currentCct.d.ts.map +1 -0
  311. package/dist/daylightSimulation/currentCct.js +217 -0
  312. package/dist/daylightSimulation/currentCct.js.map +1 -0
  313. package/dist/daylightSimulation/curves/realistic.d.ts +6 -6
  314. package/dist/daylightSimulation/curves/realistic.d.ts.map +1 -1
  315. package/dist/daylightSimulation/curves/realistic.js +36 -6
  316. package/dist/daylightSimulation/curves/realistic.js.map +1 -1
  317. package/dist/daylightSimulation/dashboardSettings.d.ts +47 -0
  318. package/dist/daylightSimulation/dashboardSettings.d.ts.map +1 -0
  319. package/dist/daylightSimulation/dashboardSettings.js +95 -0
  320. package/dist/daylightSimulation/dashboardSettings.js.map +1 -0
  321. package/dist/daylightSimulation/dashboardStatus.d.ts +72 -0
  322. package/dist/daylightSimulation/dashboardStatus.d.ts.map +1 -0
  323. package/dist/daylightSimulation/dashboardStatus.js +191 -0
  324. package/dist/daylightSimulation/dashboardStatus.js.map +1 -0
  325. package/dist/daylightSimulation/mathUtil.d.ts.map +1 -1
  326. package/dist/daylightSimulation/mathUtil.js +6 -2
  327. package/dist/daylightSimulation/mathUtil.js.map +1 -1
  328. package/dist/daylightSimulation/parseUtils.d.ts +3 -0
  329. package/dist/daylightSimulation/parseUtils.d.ts.map +1 -0
  330. package/dist/daylightSimulation/parseUtils.js +25 -0
  331. package/dist/daylightSimulation/parseUtils.js.map +1 -0
  332. package/dist/daylightSimulation/scheduleMaker.d.ts +6 -2
  333. package/dist/daylightSimulation/scheduleMaker.d.ts.map +1 -1
  334. package/dist/daylightSimulation/scheduleMaker.js +9 -11
  335. package/dist/daylightSimulation/scheduleMaker.js.map +1 -1
  336. package/dist/daylightSimulation/types.d.ts +15 -2
  337. package/dist/daylightSimulation/types.d.ts.map +1 -1
  338. package/dist/daylightSimulation/types.js +0 -2
  339. package/dist/daylightSimulation/types.js.map +1 -1
  340. package/dist/daylightSimulation/weatherUtil.d.ts.map +1 -1
  341. package/dist/daylightSimulation/weatherUtil.js +31 -0
  342. package/dist/daylightSimulation/weatherUtil.js.map +1 -1
  343. package/dist/device-control.d.ts +8 -0
  344. package/dist/device-control.d.ts.map +1 -0
  345. package/dist/device-control.js +8 -0
  346. package/dist/device-control.js.map +1 -0
  347. package/dist/deviceControl/autostart.d.ts.map +1 -1
  348. package/dist/deviceControl/autostart.js +15 -16
  349. package/dist/deviceControl/autostart.js.map +1 -1
  350. package/dist/deviceControl/bleHttpControl.d.ts +129 -0
  351. package/dist/deviceControl/bleHttpControl.d.ts.map +1 -0
  352. package/dist/deviceControl/bleHttpControl.js +753 -0
  353. package/dist/deviceControl/bleHttpControl.js.map +1 -0
  354. package/dist/deviceControl/lightControl.d.ts +9 -68
  355. package/dist/deviceControl/lightControl.d.ts.map +1 -1
  356. package/dist/deviceControl/lightControl.js +25 -255
  357. package/dist/deviceControl/lightControl.js.map +1 -1
  358. package/dist/deviceControl/lightControlCommands.d.ts +68 -0
  359. package/dist/deviceControl/lightControlCommands.d.ts.map +1 -0
  360. package/dist/deviceControl/lightControlCommands.js +244 -0
  361. package/dist/deviceControl/lightControlCommands.js.map +1 -0
  362. package/dist/deviceControl/types.d.ts +83 -4
  363. package/dist/deviceControl/types.d.ts.map +1 -1
  364. package/dist/deviceControl/types.js +1 -0
  365. package/dist/deviceControl/types.js.map +1 -1
  366. package/dist/index.d.ts +12 -0
  367. package/dist/index.d.ts.map +1 -0
  368. package/dist/index.js +8 -0
  369. package/dist/index.js.map +1 -0
  370. package/dist/test/MockLightServer.d.ts +1 -0
  371. package/dist/test/MockLightServer.d.ts.map +1 -1
  372. package/dist/test/MockLightServer.js +5 -0
  373. package/dist/test/MockLightServer.js.map +1 -1
  374. package/docs/CHANGELOG.md +171 -0
  375. package/docs/DIRECT_BLE.md +359 -0
  376. package/package.json +39 -8
  377. package/scripts/ble-cli-check.ts +129 -0
  378. package/scripts/ble-config-probe.ts +109 -0
  379. package/scripts/ble-effects-check.ts +82 -0
  380. package/scripts/ble-extended-probe.ts +44 -0
  381. package/scripts/ble-fan-check.ts +129 -0
  382. package/scripts/ble-feature-probe.ts +37 -0
  383. package/scripts/ble-followup-check.ts +165 -0
  384. package/scripts/ble-hardware-check.ts +99 -0
  385. package/scripts/ble-live-extended-check.ts +205 -0
  386. package/scripts/ble-non-ota-check.ts +193 -0
  387. package/scripts/ble-probe.ts +45 -0
  388. package/scripts/calibrate-webcam.ts +152 -0
  389. package/scripts/desktop-reference.ts +85 -0
  390. package/scripts/webcam.ts +129 -0
  391. package/dist/__tests__/lightControl.test.d.ts +0 -2
  392. package/dist/__tests__/lightControl.test.d.ts.map +0 -1
  393. package/dist/__tests__/lightControl.test.js +0 -424
  394. package/dist/__tests__/lightControl.test.js.map +0 -1
  395. package/dist/commands/deviceControl/discover.d.ts.map +0 -1
  396. package/dist/commands/deviceControl/discover.js +0 -29
  397. package/dist/commands/deviceControl/discover.js.map +0 -1
@@ -0,0 +1,129 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { readFileSync, writeFileSync } from 'node:fs';
4
+ import { z } from 'zod';
5
+ import { SavedStateSchema } from '../src/ble/library.js';
6
+
7
+ const base = process.env.AMARAN_BLE_URL ?? 'http://127.0.0.1:2708';
8
+ const Schema = z.object({
9
+ prefix: z.string(),
10
+ initial: z.record(SavedStateSchema),
11
+ grouped: z.record(SavedStateSchema),
12
+ });
13
+ function cli(...args: string[]) {
14
+ const separator = args.indexOf('--');
15
+ const flags = ['--backend', 'ble', '--url', base];
16
+ const command =
17
+ separator < 0 ? [...args, ...flags] : [...args.slice(0, separator), ...flags, ...args.slice(separator)];
18
+ return execFileSync(process.execPath, ['dist/cli.js', ...command], { encoding: 'utf8', timeout: 60_000 });
19
+ }
20
+ async function snapshot() {
21
+ const states: z.infer<typeof Schema>['initial'] = {};
22
+ for (const key of ['desk', 'front', 'back']) {
23
+ const response = await fetch(`${base}/lights/${key}/state`, { signal: AbortSignal.timeout(15_000) });
24
+ const data = z.object({ ok: z.literal(true), result: SavedStateSchema }).parse(await response.json());
25
+ states[key] = data.result;
26
+ }
27
+ return states;
28
+ }
29
+ function equal(actual: z.infer<typeof Schema>['initial'], expected: z.infer<typeof Schema>['initial']) {
30
+ for (const key of Object.keys(expected)) {
31
+ for (const field of ['mode', 'cct', 'intensity', 'sleep'] as const)
32
+ assert.equal(actual[key][field], expected[key][field]);
33
+ if (key === 'back') assert.equal(actual[key].gm, expected[key].gm);
34
+ }
35
+ }
36
+ const phase = process.argv[2];
37
+ if (phase === 'prepare') {
38
+ const prefix = `CLI verification ${Date.now()}`;
39
+ const initial = await snapshot();
40
+ assert.ok(Object.values(initial).every((state) => state.mode === 'cct'));
41
+ const filename = `artifacts/cli-check-${Date.now()}.json`;
42
+ writeFileSync(filename, JSON.stringify({ prefix, initial, grouped: initial }, null, 2), { mode: 0o600 });
43
+ const created: [string, string][] = [];
44
+ try {
45
+ cli('scene', 'save', `${prefix} scene`);
46
+ created.push(['scene', `${prefix} scene`]);
47
+ cli('preset', 'save', 'back', `${prefix} preset`);
48
+ created.push(['preset', `${prefix} preset`]);
49
+ cli('group', 'create', `${prefix} group`);
50
+ created.push(['group', `${prefix} group`]);
51
+ cli('group', 'add', `${prefix} group`, 'desk');
52
+ cli('group', 'add', `${prefix} group`, 'front');
53
+ cli('intensity', '1', `${prefix} group`);
54
+ cli('intensity', '1', `${prefix} group`, '--relative');
55
+ cli('cct', '100', `${prefix} group`, '--relative');
56
+ const grouped = await snapshot();
57
+ assert.equal(grouped.desk.intensity, 20);
58
+ assert.equal(grouped.front.intensity, 20);
59
+ assert.equal(grouped.back.intensity, initial.back.intensity);
60
+ assert.equal(grouped.desk.cct, (initial.desk.cct ?? 0) + 100);
61
+ const status = cli('status', `${prefix} group`);
62
+ assert.ok(status.includes('Status for desk') && status.includes('Status for front') && !status.includes('Unknown'));
63
+ cli('quickshot', 'save', `${prefix} quickshot`);
64
+ created.push(['quickshot', `${prefix} quickshot`]);
65
+ cli('ble', 'gm', 'back', '--', '-30');
66
+ assert.equal((await snapshot()).back.gm, -30);
67
+ writeFileSync(filename, JSON.stringify({ prefix, initial, grouped }, null, 2), { mode: 0o600 });
68
+ console.log(`Prepared persistent CLI checks: ${filename}`);
69
+ } catch (error) {
70
+ const failures: unknown[] = [error];
71
+ if (created.some(([kind]) => kind === 'scene')) {
72
+ try {
73
+ cli('scene', 'recall', `${prefix} scene`);
74
+ equal(await snapshot(), initial);
75
+ } catch (restoration) {
76
+ failures.push(restoration);
77
+ }
78
+ }
79
+ for (const [kind, name] of created.reverse()) {
80
+ try {
81
+ cli(kind, 'delete', name);
82
+ } catch (cleanup) {
83
+ failures.push(cleanup);
84
+ }
85
+ }
86
+ throw new AggregateError(failures, `CLI preparation failed; recovery snapshot: ${filename}`);
87
+ }
88
+ } else if (phase === 'verify' && process.argv[3]) {
89
+ const filename = process.argv[3];
90
+ const { prefix, initial, grouped } = Schema.parse(JSON.parse(readFileSync(filename, 'utf8')));
91
+ const errors: unknown[] = [];
92
+ try {
93
+ assert.ok(cli('group', 'list').includes(`${prefix} group`));
94
+ assert.ok(cli('preset', 'list').includes(`${prefix} preset`));
95
+ assert.ok(cli('quickshot', 'list').includes(`${prefix} quickshot`));
96
+ cli('preset', 'recall', 'back', `${prefix} preset`);
97
+ assert.equal((await snapshot()).back.gm, initial.back.gm);
98
+ cli('scene', 'recall', `${prefix} scene`);
99
+ equal(await snapshot(), initial);
100
+ cli('intensity', '1', 'all');
101
+ cli('quickshot', 'set', `${prefix} quickshot`);
102
+ equal(await snapshot(), grouped);
103
+ } catch (error) {
104
+ errors.push(error);
105
+ } finally {
106
+ try {
107
+ cli('scene', 'recall', `${prefix} scene`);
108
+ equal(await snapshot(), initial);
109
+ } catch (error) {
110
+ errors.push(error);
111
+ }
112
+ for (const [kind, name] of [
113
+ ['preset', 'preset'],
114
+ ['quickshot', 'quickshot'],
115
+ ['group', 'group'],
116
+ ['scene', 'scene'],
117
+ ]) {
118
+ try {
119
+ cli(kind, 'delete', `${prefix} ${name}`);
120
+ } catch (error) {
121
+ errors.push(error);
122
+ }
123
+ }
124
+ }
125
+ if (errors.length) throw new AggregateError(errors, 'CLI persistence checks or restoration failed');
126
+ console.log(
127
+ 'CLI groups, tint, relative controls, presets, scenes and quickshots survived restart; initial lighting restored.'
128
+ );
129
+ } else throw new Error('Use prepare, restart the daemon, then verify <manifest>');
@@ -0,0 +1,109 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import {
3
+ changeSubscription,
4
+ type MeshModel,
5
+ modelBindings,
6
+ readComposition,
7
+ subscriptions,
8
+ } from '../src/ble/configuration.js';
9
+ import { MeshCrypto } from '../src/ble/crypto.js';
10
+ import { desktopDeviceKeys } from '../src/ble/desktop.js';
11
+ import { loadMeshConfig, meshDirectory, SequenceStore } from '../src/ble/storage.js';
12
+ import { brightnessPacket, type FixtureState } from '../src/ble/telink.js';
13
+ import { MeshTransport } from '../src/ble/transport.js';
14
+
15
+ if (!process.argv[2]) throw new Error('Supply the matching Desktop database path; this probe is read-only');
16
+ const config = desktopDeviceKeys(process.argv[2], loadMeshConfig());
17
+ const crypto = new MeshCrypto(config.netKey, config.appKey);
18
+ const sequence = new SequenceStore(meshDirectory(), crypto.networkId.toString('hex'), config.source);
19
+ const link = new MeshTransport(config, sequence, process.argv.includes('--debug'));
20
+ const results: unknown[] = [];
21
+ const capable: { key: string; address: number; model: MeshModel; groups: number[] }[] = [];
22
+ const added: typeof capable = [];
23
+ const original = new Map<number, FixtureState>();
24
+ let discoveryFailed = false;
25
+ try {
26
+ await link.connect();
27
+ for (const light of config.lights) {
28
+ try {
29
+ const data = await readComposition(link, light.address);
30
+ console.log(light.key, data);
31
+ const models = [];
32
+ for (const model of data.models.filter((item) => item.company !== undefined || item.model >= 0x1000)) {
33
+ const bindings = await modelBindings(link, light.address, model);
34
+ if (!bindings.includes(0)) continue;
35
+ const groups = await subscriptions(link, light.address, model);
36
+ models.push({ model, groups, bindings });
37
+ capable.push({ key: light.key, address: light.address, model, groups });
38
+ }
39
+ const phase = await link.configuration(
40
+ light.address,
41
+ Buffer.from([0x80, 0x15, 0, 0]),
42
+ (data) => data.length === 6 && data[0] === 0x80 && data[1] === 0x17
43
+ );
44
+ console.log(light.key, 'subscriptions', models, 'key refresh status', phase.toString('hex'));
45
+ results.push({
46
+ key: light.key,
47
+ composition: data,
48
+ subscriptions: models,
49
+ keyRefreshStatus: phase.toString('hex'),
50
+ });
51
+ } catch (error) {
52
+ discoveryFailed = true;
53
+ console.error(light.key, (error as Error).message);
54
+ results.push({ key: light.key, error: (error as Error).message });
55
+ }
56
+ }
57
+ if (process.argv.includes('--exercise')) {
58
+ const group = 0xce01;
59
+ if (discoveryFailed || !capable.length || capable.some((item) => item.groups.includes(group)))
60
+ throw new Error('Incomplete discovery or test group already in use');
61
+ for (const light of config.lights) {
62
+ const state = await link.readState(light.address);
63
+ if (state.intensity > 50) throw new Error('Use at most 5% output for this check');
64
+ original.set(light.address, state);
65
+ }
66
+ for (const item of capable.filter(
67
+ (item) => item.key !== 'front' && item.model.company === undefined && item.model.model === 0x1000
68
+ )) {
69
+ console.log('Adding temporary subscription', item.key, item.model);
70
+ added.push(item);
71
+ await changeSubscription(link, item.address, item.model, group, false);
72
+ }
73
+ await link.send(group, brightnessPacket(10));
74
+ await new Promise((resolve) => setTimeout(resolve, 500));
75
+ for (const light of config.lights) {
76
+ const state = await link.readState(light.address);
77
+ const expected = light.key === 'front' ? original.get(light.address)?.intensity : 10;
78
+ if (state.intensity !== expected)
79
+ throw new Error(`Native group did not match ${light.key}: ${state.intensity}, expected ${expected}`);
80
+ }
81
+ console.log('Native subset group controlled desk/back without changing front');
82
+ }
83
+ } finally {
84
+ const failures: string[] = [];
85
+ for (const [address, state] of original) {
86
+ try {
87
+ await link.send(address, brightnessPacket(state.intensity));
88
+ const restored = await link.readState(address);
89
+ if (restored.intensity !== state.intensity) failures.push(`Could not restore brightness at ${address}`);
90
+ } catch (error) {
91
+ failures.push(`${address}: ${error}`);
92
+ }
93
+ }
94
+ for (const item of added.reverse()) {
95
+ try {
96
+ await changeSubscription(link, item.address, item.model, 0xce01, true);
97
+ } catch (error) {
98
+ failures.push(`Remove test subscription ${item.address}: ${error}`);
99
+ }
100
+ }
101
+ await link.disconnect();
102
+ sequence.close();
103
+ if (failures.length) {
104
+ console.error(failures.join('\n'));
105
+ process.exitCode = 1;
106
+ }
107
+ writeFileSync(`artifacts/config-probe-${Date.now()}.json`, JSON.stringify(results, null, 2), { mode: 0o600 });
108
+ }
109
+ process.exit(process.exitCode ?? 0);
@@ -0,0 +1,82 @@
1
+ import assert from 'node:assert/strict';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { z } from 'zod';
4
+ import { COLOR_EFFECTS, WHITE_EFFECTS } from '../src/ble/effects.js';
5
+ import { SavedStateSchema } from '../src/ble/library.js';
6
+ import type { FixtureState } from '../src/ble/telink.js';
7
+ import { captureWebcam } from './webcam.js';
8
+
9
+ const base = process.env.AMARAN_BLE_URL ?? 'http://127.0.0.1:2708';
10
+ const run = Date.now();
11
+ const original: Record<string, FixtureState> = {};
12
+ const results: unknown[] = [];
13
+ const errors: string[] = [];
14
+ let changed = false;
15
+ async function request(key: string, action: string, body?: object) {
16
+ const response = await fetch(`${base}/lights/${key}/${action}`, {
17
+ method: body ? 'POST' : 'GET',
18
+ ...(body ? { body: JSON.stringify(body), headers: { 'content-type': 'application/json' } } : {}),
19
+ signal: AbortSignal.timeout(60_000),
20
+ });
21
+ const data = z
22
+ .object({ ok: z.boolean(), result: z.unknown().optional(), error: z.string().optional() })
23
+ .parse(await response.json());
24
+ if (!response.ok || !data.ok) throw new Error(`${key}/${action}: ${data.error ?? response.status}`);
25
+ const state = SavedStateSchema.parse(data.result);
26
+ results.push({ key, action, body, state });
27
+ return state;
28
+ }
29
+ try {
30
+ for (const key of ['desk', 'front', 'back']) original[key] = await request(key, 'state');
31
+ assert.ok(
32
+ Object.values(original).every((state) => state.mode === 'cct'),
33
+ 'Begin with a steady CCT setup'
34
+ );
35
+ changed = true;
36
+ for (const key of Object.keys(original)) await request(key, 'cct', { kelvin: 3200, brightness: 0 });
37
+ captureWebcam(`effects-${run}-dark-before`);
38
+ for (const key of Object.keys(original)) {
39
+ for (const name of key === 'back' ? COLOR_EFFECTS : WHITE_EFFECTS) {
40
+ const state = await request(key, 'effect', { name, brightness: 0, frequency: 1 });
41
+ assert.equal(state.effect, name);
42
+ assert.equal(state.intensity, 0);
43
+ assert.equal((await request(key, 'effect-speed', { value: 2 })).frequency, 2);
44
+ assert.equal((await request(key, 'effect-intensity', { value: 0 })).intensity, 0);
45
+ console.log(`${key}: ${name}, zero output, verified`);
46
+ }
47
+ if (key === 'back') {
48
+ for (const name of ['faulty-bulb', 'pulsing']) {
49
+ const state = await request(key, 'effect', { name, brightness: 0, frequency: 1, hue: 240, saturation: 80 });
50
+ assert.equal(state.hue, 240);
51
+ assert.equal(state.sat, 80);
52
+ assert.equal(state.intensity, 0);
53
+ }
54
+ }
55
+ await request(key, 'effect-stop', {});
56
+ }
57
+ captureWebcam(`effects-${run}-dark-after`);
58
+ } catch (error) {
59
+ errors.push(String(error));
60
+ } finally {
61
+ if (changed)
62
+ for (const [key, state] of Object.entries(original)) {
63
+ try {
64
+ await request(key, 'cct', {
65
+ kelvin: state.cct,
66
+ brightness: state.intensity / 10,
67
+ ...(key === 'back' ? { gm: state.gm } : {}),
68
+ });
69
+ await request(key, state.sleep ? 'off' : 'on', {});
70
+ const restored = await request(key, 'state');
71
+ for (const field of ['cct', 'sleep', 'intensity'] as const) assert.equal(restored[field], state[field]);
72
+ if (key === 'back') assert.equal(restored.gm, state.gm);
73
+ } catch (error) {
74
+ errors.push(`Restore ${key}: ${error}`);
75
+ }
76
+ }
77
+ writeFileSync(`artifacts/effects-${run}-results.json`, JSON.stringify({ original, results, errors }, null, 2), {
78
+ mode: 0o600,
79
+ });
80
+ }
81
+ if (errors.length) throw new Error(errors.join('\n'));
82
+ console.log(`All native effect settings verified at zero output; artifacts/effects-${run}-results.json`);
@@ -0,0 +1,44 @@
1
+ import assert from 'node:assert/strict';
2
+ import { setTimeout as delay } from 'node:timers/promises';
3
+ import { MeshCrypto } from '../src/ble/crypto.js';
4
+ import { loadMeshConfig, meshDirectory, SequenceStore } from '../src/ble/storage.js';
5
+ import { cctPacket, powerPacket } from '../src/ble/telink.js';
6
+ import { MeshTransport } from '../src/ble/transport.js';
7
+ import { captureWebcam } from './webcam.js';
8
+
9
+ const config = loadMeshConfig();
10
+ const crypto = new MeshCrypto(config.netKey, config.appKey);
11
+ const sequence = new SequenceStore(meshDirectory(), crypto.networkId.toString('hex'), config.source);
12
+ const transport = new MeshTransport(config, sequence, true);
13
+ try {
14
+ await transport.connect();
15
+ const back = config.lights.find((light) => light.model === '150c');
16
+ if (!back) throw new Error('150c fixture not found');
17
+ const original = await transport.readState(back.address);
18
+ if (process.argv[2] === 'gm') {
19
+ try {
20
+ for (const gm of [-30, 0, 30]) {
21
+ await transport.send(back.address, cctPacket(3200, 10, gm));
22
+ await delay(300);
23
+ const state = await transport.readState(back.address);
24
+ console.log('G/M applied', state);
25
+ assert.equal(state.gm, gm);
26
+ assert.equal(state.cct, 3200);
27
+ assert.equal(state.intensity, 10);
28
+ console.log(captureWebcam(`gm-${gm < 0 ? 'minus' : 'plus'}-${Math.abs(gm)}-${Date.now()}`));
29
+ }
30
+ } finally {
31
+ await transport.send(back.address, cctPacket(original.cct ?? 3200, original.intensity, 0));
32
+ await delay(300);
33
+ await transport.send(back.address, powerPacket(!original.sleep));
34
+ console.log('Restored with corrected neutral tint', await transport.readState(back.address));
35
+ }
36
+ } else {
37
+ console.log(original);
38
+ for (const light of config.lights) console.log(light.key, await transport.readFan(light.address));
39
+ }
40
+ } finally {
41
+ await transport.disconnect();
42
+ sequence.close();
43
+ }
44
+ process.exit(0);
@@ -0,0 +1,129 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { mkdirSync, writeFileSync } from 'node:fs';
4
+ import { z } from 'zod';
5
+ import { FanStateSchema, FanStatesSchema } from '../src/ble/fan.js';
6
+ import { SavedStateSchema } from '../src/ble/library.js';
7
+ import { FAN_MODES, parseFanMode } from '../src/ble/telink.js';
8
+
9
+ const base = process.env.AMARAN_BLE_URL ?? 'http://127.0.0.1:2708';
10
+ const run = Date.now();
11
+ const records: unknown[] = [];
12
+ const failures: string[] = [];
13
+ let original: z.infer<typeof FanStatesSchema> | undefined;
14
+ let group: string | undefined;
15
+ let changed = false;
16
+ const lights: Record<string, z.infer<typeof SavedStateSchema>> = {};
17
+
18
+ async function request(route: string, body?: object, method = body ? 'POST' : 'GET') {
19
+ const response = await fetch(`${base}${route}`, {
20
+ method,
21
+ headers: { connection: 'close', ...(body ? { 'content-type': 'application/json' } : {}) },
22
+ ...(body ? { body: JSON.stringify(body) } : {}),
23
+ signal: AbortSignal.timeout(60_000),
24
+ });
25
+ const data = z
26
+ .object({ ok: z.boolean(), result: z.unknown().optional(), error: z.string().optional() })
27
+ .parse(await response.json());
28
+ if (!response.ok || !data.ok) throw new Error(`${route}: ${data.error ?? response.status}`);
29
+ records.push({ route, body, result: data.result });
30
+ return data.result;
31
+ }
32
+ function cli(...args: string[]) {
33
+ const output = execFileSync(
34
+ process.execPath,
35
+ ['dist/cli.js', 'fan', ...args, '--backend', 'ble', '--url', base, '--json'],
36
+ {
37
+ encoding: 'utf8',
38
+ timeout: 60_000,
39
+ }
40
+ );
41
+ const data = FanStatesSchema.parse(JSON.parse(output));
42
+ records.push({ cli: args, result: data });
43
+ return data;
44
+ }
45
+ async function checkLighting() {
46
+ for (const [key, before] of Object.entries(lights)) {
47
+ const after = SavedStateSchema.parse(await request(`/lights/${key}/state`));
48
+ const { observedAt: _beforeTime, ...beforeSettings } = before;
49
+ const { observedAt: _afterTime, ...afterSettings } = after;
50
+ assert.deepEqual(afterSettings, beforeSettings, `${key}: fan control changed lighting`);
51
+ }
52
+ }
53
+
54
+ try {
55
+ original = FanStatesSchema.parse(await request('/fans'));
56
+ const keys = Object.keys(original.states);
57
+ for (const [key, state] of Object.entries(original.states)) {
58
+ parseFanMode(state.mode);
59
+ assert.notEqual(state.mode, FAN_MODES.manual, 'Cannot infer an original manual RPM setpoint for restoration');
60
+ assert.equal(state.highTemperature, false);
61
+ assert.ok(state.supported.smart && state.supported.medium);
62
+ lights[key] = SavedStateSchema.parse(await request(`/lights/${key}/state`));
63
+ assert.ok(
64
+ lights[key].intensity <= 50,
65
+ 'Run fan checks at 5% brightness or less; do not stress-test thermal protection'
66
+ );
67
+ }
68
+ changed = true;
69
+ for (const state of Object.values(cli('mode', 'all', 'smart').states)) assert.equal(state.mode, 1);
70
+ for (const key of keys) {
71
+ const selected = cli('mode', key, 'medium').states[key];
72
+ assert.equal(selected.mode, 5);
73
+ assert.equal(selected.highTemperature, false);
74
+ assert.equal(selected.rpmStatus, selected.speed === 0 ? 'zero-reported' : 'rotation-reported');
75
+ assert.equal(FanStateSchema.parse(await request(`/lights/${key}/fan`)).mode, 5);
76
+ }
77
+ const savedGroup = z.object({ id: z.string() }).parse(await request('/groups', { name: `Fan verification ${run}` }));
78
+ group = savedGroup.id;
79
+ const members = [...new Set([keys[0], keys[keys.length - 1]])];
80
+ for (const member of members) await request(`/groups/${encodeURIComponent(group)}/members`, { member });
81
+ const grouped = cli('mode', group, 'smart');
82
+ assert.deepEqual(Object.keys(grouped.states).sort(), [...members].sort());
83
+ for (const state of Object.values(grouped.states)) assert.equal(state.mode, 1);
84
+ const deduplicated = FanStatesSchema.parse(await request('/fans', { targets: [group, members[0]], mode: 'medium' }));
85
+ assert.equal(Object.keys(deduplicated.states).length, members.length);
86
+ assert.equal(Object.keys(cli('info', group).states).length, members.length);
87
+ for (const mode of ['off', 'silent', 'low', 'manual']) {
88
+ await assert.rejects(
89
+ request('/fans', { targets: 'all', mode, ...(mode === 'manual' ? { rpm: 2200 } : {}) }),
90
+ /does not advertise/
91
+ );
92
+ }
93
+ await checkLighting();
94
+ if (process.argv.includes('--restart-service')) {
95
+ for (const action of ['stop', 'start'])
96
+ execFileSync(process.execPath, ['dist/cli.js', 'ble', 'service', action], { stdio: 'inherit', timeout: 100_000 });
97
+ for (const state of Object.values(cli('info').states))
98
+ assert.equal(state.mode, 5, 'Fan profile was reset on daemon restart');
99
+ await checkLighting();
100
+ }
101
+ } catch (error) {
102
+ failures.push(String(error));
103
+ } finally {
104
+ if (changed && original)
105
+ for (const [key, state] of Object.entries(original.states)) {
106
+ try {
107
+ const restored = FanStateSchema.parse(await request(`/lights/${key}/fan`, { mode: state.mode }));
108
+ assert.equal(restored.mode, state.mode);
109
+ assert.equal(restored.highTemperature, false);
110
+ } catch (error) {
111
+ failures.push(`Restore ${key}: ${error}`);
112
+ }
113
+ }
114
+ if (group) {
115
+ try {
116
+ await request(`/groups/${encodeURIComponent(group)}`, undefined, 'DELETE');
117
+ } catch (error) {
118
+ failures.push(`Group cleanup: ${error}`);
119
+ }
120
+ }
121
+ mkdirSync('artifacts/fan-safety', { recursive: true });
122
+ writeFileSync(
123
+ `artifacts/fan-safety/live-${run}.json`,
124
+ JSON.stringify({ original, lights, records, failures }, null, 2),
125
+ { mode: 0o600 }
126
+ );
127
+ }
128
+ if (failures.length) throw new Error(failures.join('\n'));
129
+ console.log(`Fan profiles, CLI groups/all, telemetry and restoration passed: artifacts/fan-safety/live-${run}.json`);
@@ -0,0 +1,37 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { MeshCrypto } from '../src/ble/crypto.js';
3
+ import { packet } from '../src/ble/packets.js';
4
+ import { decodeProductInfo } from '../src/ble/settings.js';
5
+ import { loadMeshConfig, meshDirectory, SequenceStore } from '../src/ble/storage.js';
6
+ import { MeshTransport } from '../src/ble/transport.js';
7
+
8
+ const config = loadMeshConfig();
9
+ const crypto = new MeshCrypto(config.netKey, config.appKey);
10
+ const sequence = new SequenceStore(meshDirectory(), crypto.networkId.toString('hex'), config.source);
11
+ const link = new MeshTransport(config, sequence);
12
+ const results: unknown[] = [];
13
+ try {
14
+ await link.connect();
15
+ for (const light of config.lights) {
16
+ const info = await link.readPacket(light.address, packet(0), decodeProductInfo);
17
+ console.log(light.key, info);
18
+ let dimming: unknown;
19
+ try {
20
+ dimming = await link.readPacket(light.address, packet(8), (data) => {
21
+ if (data.length !== 10 || (data[9] & 127) !== 8) return undefined;
22
+ if ((data.subarray(1).reduce((sum, byte) => sum + byte, 0) & 255) !== data[0])
23
+ throw new Error('Invalid curve checksum');
24
+ return { mode: data[8], raw: data.toString('hex') };
25
+ });
26
+ } catch (error) {
27
+ dimming = { unavailable: (error as Error).message };
28
+ }
29
+ console.log(light.key, 'dimming query', dimming);
30
+ results.push({ key: light.key, info, dimming });
31
+ }
32
+ } finally {
33
+ await link.disconnect();
34
+ sequence.close();
35
+ writeFileSync(`artifacts/feature-probe-${Date.now()}.json`, JSON.stringify(results, null, 2), { mode: 0o600 });
36
+ }
37
+ process.exit(0);