amaran-light-cli 1.11.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 (363) hide show
  1. package/README.md +108 -14
  2. package/dist/__tests__/autoCct.test.js +27 -0
  3. package/dist/__tests__/autoCct.test.js.map +1 -1
  4. package/dist/__tests__/bleHttpControl.test.d.ts +2 -0
  5. package/dist/__tests__/bleHttpControl.test.d.ts.map +1 -0
  6. package/dist/__tests__/bleHttpControl.test.js +230 -0
  7. package/dist/__tests__/bleHttpControl.test.js.map +1 -0
  8. package/dist/__tests__/bleStack.test.d.ts +2 -0
  9. package/dist/__tests__/bleStack.test.d.ts.map +1 -0
  10. package/dist/__tests__/bleStack.test.js +1239 -0
  11. package/dist/__tests__/bleStack.test.js.map +1 -0
  12. package/dist/__tests__/cctUtil.test.js +9 -4
  13. package/dist/__tests__/cctUtil.test.js.map +1 -1
  14. package/dist/__tests__/circadianDashboardSettings.test.d.ts +2 -0
  15. package/dist/__tests__/circadianDashboardSettings.test.d.ts.map +1 -0
  16. package/dist/__tests__/circadianDashboardSettings.test.js +82 -0
  17. package/dist/__tests__/circadianDashboardSettings.test.js.map +1 -0
  18. package/dist/__tests__/circadianIsolation.test.d.ts +2 -0
  19. package/dist/__tests__/circadianIsolation.test.d.ts.map +1 -0
  20. package/dist/__tests__/circadianIsolation.test.js +30 -0
  21. package/dist/__tests__/circadianIsolation.test.js.map +1 -0
  22. package/dist/__tests__/cli.config.test.js +49 -0
  23. package/dist/__tests__/cli.config.test.js.map +1 -1
  24. package/dist/__tests__/cli.smoke.test.js +34 -0
  25. package/dist/__tests__/cli.smoke.test.js.map +1 -1
  26. package/dist/__tests__/currentCct.test.d.ts +2 -0
  27. package/dist/__tests__/currentCct.test.d.ts.map +1 -0
  28. package/dist/__tests__/currentCct.test.js +29 -0
  29. package/dist/__tests__/currentCct.test.js.map +1 -0
  30. package/dist/__tests__/dashboard.test.d.ts +2 -0
  31. package/dist/__tests__/dashboard.test.d.ts.map +1 -0
  32. package/dist/__tests__/dashboard.test.js +126 -0
  33. package/dist/__tests__/dashboard.test.js.map +1 -0
  34. package/dist/__tests__/daylightSimulationParseUtils.test.d.ts +2 -0
  35. package/dist/__tests__/daylightSimulationParseUtils.test.d.ts.map +1 -0
  36. package/dist/__tests__/daylightSimulationParseUtils.test.js +16 -0
  37. package/dist/__tests__/daylightSimulationParseUtils.test.js.map +1 -0
  38. package/dist/__tests__/desktopImport.test.d.ts +2 -0
  39. package/dist/__tests__/desktopImport.test.d.ts.map +1 -0
  40. package/dist/__tests__/desktopImport.test.js +234 -0
  41. package/dist/__tests__/desktopImport.test.js.map +1 -0
  42. package/dist/__tests__/lightControl.basic.test.d.ts +2 -0
  43. package/dist/__tests__/lightControl.basic.test.d.ts.map +1 -0
  44. package/dist/__tests__/lightControl.basic.test.js +119 -0
  45. package/dist/__tests__/lightControl.basic.test.js.map +1 -0
  46. package/dist/__tests__/lightControl.colorEffects.test.d.ts +2 -0
  47. package/dist/__tests__/lightControl.colorEffects.test.d.ts.map +1 -0
  48. package/dist/__tests__/lightControl.colorEffects.test.js +170 -0
  49. package/dist/__tests__/lightControl.colorEffects.test.js.map +1 -0
  50. package/dist/__tests__/lightControl.management.test.d.ts +2 -0
  51. package/dist/__tests__/lightControl.management.test.d.ts.map +1 -0
  52. package/dist/__tests__/lightControl.management.test.js +90 -0
  53. package/dist/__tests__/lightControl.management.test.js.map +1 -0
  54. package/dist/__tests__/lightControlTestUtils.d.ts +10 -0
  55. package/dist/__tests__/lightControlTestUtils.d.ts.map +1 -0
  56. package/dist/__tests__/lightControlTestUtils.js +57 -0
  57. package/dist/__tests__/lightControlTestUtils.js.map +1 -0
  58. package/dist/__tests__/meshConfiguration.test.d.ts +2 -0
  59. package/dist/__tests__/meshConfiguration.test.d.ts.map +1 -0
  60. package/dist/__tests__/meshConfiguration.test.js +78 -0
  61. package/dist/__tests__/meshConfiguration.test.js.map +1 -0
  62. package/dist/__tests__/nativeGroups.test.d.ts +2 -0
  63. package/dist/__tests__/nativeGroups.test.d.ts.map +1 -0
  64. package/dist/__tests__/nativeGroups.test.js +104 -0
  65. package/dist/__tests__/nativeGroups.test.js.map +1 -0
  66. package/dist/__tests__/programApi.test.d.ts +2 -0
  67. package/dist/__tests__/programApi.test.d.ts.map +1 -0
  68. package/dist/__tests__/programApi.test.js +73 -0
  69. package/dist/__tests__/programApi.test.js.map +1 -0
  70. package/dist/__tests__/programs.test.d.ts +2 -0
  71. package/dist/__tests__/programs.test.d.ts.map +1 -0
  72. package/dist/__tests__/programs.test.js +176 -0
  73. package/dist/__tests__/programs.test.js.map +1 -0
  74. package/dist/__tests__/scheduleMaker.test.d.ts +2 -0
  75. package/dist/__tests__/scheduleMaker.test.d.ts.map +1 -0
  76. package/dist/__tests__/scheduleMaker.test.js +25 -0
  77. package/dist/__tests__/scheduleMaker.test.js.map +1 -0
  78. package/dist/ble/async.d.ts +2 -0
  79. package/dist/ble/async.d.ts.map +1 -0
  80. package/dist/ble/async.js +15 -0
  81. package/dist/ble/async.js.map +1 -0
  82. package/dist/ble/colors.d.ts +5 -0
  83. package/dist/ble/colors.d.ts.map +1 -0
  84. package/dist/ble/colors.js +44 -0
  85. package/dist/ble/colors.js.map +1 -0
  86. package/dist/ble/configuration.d.ts +22 -0
  87. package/dist/ble/configuration.d.ts.map +1 -0
  88. package/dist/ble/configuration.js +122 -0
  89. package/dist/ble/configuration.js.map +1 -0
  90. package/dist/ble/controller.d.ts +118 -0
  91. package/dist/ble/controller.d.ts.map +1 -0
  92. package/dist/ble/controller.js +931 -0
  93. package/dist/ble/controller.js.map +1 -0
  94. package/dist/ble/crypto.d.ts +34 -0
  95. package/dist/ble/crypto.d.ts.map +1 -0
  96. package/dist/ble/crypto.js +178 -0
  97. package/dist/ble/crypto.js.map +1 -0
  98. package/dist/ble/daemon.d.ts +2 -0
  99. package/dist/ble/daemon.d.ts.map +1 -0
  100. package/dist/ble/daemon.js +76 -0
  101. package/dist/ble/daemon.js.map +1 -0
  102. package/dist/ble/dashboard.d.ts +331 -0
  103. package/dist/ble/dashboard.d.ts.map +1 -0
  104. package/dist/ble/dashboard.js +280 -0
  105. package/dist/ble/dashboard.js.map +1 -0
  106. package/dist/ble/desktop.d.ts +4 -0
  107. package/dist/ble/desktop.d.ts.map +1 -0
  108. package/dist/ble/desktop.js +33 -0
  109. package/dist/ble/desktop.js.map +1 -0
  110. package/dist/ble/desktopLibrary.d.ts +12 -0
  111. package/dist/ble/desktopLibrary.d.ts.map +1 -0
  112. package/dist/ble/desktopLibrary.js +346 -0
  113. package/dist/ble/desktopLibrary.js.map +1 -0
  114. package/dist/ble/effects.d.ts +34 -0
  115. package/dist/ble/effects.d.ts.map +1 -0
  116. package/dist/ble/effects.js +138 -0
  117. package/dist/ble/effects.js.map +1 -0
  118. package/dist/ble/fan.d.ts +244 -0
  119. package/dist/ble/fan.d.ts.map +1 -0
  120. package/dist/ble/fan.js +46 -0
  121. package/dist/ble/fan.js.map +1 -0
  122. package/dist/ble/library.d.ts +341 -0
  123. package/dist/ble/library.d.ts.map +1 -0
  124. package/dist/ble/library.js +201 -0
  125. package/dist/ble/library.js.map +1 -0
  126. package/dist/ble/media.d.ts +133 -0
  127. package/dist/ble/media.d.ts.map +1 -0
  128. package/dist/ble/media.js +186 -0
  129. package/dist/ble/media.js.map +1 -0
  130. package/dist/ble/nativeGroups.d.ts +22 -0
  131. package/dist/ble/nativeGroups.d.ts.map +1 -0
  132. package/dist/ble/nativeGroups.js +114 -0
  133. package/dist/ble/nativeGroups.js.map +1 -0
  134. package/dist/ble/packets.d.ts +3 -0
  135. package/dist/ble/packets.d.ts.map +1 -0
  136. package/dist/ble/packets.js +15 -0
  137. package/dist/ble/packets.js.map +1 -0
  138. package/dist/ble/programs.d.ts +318 -0
  139. package/dist/ble/programs.d.ts.map +1 -0
  140. package/dist/ble/programs.js +329 -0
  141. package/dist/ble/programs.js.map +1 -0
  142. package/dist/ble/provisioning.d.ts +13 -0
  143. package/dist/ble/provisioning.d.ts.map +1 -0
  144. package/dist/ble/provisioning.js +39 -0
  145. package/dist/ble/provisioning.js.map +1 -0
  146. package/dist/ble/server.d.ts +18 -0
  147. package/dist/ble/server.d.ts.map +1 -0
  148. package/dist/ble/server.js +548 -0
  149. package/dist/ble/server.js.map +1 -0
  150. package/dist/ble/service.d.ts +2 -0
  151. package/dist/ble/service.d.ts.map +1 -0
  152. package/dist/ble/service.js +77 -0
  153. package/dist/ble/service.js.map +1 -0
  154. package/dist/ble/settings.d.ts +18 -0
  155. package/dist/ble/settings.d.ts.map +1 -0
  156. package/dist/ble/settings.js +25 -0
  157. package/dist/ble/settings.js.map +1 -0
  158. package/dist/ble/setup.d.ts +2 -0
  159. package/dist/ble/setup.d.ts.map +1 -0
  160. package/dist/ble/setup.js +31 -0
  161. package/dist/ble/setup.js.map +1 -0
  162. package/dist/ble/storage.d.ts +94 -0
  163. package/dist/ble/storage.d.ts.map +1 -0
  164. package/dist/ble/storage.js +132 -0
  165. package/dist/ble/storage.js.map +1 -0
  166. package/dist/ble/telink.d.ts +63 -0
  167. package/dist/ble/telink.d.ts.map +1 -0
  168. package/dist/ble/telink.js +108 -0
  169. package/dist/ble/telink.js.map +1 -0
  170. package/dist/ble/transitions.d.ts +3 -0
  171. package/dist/ble/transitions.d.ts.map +1 -0
  172. package/dist/ble/transitions.js +28 -0
  173. package/dist/ble/transitions.js.map +1 -0
  174. package/dist/ble/transport.d.ts +48 -0
  175. package/dist/ble/transport.d.ts.map +1 -0
  176. package/dist/ble/transport.js +370 -0
  177. package/dist/ble/transport.js.map +1 -0
  178. package/dist/circadian.d.ts +3 -0
  179. package/dist/circadian.d.ts.map +1 -1
  180. package/dist/circadian.js +2 -0
  181. package/dist/circadian.js.map +1 -1
  182. package/dist/cli.d.ts +3 -2
  183. package/dist/cli.d.ts.map +1 -1
  184. package/dist/cli.js +125 -6
  185. package/dist/cli.js.map +1 -1
  186. package/dist/commands/__tests__/newCommands.test.js +3 -4
  187. package/dist/commands/__tests__/newCommands.test.js.map +1 -1
  188. package/dist/commands/cmdUtils.d.ts +5 -2
  189. package/dist/commands/cmdUtils.d.ts.map +1 -1
  190. package/dist/commands/cmdUtils.js +29 -10
  191. package/dist/commands/cmdUtils.js.map +1 -1
  192. package/dist/commands/daylightSimulation/autoCct.d.ts.map +1 -1
  193. package/dist/commands/daylightSimulation/autoCct.js +67 -197
  194. package/dist/commands/daylightSimulation/autoCct.js.map +1 -1
  195. package/dist/commands/daylightSimulation/graphSchedule.d.ts +1 -1
  196. package/dist/commands/daylightSimulation/graphSchedule.d.ts.map +1 -1
  197. package/dist/commands/daylightSimulation/printSchedule.d.ts +1 -1
  198. package/dist/commands/daylightSimulation/printSchedule.d.ts.map +1 -1
  199. package/dist/commands/daylightSimulation/printSchedule.js +2 -2
  200. package/dist/commands/daylightSimulation/printSchedule.js.map +1 -1
  201. package/dist/commands/daylightSimulation/service.d.ts +1 -1
  202. package/dist/commands/daylightSimulation/service.d.ts.map +1 -1
  203. package/dist/commands/daylightSimulation/service.js +5 -3
  204. package/dist/commands/daylightSimulation/service.js.map +1 -1
  205. package/dist/commands/daylightSimulation/simulateSchedule.d.ts +1 -1
  206. package/dist/commands/daylightSimulation/simulateSchedule.d.ts.map +1 -1
  207. package/dist/commands/daylightSimulation/simulateSchedule.js +5 -4
  208. package/dist/commands/daylightSimulation/simulateSchedule.js.map +1 -1
  209. package/dist/commands/deviceControl/ble.d.ts +4 -0
  210. package/dist/commands/deviceControl/ble.d.ts.map +1 -0
  211. package/dist/commands/deviceControl/ble.js +214 -0
  212. package/dist/commands/deviceControl/ble.js.map +1 -0
  213. package/dist/commands/deviceControl/cct.d.ts.map +1 -1
  214. package/dist/commands/deviceControl/cct.js +41 -11
  215. package/dist/commands/deviceControl/cct.js.map +1 -1
  216. package/dist/commands/deviceControl/color.d.ts.map +1 -1
  217. package/dist/commands/deviceControl/color.js +13 -3
  218. package/dist/commands/deviceControl/color.js.map +1 -1
  219. package/dist/commands/deviceControl/config.d.ts.map +1 -1
  220. package/dist/commands/deviceControl/config.js +26 -3
  221. package/dist/commands/deviceControl/config.js.map +1 -1
  222. package/dist/commands/deviceControl/{discover.d.ts → desktop.d.ts} +3 -2
  223. package/dist/commands/deviceControl/desktop.d.ts.map +1 -0
  224. package/dist/commands/deviceControl/desktop.js +66 -0
  225. package/dist/commands/deviceControl/desktop.js.map +1 -0
  226. package/dist/commands/deviceControl/effect.d.ts.map +1 -1
  227. package/dist/commands/deviceControl/effect.js +90 -159
  228. package/dist/commands/deviceControl/effect.js.map +1 -1
  229. package/dist/commands/deviceControl/fan.d.ts.map +1 -1
  230. package/dist/commands/deviceControl/fan.js +77 -95
  231. package/dist/commands/deviceControl/fan.js.map +1 -1
  232. package/dist/commands/deviceControl/group.d.ts.map +1 -1
  233. package/dist/commands/deviceControl/group.js +43 -6
  234. package/dist/commands/deviceControl/group.js.map +1 -1
  235. package/dist/commands/deviceControl/hsi.d.ts.map +1 -1
  236. package/dist/commands/deviceControl/hsi.js +3 -1
  237. package/dist/commands/deviceControl/hsi.js.map +1 -1
  238. package/dist/commands/deviceControl/info.d.ts.map +1 -1
  239. package/dist/commands/deviceControl/info.js +0 -50
  240. package/dist/commands/deviceControl/info.js.map +1 -1
  241. package/dist/commands/deviceControl/intensity.d.ts.map +1 -1
  242. package/dist/commands/deviceControl/intensity.js +32 -8
  243. package/dist/commands/deviceControl/intensity.js.map +1 -1
  244. package/dist/commands/deviceControl/list.d.ts.map +1 -1
  245. package/dist/commands/deviceControl/list.js +3 -2
  246. package/dist/commands/deviceControl/list.js.map +1 -1
  247. package/dist/commands/deviceControl/power.d.ts.map +1 -1
  248. package/dist/commands/deviceControl/power.js +9 -3
  249. package/dist/commands/deviceControl/power.js.map +1 -1
  250. package/dist/commands/deviceControl/preset.d.ts.map +1 -1
  251. package/dist/commands/deviceControl/preset.js +56 -19
  252. package/dist/commands/deviceControl/preset.js.map +1 -1
  253. package/dist/commands/deviceControl/program.d.ts +4 -0
  254. package/dist/commands/deviceControl/program.d.ts.map +1 -0
  255. package/dist/commands/deviceControl/program.js +199 -0
  256. package/dist/commands/deviceControl/program.js.map +1 -0
  257. package/dist/commands/deviceControl/quickshot.d.ts.map +1 -1
  258. package/dist/commands/deviceControl/quickshot.js +75 -13
  259. package/dist/commands/deviceControl/quickshot.js.map +1 -1
  260. package/dist/commands/deviceControl/scene.d.ts.map +1 -1
  261. package/dist/commands/deviceControl/scene.js +72 -9
  262. package/dist/commands/deviceControl/scene.js.map +1 -1
  263. package/dist/commands/deviceControl/status.d.ts.map +1 -1
  264. package/dist/commands/deviceControl/status.js +26 -3
  265. package/dist/commands/deviceControl/status.js.map +1 -1
  266. package/dist/commands/parseUtils.d.ts +1 -1
  267. package/dist/commands/parseUtils.d.ts.map +1 -1
  268. package/dist/commands/parseUtils.js +1 -11
  269. package/dist/commands/parseUtils.js.map +1 -1
  270. package/dist/commands-api.d.ts +1 -1
  271. package/dist/commands-api.d.ts.map +1 -1
  272. package/dist/commands-api.js +1 -1
  273. package/dist/commands-api.js.map +1 -1
  274. package/dist/commands.d.ts.map +1 -1
  275. package/dist/commands.js +4 -2
  276. package/dist/commands.js.map +1 -1
  277. package/dist/config.d.ts +82 -0
  278. package/dist/config.d.ts.map +1 -1
  279. package/dist/config.js +13 -2
  280. package/dist/config.js.map +1 -1
  281. package/dist/daylightSimulation/cctUtil.d.ts.map +1 -1
  282. package/dist/daylightSimulation/cctUtil.js +2 -2
  283. package/dist/daylightSimulation/cctUtil.js.map +1 -1
  284. package/dist/daylightSimulation/constants.d.ts +10 -0
  285. package/dist/daylightSimulation/constants.d.ts.map +1 -1
  286. package/dist/daylightSimulation/constants.js +4 -0
  287. package/dist/daylightSimulation/constants.js.map +1 -1
  288. package/dist/daylightSimulation/currentCct.d.ts +42 -0
  289. package/dist/daylightSimulation/currentCct.d.ts.map +1 -0
  290. package/dist/daylightSimulation/currentCct.js +217 -0
  291. package/dist/daylightSimulation/currentCct.js.map +1 -0
  292. package/dist/daylightSimulation/dashboardSettings.d.ts +47 -0
  293. package/dist/daylightSimulation/dashboardSettings.d.ts.map +1 -0
  294. package/dist/daylightSimulation/dashboardSettings.js +95 -0
  295. package/dist/daylightSimulation/dashboardSettings.js.map +1 -0
  296. package/dist/daylightSimulation/dashboardStatus.d.ts +72 -0
  297. package/dist/daylightSimulation/dashboardStatus.d.ts.map +1 -0
  298. package/dist/daylightSimulation/dashboardStatus.js +191 -0
  299. package/dist/daylightSimulation/dashboardStatus.js.map +1 -0
  300. package/dist/daylightSimulation/parseUtils.d.ts +3 -0
  301. package/dist/daylightSimulation/parseUtils.d.ts.map +1 -0
  302. package/dist/daylightSimulation/parseUtils.js +25 -0
  303. package/dist/daylightSimulation/parseUtils.js.map +1 -0
  304. package/dist/daylightSimulation/scheduleMaker.d.ts +2 -2
  305. package/dist/daylightSimulation/scheduleMaker.d.ts.map +1 -1
  306. package/dist/daylightSimulation/scheduleMaker.js +6 -6
  307. package/dist/daylightSimulation/scheduleMaker.js.map +1 -1
  308. package/dist/daylightSimulation/types.d.ts +13 -2
  309. package/dist/daylightSimulation/types.d.ts.map +1 -1
  310. package/dist/daylightSimulation/types.js +0 -2
  311. package/dist/daylightSimulation/types.js.map +1 -1
  312. package/dist/device-control.d.ts +1 -0
  313. package/dist/device-control.d.ts.map +1 -1
  314. package/dist/device-control.js +1 -0
  315. package/dist/device-control.js.map +1 -1
  316. package/dist/deviceControl/bleHttpControl.d.ts +129 -0
  317. package/dist/deviceControl/bleHttpControl.d.ts.map +1 -0
  318. package/dist/deviceControl/bleHttpControl.js +753 -0
  319. package/dist/deviceControl/bleHttpControl.js.map +1 -0
  320. package/dist/deviceControl/lightControl.d.ts +8 -68
  321. package/dist/deviceControl/lightControl.d.ts.map +1 -1
  322. package/dist/deviceControl/lightControl.js +6 -246
  323. package/dist/deviceControl/lightControl.js.map +1 -1
  324. package/dist/deviceControl/lightControlCommands.d.ts +68 -0
  325. package/dist/deviceControl/lightControlCommands.d.ts.map +1 -0
  326. package/dist/deviceControl/lightControlCommands.js +244 -0
  327. package/dist/deviceControl/lightControlCommands.js.map +1 -0
  328. package/dist/deviceControl/types.d.ts +75 -4
  329. package/dist/deviceControl/types.d.ts.map +1 -1
  330. package/dist/deviceControl/types.js +1 -0
  331. package/dist/deviceControl/types.js.map +1 -1
  332. package/dist/index.d.ts +2 -1
  333. package/dist/index.d.ts.map +1 -1
  334. package/dist/index.js +1 -0
  335. package/dist/index.js.map +1 -1
  336. package/dist/test/MockLightServer.d.ts +1 -0
  337. package/dist/test/MockLightServer.d.ts.map +1 -1
  338. package/dist/test/MockLightServer.js +5 -0
  339. package/dist/test/MockLightServer.js.map +1 -1
  340. package/docs/CHANGELOG.md +37 -0
  341. package/docs/DIRECT_BLE.md +359 -0
  342. package/package.json +8 -4
  343. package/scripts/ble-cli-check.ts +129 -0
  344. package/scripts/ble-config-probe.ts +109 -0
  345. package/scripts/ble-effects-check.ts +82 -0
  346. package/scripts/ble-extended-probe.ts +44 -0
  347. package/scripts/ble-fan-check.ts +129 -0
  348. package/scripts/ble-feature-probe.ts +37 -0
  349. package/scripts/ble-followup-check.ts +165 -0
  350. package/scripts/ble-hardware-check.ts +99 -0
  351. package/scripts/ble-live-extended-check.ts +205 -0
  352. package/scripts/ble-non-ota-check.ts +193 -0
  353. package/scripts/ble-probe.ts +45 -0
  354. package/scripts/calibrate-webcam.ts +152 -0
  355. package/scripts/desktop-reference.ts +85 -0
  356. package/scripts/webcam.ts +129 -0
  357. package/dist/__tests__/lightControl.test.d.ts +0 -2
  358. package/dist/__tests__/lightControl.test.d.ts.map +0 -1
  359. package/dist/__tests__/lightControl.test.js +0 -424
  360. package/dist/__tests__/lightControl.test.js.map +0 -1
  361. package/dist/commands/deviceControl/discover.d.ts.map +0 -1
  362. package/dist/commands/deviceControl/discover.js +0 -29
  363. package/dist/commands/deviceControl/discover.js.map +0 -1
@@ -0,0 +1,359 @@
1
+ # Direct Bluetooth control
2
+
3
+ The included macOS BLE Mesh daemon controls already-provisioned amaran 200x / 200x S and 150c lights without the desktop application or its Python SDK. It continues the protocol investigation in `wesbos/amaran-BLE-control`, but replaces random sequence numbers, unverified writes, and fixed capability assumptions with an independently tested implementation.
4
+
5
+ ## Supported controls
6
+
7
+ | Fixture | CCT range | Dimming | Color | State |
8
+ | --- | --- | --- | --- | --- |
9
+ | 200x / 200x S | 2700-6500 K, 100-K steps | 0-100%, 1% steps | CCT only | Power, brightness, mode, CCT |
10
+ | 150c | 2500-7500 K, 100-K steps | 0-100%, 1% steps | CCT G/M (-100 to +100, steps of 10), basic HSI | Power, brightness, mode, CCT/G/M or HSI |
11
+
12
+ On, off and toggle are supported for each fixture or all configured lights. Zero brightness is not sleep: the fixture can report awake with its LEDs dark. CCT and HSI wake the fixture. Requests are rounded to the fixture resolution before transmission and verified against those applied values. A CCT request without brightness first reads and preserves actual brightness.
13
+
14
+ Additional controls include relative brightness/CCT, named or hex colors converted to HSI, native effects and trigger requests, fan profiles, batched control, CCT/HSI/scene transitions, persistent local groups/scenes/presets/quickshots with fan profiles, and manual overrides for circadian control.
15
+
16
+ Neither fixture advertises native RGB/XY; the 150c does not advertise advanced HSI CCT/G/M. Native mesh subscriptions, Desktop effect-preset/quickshot/workspace import, and host-driven timeline/audio/camera programs are implemented. Provisioning discovery and key-refresh phase inspection are read-only; joining/re-keying a mesh is not implemented. OTA/firmware updates are intentionally excluded. Extended CCT and unverified dimming-curve writes remain unavailable. Hardware validation uses two 200x S fixtures and one 150c; an original non-S 200x was not available.
17
+
18
+ ### Tint, colors and relative adjustments
19
+
20
+ ```sh
21
+ amaran-cli ble gm back 30
22
+ amaran-cli ble gm back -- -30
23
+ amaran-cli intensity --backend ble --relative -- -1 all
24
+ amaran-cli cct 100 all --relative --backend ble
25
+ amaran-cli color '#00ff00' back -i 1 --backend ble
26
+ ```
27
+
28
+ G/M is signed: negative is magenta, positive green, zero neutral. Ordinary CCT updates preserve actual 150c tint. Relative adjustments read current settings and clamp to each model's range; relative CCT requires CCT mode. Color names/hex express hue and saturation; brightness remains independently controlled. No RGB mode is implied.
29
+
30
+ ### Native effects
31
+
32
+ The 200x S exposes paparazzi, fireworks, faulty-bulb, lightning, TV, pulsing, strobe, explosion and fire. The 150c exposes paparazzi, fireworks, faulty-bulb, lightning, TV, pulsing, fire, cop-car and party-lights. The 150c's pulsing and faulty-bulb also support HSI variants.
33
+
34
+ ```sh
35
+ amaran-cli effect list --backend ble
36
+ amaran-cli effect custom desk pulsing --backend ble \
37
+ --params '{"brightness":1,"frequency":1,"kelvin":3200}'
38
+ amaran-cli effect custom back pulsing --backend ble \
39
+ --params '{"brightness":1,"frequency":1,"hue":120,"saturation":100}'
40
+ amaran-cli effect animation-speed back 4 --backend ble
41
+ amaran-cli effect stop all --backend ble
42
+ ```
43
+
44
+ `frequency` is 1-10. The separate native animation-speed field is 0-10 and is encoded only by Lightning,
45
+ Faulty Bulb and Pulsing; the command requires matching readback and rejects other effects. `effect speed` retains its
46
+ legacy meaning and changes frequency. CCT-capable effects accept `kelvin` and 150c `gm`; HSI variants instead accept
47
+ `hue` and `saturation`. TV/fire/fireworks/cop-car use native `palette` indices (0-2), not Kelvin. Party-lights accepts
48
+ saturation. Parameters that do not apply to an effect are rejected, not ignored. The legacy `effect intensity`
49
+ command uses 0-1000 API units, whereas `--params` brightness and `effect set -i` use percent.
50
+
51
+ Starting an effect saves its preceding steady state persistently. `effect stop` restores that CCT/HSI setting, brightness and power, including after daemon restart. An effect started by another controller without saved history needs an explicit CCT/HSI setting to stop. Avoid strobe and other rapid flashing around photosensitive people; hardware diagnostics deliberately exclude rapid flashing.
52
+
53
+ `effect stop GROUP` now restores each member independently after validating the complete group. `effect trigger DEVICE` (alias `retrigger`) sends one manual trigger request for an awake lightning, faulty-bulb, pulsing, strobe or explosion effect; groups and `all` are supported. Trigger requests are not retried or repaired, to avoid firing twice after a lost acknowledgement. The response verifies persistent effect settings but explicitly returns `triggerRequest.eventConfirmed:false`: the firmware does not provide a distinct acknowledgement that a transient event fired. This is not an optical trigger guarantee.
54
+
55
+ ### Cooling safety
56
+
57
+ ```sh
58
+ amaran-cli fan info all --backend ble
59
+ amaran-cli fan mode desk smart --backend ble
60
+ amaran-cli fan mode front medium --backend ble
61
+ amaran-cli fan mode back medium --backend ble
62
+ amaran-cli fan mode all smart --backend ble
63
+ amaran-cli fan mode Work medium --backend ble
64
+ amaran-cli fan info --backend ble --json
65
+ ```
66
+
67
+ All eight native fan modes are implemented. Names are case-insensitive, and numeric codes also work:
68
+
69
+ | Mode | Code | Additional input |
70
+ | --- | --- | --- |
71
+ | Manual | 0 | Required `--rpm` |
72
+ | Smart | 1 | None |
73
+ | Max | 2 | None |
74
+ | Off | 3 | LEDs must already be off or at zero brightness |
75
+ | High | 4 | None |
76
+ | Medium | 5 | None |
77
+ | Low | 6 | None |
78
+ | Silent | 7 | None |
79
+
80
+ Every selection still requires the fixture's actual advertised support. Both tested 200x S fixtures and the 150c advertise **Smart and Medium only**. Other modes are implemented in the protocol/API/CLI, not forced onto those fixtures.
81
+
82
+ On a fixture advertising manual control, use `fan mode DEVICE manual --rpm 2200 --backend ble`, or the equivalent `fan speed DEVICE 2200 --backend ble`. Manual mode never silently defaults to zero RPM. The integer range 0-65535 comes from the SDK's 16-bit wire field, not a manufacturer-rated speed range; a fixture may reject or clamp requests. Manual commands require matching mode and reported RPM readback, and fail explicitly if that cannot be verified. These fixtures cannot physically validate manual control because they do not advertise it.
83
+
84
+ Off and manual zero-RPM requests check LED state both before batch delivery and immediately before changing the fan. They never turn the LEDs off on the user's behalf. Pause lighting automation before using stopped cooling, and restore an appropriate cooling profile before increasing light output.
85
+
86
+ `fan info` defaults to all physical fixtures and accepts a fixture or local group. It reports mode/name, RPM, temperature, thermal-protection flag, and `allowedModes`; `--json` returns a keyed `states` object including raw advertised support flags. RPM is observational: `rpmStatus: "zero-reported"` does **not** assert a stopped/failed fan, and a successful Medium selection does not require a positive RPM. The earlier Smart-only restriction and positive-RPM assumption have been removed.
87
+
88
+ The [manufacturer's 150c settings guide](https://help.amarancreators.com/en/amaran-150c-300c/light-configuration-settings) documents on-fixture over-temperature shutdown and lockout requiring a power cycle. It does not document a mandatory minimum RPM or exact fan-start thresholds; an equivalent shutdown sequence has not been established for the 200x S. Fan control does not invent temperature thresholds or bypass firmware protection. Any reported active thermal protection blocks profile changes, including automatic Smart recovery. The fan-control path never power-cycles or wakes the light.
89
+
90
+ Fan changes prevalidate every target, recheck immediately before each write, and verify the selected native mode afterward. Already-selected automatic profiles are no-ops. Manual RPM requests always write their explicit setpoint rather than mistaking the current RPM reading for a remembered target. Actual communication failures, mode mismatches and unverified manual RPM remain errors; a readable, non-tripped fixture can be returned to Smart if verification fails, without hiding the original failure. Cancellation may similarly restore Smart after a write. If protection is active or recovery cannot be verified, the error explicitly says so.
91
+
92
+ Groups and `all` are serialized, deduplicated and verified per fixture, not mesh-broadcast fan commands. Partial failures identify verified members and may leave some profiles changed. Fan control sends no LED power/color/brightness commands; any native firmware output limiting or thermal protection remains in the fixture. The daemon does not reset fan profiles at startup.
93
+
94
+ ### All-light delivery, groups and saved lighting
95
+
96
+ ```sh
97
+ amaran-cli intensity 5 all --backend ble
98
+ amaran-cli ble batch brightness --targets all --args '{"value":5}' --broadcast
99
+ amaran-cli ble fade 1 3 --targets all
100
+ amaran-cli group create Work --backend ble
101
+ amaran-cli group add Work desk --backend ble
102
+ amaran-cli group add Work front --backend ble
103
+ amaran-cli group show Work --backend ble
104
+ amaran-cli intensity 5 Work --backend ble
105
+ amaran-cli status Work --backend ble
106
+ amaran-cli scene save Evening --backend ble
107
+ amaran-cli scene save Work --targets desk,front --backend ble
108
+ amaran-cli scene show Evening --backend ble
109
+ amaran-cli scene recall Evening --backend ble
110
+ amaran-cli preset save back Portrait --backend ble
111
+ amaran-cli preset show Portrait --backend ble
112
+ amaran-cli preset recall back Portrait --backend ble
113
+ amaran-cli preset recall back Portrait --fade 2 --backend ble
114
+ amaran-cli quickshot save Workday --backend ble
115
+ amaran-cli quickshot save Portrait --targets back --backend ble
116
+ amaran-cli quickshot show Workday --backend ble
117
+ amaran-cli quickshot set Workday --backend ble
118
+ amaran-cli quickshot set Workday --fade 2 --backend ble
119
+ ```
120
+
121
+ Normal bulk commands prevalidate and read all targets before sending a burst of unicast packets, then verify every member. Explicit broadcast sends one mesh packet and verifies each member, repairing mismatches individually. It requires the complete configured target set and identical encoded settings. **Broadcast reaches every provisioned node on that mesh**, including any nodes absent from the configuration; it is not a subset-group operation. Neither method promises atomic or hard-real-time synchronization.
122
+
123
+ Groups start as local logical membership lists and can optionally be enabled as verified native subscriptions. They accept shared lighting settings; inspection uses `status GROUP` because members may differ. They are excluded from physical-light automation to avoid duplicate commands. Presets hold one fixture's state; scenes and quickshots hold a multi-fixture snapshot, including power, tint, effect parameters and native fan profiles. Manual fan setpoints cannot be inferred from measured RPM, so snapshots refuse an active Manual fan profile. Old saved entries without fan settings remain compatible. Local and imported entries share the same on-disk library, while stable Desktop source IDs keep repeat imports idempotent. Retargeting a preset validates the receiving model before changing it.
124
+
125
+ `group rename ID NAME` preserves identity/membership. `preset update ID --name NAME` and `quickshot update ID --name NAME` replace their saved states from the original fixture targets, optionally renaming them. Direct BLE is the default backend; `--backend ble` remains accepted when an explicit declaration is useful in automation.
126
+ The corresponding `show` commands print the complete persisted record, including per-fixture power, color/effect parameters, fan profile, and native group metadata, without applying it.
127
+
128
+ `ble fade` provides a host-paced brightness transition over 0.5-20 seconds, at up to two updates per second with whole-percent steps. It preserves power state, rejects active native effects, and verifies final brightness on every target. Interrupting it stops subsequent writes and may leave intermediate brightness. Saved-state recall is serialized, not atomic; failures can leave some fixtures changed.
129
+
130
+ ```sh
131
+ amaran-cli ble transition cct 2 --targets all --args '{"kelvin":4500,"brightness":5}'
132
+ amaran-cli ble transition hsi 2 --targets back --args '{"hue":120,"saturation":100,"brightness":1}'
133
+ amaran-cli scene recall Evening --fade 3 --backend ble
134
+ ```
135
+
136
+ CCT transitions interpolate Kelvin/tint; HSI transitions take the shortest hue arc. CCT/HSI mode changes fade through zero output and require at least one second. These are host-paced 0.5-20 second transitions at up to two updates/second, not native timed/atomic fades. Active effects must be stopped first. Final color, brightness, power and saved fan settings are verified; cancellation leaves intermediate settings. Sleeping scene entries are restored with zero emitted output before their remembered brightness is reinstated, avoiding an on/off flash.
137
+
138
+ ## Manual control and testing
139
+
140
+ ### Native groups and read-only mesh discovery
141
+
142
+ ```sh
143
+ # Private, same-mesh credentials only; keys are never returned by the HTTP API.
144
+ amaran-cli ble mesh import-keys /path/to/amaran.db
145
+ amaran-cli ble mesh inspect
146
+ amaran-cli ble mesh discover
147
+ amaran-cli group native Work enable --backend ble
148
+ amaran-cli intensity 5 Work --backend ble
149
+ amaran-cli group native Work sync --backend ble
150
+ amaran-cli group native Work disable --backend ble
151
+ ```
152
+
153
+ Device Key import verifies matching network/application keys, fixture MACs/addresses, and authenticated composition responses before persisting keys under the private mesh config directory. Mesh inspection reads actual device composition, existing application bindings, subscriptions and key-refresh phase. It does not trust Desktop's cached composition, which differed from the live 150c during verification.
154
+
155
+ Native group setup uses the already-bound primary Generic OnOff model (`0x1000`), verified to route these fixtures' native lighting commands. It allocates an unused `0xc000-0xfeff` address and never replaces other subscriptions. A persistent pending record is written before changes; interrupted operations can be resumed with `sync` or removed with `disable`. Native membership updates are verified, and deleting a native group first removes its owned subscriptions. No reset, application-key rebind or key rotation is performed.
156
+
157
+ Ready native groups use one group-addressed lighting packet when members need identical encoded settings; otherwise delivery falls back to explicit unicast and reports that choice. Pending groups do not use their native address. `ble mesh discover` lists provisioning-service advertisements only: it does not identify ownership, pair devices, or reset/re-provision existing fixtures. Full provisioning/key refresh remain separate, unimplemented operations; inspection confirms phase but does not change it.
158
+
159
+ ### Desktop library import
160
+
161
+ ```sh
162
+ amaran-cli ble import-desktop /path/to/amaran.db
163
+ amaran-cli ble import-desktop /path/to/amaran.db --apply
164
+ amaran-cli ble import-desktop /path/to/amaran.db --apply --replace
165
+ ```
166
+
167
+ The default is a preview with errors/warnings. Import reads SQLite in read-only mode and maps fixture IDs by MAC. Desktop quickshots become local quickshots; Desktop scenes are workspaces and become local groups, not invented lighting snapshots. Desktop effect presets become retargetable local presets for Paparazzi, Lightning, TV, Fire, Strobe, Explosion, Faulty Bulb, Pulsing, Cop Car, Party Lights, and Fireworks. The importer preserves frequency, native animation speed, trigger mode, CCT- or HSI-effect variants, palettes and saturation, converts preset intensity from 0-100 to the direct API's 0-1000 units, and converts Desktop 150c tint from its stored 0-200 range to signed G/M. Each generic Desktop preset is stored against the first configured fixture that can reproduce it; `preset recall DEVICE ID` validates and retargets that state to another compatible fixture. Import never applies lighting or changes native subscriptions/fan modes.
168
+
169
+ Stable source IDs make repeat imports idempotent. Changed imports require `--replace`; unrelated local-name collisions and imported groups with native subscriptions are protected. Duplicate Desktop preset names receive stable numeric suffixes instead of being dropped. Unmapped, malformed, unsupported-effect, or fixture-incompatible records stop an apply unless `--allow-partial` explicitly permits the valid subset. Non-effect Desktop preset categories remain skipped with a warning. An implicit All group without explicit membership is reported and skipped.
170
+
171
+ ### Timeline and media control
172
+
173
+ ```sh
174
+ amaran-cli ble program timeline.json --targets all
175
+ amaran-cli ble audio /path/to/music.wav --targets all --seconds 30 --max 5
176
+ amaran-cli ble picker --image /path/to/image.png --targets back --seconds 10 --max 1
177
+ amaran-cli ble picker --camera 0 --targets back --region 0,0,1280,720 --seconds 10 --max 1
178
+ # Microphone capture is explicit, local, and requires macOS permission.
179
+ amaran-cli ble microphone 0 --targets all --seconds 30 --max 5
180
+ amaran-cli ble jobs list
181
+ amaran-cli ble jobs stop JOB_ID
182
+ ```
183
+
184
+ Timeline JSON contains `duration` (1-1200 seconds), optional `restore`, and `steps` with `at`, `action`, and `args`. For example:
185
+
186
+ ```json
187
+ {"duration":3,"steps":[{"at":0,"action":"brightness","args":{"value":1}},{"at":1,"action":"cct","args":{"kelvin":3200,"brightness":2}}]}
188
+ ```
189
+
190
+ These are bounded host-driven programs, not on-fixture timelines. Cues must be ordered at least 0.5 seconds apart. Media uses local `ffmpeg`: audio RMS drives brightness with smoothing; image/camera averages drive HSI. RGB is converted to the fixture's HSI controls, not a native RGB mode. Picker targets must support HSI; media starts require awake fixtures in steady CCT/HSI mode. Media brightness defaults to the fixture's full 100% range; use `--max` when a lower level is wanted. Maximum duration is 20 minutes, and data is not uploaded. Files must be local; media subprocesses cannot fetch network URLs. Camera and microphone capture currently require macOS.
191
+
192
+ Live camera/microphone capture runs in the foreground CLI, which has the terminal's OS permissions; only validated RGB averages/RMS numbers are sent to the loopback daemon. It does not bypass or modify macOS privacy settings. Live capture cannot use `--background`; local files and timelines can. `ffmpeg` is resolved from standard installation locations or `AMARAN_FFMPEG_PATH`, so a LaunchAgent's minimal PATH does not hide Homebrew installations. Media duration starts with the first usable sample; startup has a separate 10-second deadline.
193
+
194
+ The daemon owns each job, exposes preparing/running/restoring/terminal status, and keeps only bounded history. `--background` returns its ID; otherwise the CLI waits and Ctrl-C stops it. Input frames use a latest-value slot rather than an unbounded backlog. Default completion/explicit stop restores the original lighting/fan settings and prior override expiry; `--no-restore` keeps the final state. Manual lighting commands cancel overlapping jobs without restoring over the user's new command. A new program supersedes older overlapping programs. Daemon shutdown cancels jobs and does not restart them on boot. Errors and restoration failures are reported explicitly; they are not successful completions.
195
+
196
+ ### Circadian overrides
197
+
198
+ Manual lighting mutations create a **30-minute per-fixture override**, persisted across daemon restart. This also protects potentially partial/failed manual operations from being overwritten by the next automatic update. `auto-cct` uses the default BLE daemon action that checks overrides inside the same serialized queue as manual commands. It also skips sleeping fixtures, reported thermal protection, and reported stopped-cooling modes. Fan-only changes and read-only queries do not create lighting overrides.
199
+
200
+ ```sh
201
+ amaran-cli ble override status --targets all
202
+ amaran-cli ble override hold --targets desk,back --minutes 60
203
+ amaran-cli ble override resume --targets all
204
+ amaran-cli ble info all
205
+ amaran-cli ble health
206
+ ```
207
+
208
+ Override status reports remaining milliseconds per fixture. Resume clears the hold; the next circadian update takes over. Changes made outside this daemon are not automatically assigned a hold, so explicitly hold before using the desktop app or physical controls for comparisons.
209
+
210
+ `ble info` reads native product/firmware/protocol identifiers and reported feature bits. Version fields are raw SDK codes, not invented semantic version strings. Read-only dimming-curve queries were attempted on all three fixtures and produced no response; curve writes remain disabled. Provisioning/key rotation are not implemented, and OTA/firmware updates are intentionally excluded.
211
+ `ble health` prints daemon connectivity, protocol version, feature flags, configured fixture capabilities, and logical groups without changing hardware state.
212
+
213
+ ### Local web dashboard
214
+
215
+ ```sh
216
+ amaran-cli ble dashboard
217
+ amaran-cli ble dashboard --open
218
+ ```
219
+
220
+ The daemon serves a responsive dashboard at `http://127.0.0.1:2708/dashboard`. It covers the verified runtime
221
+ controls exposed by the CLI: individual/all/group power and color, effects and animation speed, fan modes, logical
222
+ and native groups, scenes/presets/quickshots with transitions, circadian holds, jobs, local audio/image paths,
223
+ browser camera/microphone sampling, Desktop import and mesh diagnostics. Daemon installation and initial private
224
+ mesh import remain CLI operations because the web interface does not receive mesh credentials.
225
+
226
+ The compact live-status cards provide direct per-fixture power, full-range brightness and model-correct Kelvin
227
+ sliders. Slider values update locally while dragging and send one verified BLE write when released, avoiding a flood
228
+ of intermediate mesh commands. In CCT mode each card also shows estimated lux: the interpolated full-output
229
+ calibration at that Kelvin multiplied by verified brightness. Sleeping fixtures show 0 lx; HSI/effect states show no
230
+ CCT-based estimate.
231
+
232
+ The dashboard uses the ordinary `maxLux` number/map as its fallback calibration. Different measured curves can be
233
+ assigned by model in `config.json` under `maxLuxByModel`, whose supported keys are `200x`, `200x-s`, and `150c`.
234
+ Each value uses the same Kelvin-to-lux map shape as `maxLux`. Use the locally measured curves for the actual setup; a
235
+ model-specific entry takes precedence over the shared fallback.
236
+
237
+ The dashboard is loopback-only and same-origin: it loads no CDN resources, rejects cross-origin browser requests,
238
+ and applies a restrictive Content Security Policy. Fixture registration still comes from private `mesh.json`.
239
+ UI title, refresh interval and fixture ordering/labels are stored in
240
+ `dashboard-settings.json`; the last successful readback is written with an `updatedAt` timestamp to
241
+ `dashboard-status.json`. Both live beside the BLE state in the platform config directory. The UI distinguishes the
242
+ cached timestamp from live connectivity and refreshes verified fixture/fan readback rather than assuming that a
243
+ request succeeded.
244
+
245
+ ## Setup and services
246
+
247
+ ```sh
248
+ npm ci
249
+ npm run build
250
+ node dist/cli.js ble import /path/to/private/lights.json
251
+ node dist/cli.js ble serve
252
+ ```
253
+
254
+ Import reads the previous project's private `lights.json`. Names must identify the supported model. The dedicated controller source defaults to `32766` (`0x7ffe`); use `--source` only when allocating a different unused unicast address. It must not overlap any fixture element or another controller. The desktop's address `1` is deliberately never reused for transmissions.
255
+
256
+ Quit Amaran Desktop before connecting. On the first background launch, macOS may separately request **Bluetooth permission for Node**, even if an interactive terminal already works. Approve it in System Settings > Privacy & Security > Bluetooth. The initialization timeout explicitly identifies this requirement; no elevated privileges or changes to the TCC database are needed.
257
+
258
+ ```sh
259
+ node dist/cli.js ble service install
260
+ node dist/cli.js ble service status
261
+ node dist/cli.js ble service stop
262
+ node dist/cli.js ble service start
263
+ ```
264
+
265
+ The LaunchAgent is `com.amaran-cli.ble`. It uses the current compiled CLI and Node executable, starts at user login, and restarts after crashes. Keep that CLI installation/build path available. Logs are in `~/Library/Logs/amaran-cli/`. `stop` unloads it; `start` loads it again. A Node upgrade that removes its recorded executable path requires updating the LaunchAgent.
266
+
267
+ The CLI defaults to direct BLE. Use `--backend desktop` to opt into Amaran Desktop; `--backend websocket` remains a compatibility alias. Desktop-only WebSocket discovery and vendor firmware updating are isolated under `desktop discover` and `desktop firmware update`; there is no synthetic firmware-current check. Explicit `--backend ble` is optional but may still be useful in automation:
268
+
269
+ ```sh
270
+ node dist/cli.js list --backend ble
271
+ node dist/cli.js status --backend ble
272
+ node dist/cli.js cct 4500 desk -i 5 --backend ble
273
+ node dist/cli.js hsi 120 100 5 back --backend ble
274
+ node dist/cli.js auto-cct --backend ble --service-mode
275
+ ```
276
+
277
+ Avoid running competing desktop or circadian commands during hardware comparisons. When moving existing automation to BLE, explicitly change its command/backend; importing mesh credentials does not rewrite other services.
278
+
279
+ ## Reliability and private state
280
+
281
+ `ble/mesh.json`, `ble/sequence.json` and `ble/library.json` live underneath the platform config directory (`AMARAN_CLI_CONFIG_DIR` overrides it). The first contains secrets: do not commit or share it. Back up mesh identity and sequence together. The library uses validated, atomic private JSON writes and also preserves pre-effect steady-state history.
282
+
283
+ Sequence blocks are written and fsynced before sending. Restarts skip unused reserved values instead of reusing them. Counters never wrap. Missing sequence state, exhausted counters, identity mismatches, or conflicting controller processes fail closed. A lock left by a dead process can be recovered; a live process retains its lock. Do not delete sequence state to troubleshoot Bluetooth.
284
+
285
+ The connection only selects advertisements matching the configured mesh network. Beacons, proxy filter acknowledgements, network packets and application packets are authenticated. Proxy fragmentation is handled, notifications are subscribed before initialization, and every operation has a deadline. Commands are serialized. State is read before mutation; matching readback is required afterward. Failed writes/mismatches have bounded retries and reconnection. Toggle retries resend the already-calculated power state, not another toggle.
286
+
287
+ Readback is fixture telemetry, not a photometer. A device can report a setting while a physical emitter is faulty; the webcam loop supplies a separate physical check. BLE link health is exposed as `connected`, not confused with configured device presence.
288
+
289
+ ## HTTP API
290
+
291
+ The daemon binds only to `127.0.0.1:2708`, does not enable CORS, and rejects browser-origin requests. Do not expose it through a public proxy. Local processes are trusted.
292
+
293
+ ```sh
294
+ curl http://127.0.0.1:2708/health
295
+ curl http://127.0.0.1:2708/lights/desk/state
296
+ curl -X POST http://127.0.0.1:2708/lights/desk/cct \
297
+ -H 'Content-Type: application/json' \
298
+ -d '{"kelvin":3200,"brightness":1}'
299
+ ```
300
+
301
+ Light actions are `state`, `on`, `off`, `toggle`, `brightness` (`value` in percent), `cct` (`kelvin`, optional `brightness`/`gm`), `gm` (`value`), `hsi` (`hue`, `saturation`, optional `brightness`), `color`, `increment-brightness`, `increment-cct`, `effect`, `effect-speed`, `effect-intensity`, `effect-stop` and `effect-trigger`. Fan GET/POST uses `/lights/KEY/fan` with optional `mode` and manual `rpm`; a group key returns keyed `states`. `GET /fans` reads all fans. `POST /fans` accepts `{targets:"all"|["desk","group:ID"], mode?, rpm?}`; omitting both mode and RPM is read-only. RPM requires manual mode, and manual mode requires RPM. Group expansion deduplicates members, and all fan bulk results have shape `{states:{fixtureKey:FanState}}`. Feature flags `fanTargets` and `fanManualRpm` identify endpoint/manual-setpoint support separately from each fixture's capability flags.
302
+
303
+ `POST /transition` takes `{targets, action:"cct"|"hsi"|"brightness", args, seconds}`; saved-state recall accepts optional `seconds` for a scene transition. `POST /overrides` takes `{targets, minutes?}`: omit minutes to inspect, use zero to resume. `POST /lights/KEY/auto-cct` returns either verified applied state or `{skipped:true, reason}` without claiming a hardware write. `GET /lights/KEY/info` reads native product information. Group renaming uses `POST /groups/ID/rename`.
304
+
305
+ `GET /dashboard/circadian` reports the installed service state, persistent settings, recent target, effective weather mode, and a 15-minute daily schedule used by the interactive dashboard graph. Schedule points distinguish natural intensity, capped service intensity, modeled sunlight lux, and the measured system lux capacity. `POST /dashboard/circadian/settings` validates and persists service enablement, interval, curve, live weather, location, Kelvin bounds, and intensity bounds; installed LaunchAgent arguments are reloaded when required. Weather status includes the current difference from clear-sky output. `POST /programs` starts a validated asynchronous job; `GET /programs[/ID]` reports status and `DELETE /programs/ID` stops it. Acceptance is not completion. Foreground capture uses source `{kind:"samples",media:"image"|"audio"}` and `POST /programs/ID/sample` with `{rgb:[r,g,b]}` or `{rms:0..1}`; this endpoint accepts numeric samples, not image/audio uploads. `POST /desktop/import` previews/applies library metadata. `POST /mesh/keys` imports matching private Device Keys; `GET /mesh/inspect` and `/mesh/discover` are read-only. `POST /groups/ID/native` accepts `enable`, `sync`, or `disable`; regular group membership/deletion routes maintain native subscriptions when enabled. No Device Keys appear in metadata responses.
306
+
307
+ `POST /batch` takes `{targets:"all"|["desk","front"], action, args, broadcast?:boolean}`. `POST /fade` takes `{targets, brightness, seconds}`. `GET /effects` lists the union of supported effect names; per-fixture capabilities remain authoritative. `/groups` provides local membership CRUD. `/library/scenes`, `/library/presets` and `/library/quickshots` provide list/save, ID-or-name update/delete and `/:id/recall`; preset recall may include a `target` fixture key.
308
+
309
+ Successful hardware commands return `{ "ok": true, "verified": true, "result": { ... } }`. Fixture states contain `sleep`, `intensity` in 0-1000 API units, `mode` (`cct`, `hsi` or `effect`), applicable parameters and `observedAt`; batch results include delivery type and keyed states. Library saves confirm persistence with `ok:true`, not a misleading hardware-verification flag. Invalid requests and unverified commands return `ok:false` with an error. Queue depth and request size are bounded, and disconnected clients cannot start queued mutations.
310
+
311
+ The legacy external daemon remains compatible for commands it supports, but it cannot gain verified delivery or live state just by using the new CLI.
312
+
313
+ ## Protocol findings
314
+
315
+ - Physical controls use access opcode `0x26` and a ten-byte checksummed payload. Standard Generic OnOff/Lightness state is not sufficient to verify the actual LEDs.
316
+ - The CCT field at bits 52-61 uses **10-K units** on these fixtures. Writing raw kelvin or the previous guessed flag produces incorrect/clamped colors.
317
+ - Actual fixture output quantizes CCT to **100-K steps**, despite that finer wire format. A 4540-K wire command read back as 4500 K on all three fixtures. Normalize requested settings to the supported resolution before checking them, and report the applied value.
318
+ - Intensity occupies bits 62-71, but these fixture models quantize to whole percent. They do not support the protocol's nominal tenth-percent precision.
319
+ - Legacy G/M in CCT bits 45-51 is an index: **0 means -100 magenta, 10 means neutral, 20 means +100 green**. The earlier all-zero tint field was wrong on the 150c even though its Kelvin/brightness readback matched.
320
+ - Effect command `0x87` uses native wire IDs, not the SDK's public effect enum numbers. Compact CCT and HSI effect variants have different bit layouts. Fan query/set commands are `0x09`/`0x89`.
321
+ - Menu requests use command byte `0x0e`. Replies are addressed to **the original provisioner `0x0001`**, not the querying controller. The proxy therefore whitelists both that address and the dedicated controller source. Receiving a reply to `1` does not require transmitting as `1`.
322
+ - Reply bit 8 is an awake flag: zero means sleeping. Commands and status must not invert this distinction.
323
+ - Proxy configuration uses an eight-byte network MIC and an authenticated Filter Status response; write completion alone is not initialization success.
324
+
325
+ These details were checked against the installed SDK's packet encoder/field accessors, actual decrypted fixture responses, desktop behavior and webcam captures. No SDK binary or private network key is distributed in this repository.
326
+
327
+ ## Hardware feedback loop
328
+
329
+ The following diagnostics require `ffmpeg` with AVFoundation and explicit camera permission:
330
+
331
+ ```sh
332
+ npx tsx scripts/webcam.ts baseline
333
+ # Stop the daemon before calibration: it owns the mesh connection/counters.
334
+ npx tsx scripts/calibrate-webcam.ts
335
+ # Optional desktop comparison, still with the BLE daemon stopped.
336
+ npx tsx scripts/desktop-reference.ts reference
337
+ # Quit Amaran Desktop, then resume the BLE daemon before API-based checks.
338
+ npx tsx scripts/ble-hardware-check.ts
339
+ npx tsx scripts/ble-live-extended-check.ts
340
+ npx tsx scripts/ble-effects-check.ts
341
+ npx tsx scripts/ble-fan-check.ts --restart-service
342
+ npx tsx scripts/ble-followup-check.ts
343
+ ```
344
+
345
+ Captures and JSON reports stay in the project's gitignored `artifacts/` directory. No images are uploaded. Pixel format `nv12` avoids the corrupted packed-pixel frames observed with the default camera format.
346
+
347
+ Calibration isolates each fixture at 1%, locates its emitting face against a dark reference, and saves fixture-keyed regions to `artifacts/webcam/regions.json`. Recalibrate after moving either camera or lights; captures without that file are explicitly marked uncalibrated. Inspect isolated frames to distinguish emitters from glare/reflections. `--reuse` recomputes regions from the most recent saved isolation captures without operating lights.
348
+
349
+ The core check isolates each light, compares warm/cool output, exercises 0/1/2% brightness, and verifies the 150c's red/green/blue channel dominance; it finishes by putting lights to sleep. Color assertions use the emitter's surrounding halo, excluding the clipped white center, rather than mistaking overexposure for incorrect color. The extended check covers tint, allowed cooling profiles, batch/broadcast brightness, fades, saved-state recall and representative slow native effects; it restores initial CCT/power/brightness/tint and fan modes and records restoration failures. A sampled camera sequence measures rising and falling pulsing output after initial exposure settles.
350
+
351
+ The dedicated effects check verifies every advertised effect, frequency changes and both 150c HSI variants with brightness held at **zero**, then restores initial settings. This exercises real packet/readback behavior without emitting rapid flashes. It is not an optical certification of every flashing pattern.
352
+
353
+ `npx tsx scripts/ble-cli-check.ts prepare` exercises the real CLI and creates temporary library records, then deliberately leaves a prepared lighting state. Restart the daemon and run `npx tsx scripts/ble-cli-check.ts verify <printed-manifest-path>` to verify persistence, restore initial lighting and remove those temporary records. Keep competing automation paused across both phases.
354
+
355
+ The fan check requires brightness at 5% or less and never changes LED settings. It verifies individual and grouped Smart/Medium selection, zero-RPM reporting, unsupported-mode rejection, and restoration of the original fan profiles. Its optional `--restart-service` flag checks that profiles survive a restart of the installed BLE LaunchAgent. Keep automation paused during that restart. All eight mode codes and manual setpoint encoding/readback are covered by SDK-derived vectors and simulated advertised capabilities. Thermal trips, missing telemetry and recovery failures are simulated, never induced by overheating real fixtures. Diagnostics refuse an initial Manual profile because its original setpoint cannot be inferred safely from current RPM.
356
+
357
+ The follow-up check covers product reads, CCT/HSI transitions with camera color feedback, automatic-update suppression, group rename/effect stop, zero-output trigger requests, saved-state updates and scene/fan restoration. It preserves prior override expiry times on success; on failure, protective holds remain in place for investigation. Older lighting diagnostics also generate manual holds; explicitly resume selected fixtures when their checks/restoration are complete.
358
+
359
+ Automatic exposure, white balance and clipped highlights mean camera values are **not calibrated lux or CCT measurements**. Physical checks establish on/off, color and representative effect response; authenticated telemetry verifies exact applied digital settings. These are low-output checks, not high-output thermal certification or long-duration endurance testing.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "amaran-light-cli",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "type": "module",
5
- "description": "Command line tool for controlling Aputure Amaran lights via WebSocket to a local Amaran desktop app.",
5
+ "description": "CLI and local web dashboard for verified direct-Bluetooth or Desktop control of Aputure Amaran lights.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "exports": {
@@ -56,7 +56,8 @@
56
56
  "scripts",
57
57
  "README.md",
58
58
  "LICENSE",
59
- "docs/CHANGELOG.md"
59
+ "docs/CHANGELOG.md",
60
+ "docs/DIRECT_BLE.md"
60
61
  ],
61
62
  "repository": {
62
63
  "type": "git",
@@ -86,11 +87,14 @@
86
87
  "chart.js": "^4.5.1",
87
88
  "chartjs-node-canvas": "^5.0.0",
88
89
  "commander": "^14.0.0",
89
- "geoip-lite": "^1.4.10",
90
+ "geoip-lite": "^2.0.2",
90
91
  "suncalc": "^1.9.0",
91
92
  "ws": "^8.14.2",
92
93
  "zod": "^3.23.0"
93
94
  },
95
+ "optionalDependencies": {
96
+ "@abandonware/noble": "^1.9.2-26"
97
+ },
94
98
  "devDependencies": {
95
99
  "@biomejs/biome": "^2.3.8",
96
100
  "@types/geoip-lite": "^1.4.4",
@@ -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>');