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.
- package/README.md +154 -17
- package/dist/__tests__/autoCct.test.js +35 -4
- package/dist/__tests__/autoCct.test.js.map +1 -1
- package/dist/__tests__/autoCctMaxLux.test.js +11 -9
- package/dist/__tests__/autoCctMaxLux.test.js.map +1 -1
- package/dist/__tests__/autostart.test.js +18 -0
- package/dist/__tests__/autostart.test.js.map +1 -1
- package/dist/__tests__/bleHttpControl.test.d.ts +2 -0
- package/dist/__tests__/bleHttpControl.test.d.ts.map +1 -0
- package/dist/__tests__/bleHttpControl.test.js +230 -0
- package/dist/__tests__/bleHttpControl.test.js.map +1 -0
- package/dist/__tests__/bleStack.test.d.ts +2 -0
- package/dist/__tests__/bleStack.test.d.ts.map +1 -0
- package/dist/__tests__/bleStack.test.js +1239 -0
- package/dist/__tests__/bleStack.test.js.map +1 -0
- package/dist/__tests__/cctUtil.test.js +11 -5
- package/dist/__tests__/cctUtil.test.js.map +1 -1
- package/dist/__tests__/circadianDashboardSettings.test.d.ts +2 -0
- package/dist/__tests__/circadianDashboardSettings.test.d.ts.map +1 -0
- package/dist/__tests__/circadianDashboardSettings.test.js +82 -0
- package/dist/__tests__/circadianDashboardSettings.test.js.map +1 -0
- package/dist/__tests__/circadianIsolation.test.d.ts +2 -0
- package/dist/__tests__/circadianIsolation.test.d.ts.map +1 -0
- package/dist/__tests__/circadianIsolation.test.js +30 -0
- package/dist/__tests__/circadianIsolation.test.js.map +1 -0
- package/dist/__tests__/cli.config.test.d.ts +2 -0
- package/dist/__tests__/cli.config.test.d.ts.map +1 -0
- package/dist/__tests__/cli.config.test.js +114 -0
- package/dist/__tests__/cli.config.test.js.map +1 -0
- package/dist/__tests__/cli.smoke.test.js +70 -17
- package/dist/__tests__/cli.smoke.test.js.map +1 -1
- package/dist/__tests__/currentCct.test.d.ts +2 -0
- package/dist/__tests__/currentCct.test.d.ts.map +1 -0
- package/dist/__tests__/currentCct.test.js +29 -0
- package/dist/__tests__/currentCct.test.js.map +1 -0
- package/dist/__tests__/dashboard.test.d.ts +2 -0
- package/dist/__tests__/dashboard.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard.test.js +126 -0
- package/dist/__tests__/dashboard.test.js.map +1 -0
- package/dist/__tests__/daylightSimulationParseUtils.test.d.ts +2 -0
- package/dist/__tests__/daylightSimulationParseUtils.test.d.ts.map +1 -0
- package/dist/__tests__/daylightSimulationParseUtils.test.js +16 -0
- package/dist/__tests__/daylightSimulationParseUtils.test.js.map +1 -0
- package/dist/__tests__/desktopImport.test.d.ts +2 -0
- package/dist/__tests__/desktopImport.test.d.ts.map +1 -0
- package/dist/__tests__/desktopImport.test.js +234 -0
- package/dist/__tests__/desktopImport.test.js.map +1 -0
- package/dist/__tests__/lightControl.basic.test.d.ts +2 -0
- package/dist/__tests__/lightControl.basic.test.d.ts.map +1 -0
- package/dist/__tests__/lightControl.basic.test.js +119 -0
- package/dist/__tests__/lightControl.basic.test.js.map +1 -0
- package/dist/__tests__/lightControl.colorEffects.test.d.ts +2 -0
- package/dist/__tests__/lightControl.colorEffects.test.d.ts.map +1 -0
- package/dist/__tests__/lightControl.colorEffects.test.js +170 -0
- package/dist/__tests__/lightControl.colorEffects.test.js.map +1 -0
- package/dist/__tests__/lightControl.management.test.d.ts +2 -0
- package/dist/__tests__/lightControl.management.test.d.ts.map +1 -0
- package/dist/__tests__/lightControl.management.test.js +90 -0
- package/dist/__tests__/lightControl.management.test.js.map +1 -0
- package/dist/__tests__/lightControlTestUtils.d.ts +10 -0
- package/dist/__tests__/lightControlTestUtils.d.ts.map +1 -0
- package/dist/__tests__/lightControlTestUtils.js +57 -0
- package/dist/__tests__/lightControlTestUtils.js.map +1 -0
- package/dist/__tests__/maxLuxInterpolation.test.js +4 -2
- package/dist/__tests__/maxLuxInterpolation.test.js.map +1 -1
- package/dist/__tests__/meshConfiguration.test.d.ts +2 -0
- package/dist/__tests__/meshConfiguration.test.d.ts.map +1 -0
- package/dist/__tests__/meshConfiguration.test.js +78 -0
- package/dist/__tests__/meshConfiguration.test.js.map +1 -0
- package/dist/__tests__/nativeGroups.test.d.ts +2 -0
- package/dist/__tests__/nativeGroups.test.d.ts.map +1 -0
- package/dist/__tests__/nativeGroups.test.js +104 -0
- package/dist/__tests__/nativeGroups.test.js.map +1 -0
- package/dist/__tests__/packageExports.test.d.ts +2 -0
- package/dist/__tests__/packageExports.test.d.ts.map +1 -0
- package/dist/__tests__/packageExports.test.js +33 -0
- package/dist/__tests__/packageExports.test.js.map +1 -0
- package/dist/__tests__/programApi.test.d.ts +2 -0
- package/dist/__tests__/programApi.test.d.ts.map +1 -0
- package/dist/__tests__/programApi.test.js +73 -0
- package/dist/__tests__/programApi.test.js.map +1 -0
- package/dist/__tests__/programs.test.d.ts +2 -0
- package/dist/__tests__/programs.test.d.ts.map +1 -0
- package/dist/__tests__/programs.test.js +176 -0
- package/dist/__tests__/programs.test.js.map +1 -0
- package/dist/__tests__/scheduleMaker.test.d.ts +2 -0
- package/dist/__tests__/scheduleMaker.test.d.ts.map +1 -0
- package/dist/__tests__/scheduleMaker.test.js +25 -0
- package/dist/__tests__/scheduleMaker.test.js.map +1 -0
- package/dist/__tests__/simulateSchedule.test.js +7 -3
- package/dist/__tests__/simulateSchedule.test.js.map +1 -1
- package/dist/ble/async.d.ts +2 -0
- package/dist/ble/async.d.ts.map +1 -0
- package/dist/ble/async.js +15 -0
- package/dist/ble/async.js.map +1 -0
- package/dist/ble/colors.d.ts +5 -0
- package/dist/ble/colors.d.ts.map +1 -0
- package/dist/ble/colors.js +44 -0
- package/dist/ble/colors.js.map +1 -0
- package/dist/ble/configuration.d.ts +22 -0
- package/dist/ble/configuration.d.ts.map +1 -0
- package/dist/ble/configuration.js +122 -0
- package/dist/ble/configuration.js.map +1 -0
- package/dist/ble/controller.d.ts +118 -0
- package/dist/ble/controller.d.ts.map +1 -0
- package/dist/ble/controller.js +931 -0
- package/dist/ble/controller.js.map +1 -0
- package/dist/ble/crypto.d.ts +34 -0
- package/dist/ble/crypto.d.ts.map +1 -0
- package/dist/ble/crypto.js +178 -0
- package/dist/ble/crypto.js.map +1 -0
- package/dist/ble/daemon.d.ts +2 -0
- package/dist/ble/daemon.d.ts.map +1 -0
- package/dist/ble/daemon.js +76 -0
- package/dist/ble/daemon.js.map +1 -0
- package/dist/ble/dashboard.d.ts +331 -0
- package/dist/ble/dashboard.d.ts.map +1 -0
- package/dist/ble/dashboard.js +280 -0
- package/dist/ble/dashboard.js.map +1 -0
- package/dist/ble/desktop.d.ts +4 -0
- package/dist/ble/desktop.d.ts.map +1 -0
- package/dist/ble/desktop.js +33 -0
- package/dist/ble/desktop.js.map +1 -0
- package/dist/ble/desktopLibrary.d.ts +12 -0
- package/dist/ble/desktopLibrary.d.ts.map +1 -0
- package/dist/ble/desktopLibrary.js +346 -0
- package/dist/ble/desktopLibrary.js.map +1 -0
- package/dist/ble/effects.d.ts +34 -0
- package/dist/ble/effects.d.ts.map +1 -0
- package/dist/ble/effects.js +138 -0
- package/dist/ble/effects.js.map +1 -0
- package/dist/ble/fan.d.ts +244 -0
- package/dist/ble/fan.d.ts.map +1 -0
- package/dist/ble/fan.js +46 -0
- package/dist/ble/fan.js.map +1 -0
- package/dist/ble/library.d.ts +341 -0
- package/dist/ble/library.d.ts.map +1 -0
- package/dist/ble/library.js +201 -0
- package/dist/ble/library.js.map +1 -0
- package/dist/ble/media.d.ts +133 -0
- package/dist/ble/media.d.ts.map +1 -0
- package/dist/ble/media.js +186 -0
- package/dist/ble/media.js.map +1 -0
- package/dist/ble/nativeGroups.d.ts +22 -0
- package/dist/ble/nativeGroups.d.ts.map +1 -0
- package/dist/ble/nativeGroups.js +114 -0
- package/dist/ble/nativeGroups.js.map +1 -0
- package/dist/ble/packets.d.ts +3 -0
- package/dist/ble/packets.d.ts.map +1 -0
- package/dist/ble/packets.js +15 -0
- package/dist/ble/packets.js.map +1 -0
- package/dist/ble/programs.d.ts +318 -0
- package/dist/ble/programs.d.ts.map +1 -0
- package/dist/ble/programs.js +329 -0
- package/dist/ble/programs.js.map +1 -0
- package/dist/ble/provisioning.d.ts +13 -0
- package/dist/ble/provisioning.d.ts.map +1 -0
- package/dist/ble/provisioning.js +39 -0
- package/dist/ble/provisioning.js.map +1 -0
- package/dist/ble/server.d.ts +18 -0
- package/dist/ble/server.d.ts.map +1 -0
- package/dist/ble/server.js +548 -0
- package/dist/ble/server.js.map +1 -0
- package/dist/ble/service.d.ts +2 -0
- package/dist/ble/service.d.ts.map +1 -0
- package/dist/ble/service.js +77 -0
- package/dist/ble/service.js.map +1 -0
- package/dist/ble/settings.d.ts +18 -0
- package/dist/ble/settings.d.ts.map +1 -0
- package/dist/ble/settings.js +25 -0
- package/dist/ble/settings.js.map +1 -0
- package/dist/ble/setup.d.ts +2 -0
- package/dist/ble/setup.d.ts.map +1 -0
- package/dist/ble/setup.js +31 -0
- package/dist/ble/setup.js.map +1 -0
- package/dist/ble/storage.d.ts +94 -0
- package/dist/ble/storage.d.ts.map +1 -0
- package/dist/ble/storage.js +132 -0
- package/dist/ble/storage.js.map +1 -0
- package/dist/ble/telink.d.ts +63 -0
- package/dist/ble/telink.d.ts.map +1 -0
- package/dist/ble/telink.js +108 -0
- package/dist/ble/telink.js.map +1 -0
- package/dist/ble/transitions.d.ts +3 -0
- package/dist/ble/transitions.d.ts.map +1 -0
- package/dist/ble/transitions.js +28 -0
- package/dist/ble/transitions.js.map +1 -0
- package/dist/ble/transport.d.ts +48 -0
- package/dist/ble/transport.d.ts.map +1 -0
- package/dist/ble/transport.js +370 -0
- package/dist/ble/transport.js.map +1 -0
- package/dist/circadian.d.ts +19 -0
- package/dist/circadian.d.ts.map +1 -0
- package/dist/circadian.js +13 -0
- package/dist/circadian.js.map +1 -0
- package/dist/cli.d.ts +5 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +159 -33
- package/dist/cli.js.map +1 -1
- package/dist/commands/__tests__/integration.test.js +28 -0
- package/dist/commands/__tests__/integration.test.js.map +1 -1
- package/dist/commands/__tests__/newCommands.test.js +3 -4
- package/dist/commands/__tests__/newCommands.test.js.map +1 -1
- package/dist/commands/cmdUtils.d.ts +8 -2
- package/dist/commands/cmdUtils.d.ts.map +1 -1
- package/dist/commands/cmdUtils.js +40 -2
- package/dist/commands/cmdUtils.js.map +1 -1
- package/dist/commands/daylightSimulation/autoCct.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/autoCct.js +74 -172
- package/dist/commands/daylightSimulation/autoCct.js.map +1 -1
- package/dist/commands/daylightSimulation/graphSchedule.d.ts +1 -1
- package/dist/commands/daylightSimulation/graphSchedule.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/graphSchedule.js +2 -0
- package/dist/commands/daylightSimulation/graphSchedule.js.map +1 -1
- package/dist/commands/daylightSimulation/printSchedule.d.ts +1 -1
- package/dist/commands/daylightSimulation/printSchedule.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/printSchedule.js +4 -2
- package/dist/commands/daylightSimulation/printSchedule.js.map +1 -1
- package/dist/commands/daylightSimulation/service.d.ts +1 -1
- package/dist/commands/daylightSimulation/service.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/service.js +20 -10
- package/dist/commands/daylightSimulation/service.js.map +1 -1
- package/dist/commands/daylightSimulation/simulateSchedule.d.ts +1 -1
- package/dist/commands/daylightSimulation/simulateSchedule.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/simulateSchedule.js +31 -13
- package/dist/commands/daylightSimulation/simulateSchedule.js.map +1 -1
- package/dist/commands/daylightSimulation/weather.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/weather.js +19 -4
- package/dist/commands/daylightSimulation/weather.js.map +1 -1
- package/dist/commands/deviceControl/ble.d.ts +4 -0
- package/dist/commands/deviceControl/ble.d.ts.map +1 -0
- package/dist/commands/deviceControl/ble.js +214 -0
- package/dist/commands/deviceControl/ble.js.map +1 -0
- package/dist/commands/deviceControl/cct.d.ts.map +1 -1
- package/dist/commands/deviceControl/cct.js +47 -19
- package/dist/commands/deviceControl/cct.js.map +1 -1
- package/dist/commands/deviceControl/color.d.ts.map +1 -1
- package/dist/commands/deviceControl/color.js +16 -12
- package/dist/commands/deviceControl/color.js.map +1 -1
- package/dist/commands/deviceControl/config.d.ts.map +1 -1
- package/dist/commands/deviceControl/config.js +94 -58
- package/dist/commands/deviceControl/config.js.map +1 -1
- package/dist/commands/deviceControl/{discover.d.ts → desktop.d.ts} +3 -2
- package/dist/commands/deviceControl/desktop.d.ts.map +1 -0
- package/dist/commands/deviceControl/desktop.js +66 -0
- package/dist/commands/deviceControl/desktop.js.map +1 -0
- package/dist/commands/deviceControl/effect.d.ts.map +1 -1
- package/dist/commands/deviceControl/effect.js +90 -159
- package/dist/commands/deviceControl/effect.js.map +1 -1
- package/dist/commands/deviceControl/fan.d.ts.map +1 -1
- package/dist/commands/deviceControl/fan.js +77 -95
- package/dist/commands/deviceControl/fan.js.map +1 -1
- package/dist/commands/deviceControl/group.d.ts.map +1 -1
- package/dist/commands/deviceControl/group.js +43 -6
- package/dist/commands/deviceControl/group.js.map +1 -1
- package/dist/commands/deviceControl/hsi.d.ts.map +1 -1
- package/dist/commands/deviceControl/hsi.js +6 -10
- package/dist/commands/deviceControl/hsi.js.map +1 -1
- package/dist/commands/deviceControl/info.d.ts.map +1 -1
- package/dist/commands/deviceControl/info.js +0 -50
- package/dist/commands/deviceControl/info.js.map +1 -1
- package/dist/commands/deviceControl/intensity.d.ts.map +1 -1
- package/dist/commands/deviceControl/intensity.js +38 -16
- package/dist/commands/deviceControl/intensity.js.map +1 -1
- package/dist/commands/deviceControl/list.d.ts.map +1 -1
- package/dist/commands/deviceControl/list.js +21 -15
- package/dist/commands/deviceControl/list.js.map +1 -1
- package/dist/commands/deviceControl/power.d.ts.map +1 -1
- package/dist/commands/deviceControl/power.js +19 -28
- package/dist/commands/deviceControl/power.js.map +1 -1
- package/dist/commands/deviceControl/preset.d.ts.map +1 -1
- package/dist/commands/deviceControl/preset.js +56 -19
- package/dist/commands/deviceControl/preset.js.map +1 -1
- package/dist/commands/deviceControl/program.d.ts +4 -0
- package/dist/commands/deviceControl/program.d.ts.map +1 -0
- package/dist/commands/deviceControl/program.js +199 -0
- package/dist/commands/deviceControl/program.js.map +1 -0
- package/dist/commands/deviceControl/quickshot.d.ts.map +1 -1
- package/dist/commands/deviceControl/quickshot.js +75 -13
- package/dist/commands/deviceControl/quickshot.js.map +1 -1
- package/dist/commands/deviceControl/scene.d.ts.map +1 -1
- package/dist/commands/deviceControl/scene.js +72 -9
- package/dist/commands/deviceControl/scene.js.map +1 -1
- package/dist/commands/deviceControl/status.d.ts.map +1 -1
- package/dist/commands/deviceControl/status.js +29 -6
- package/dist/commands/deviceControl/status.js.map +1 -1
- package/dist/commands/parseUtils.d.ts +6 -0
- package/dist/commands/parseUtils.d.ts.map +1 -0
- package/dist/commands/parseUtils.js +32 -0
- package/dist/commands/parseUtils.js.map +1 -0
- package/dist/commands-api.d.ts +28 -0
- package/dist/commands-api.d.ts.map +1 -0
- package/dist/commands-api.js +27 -0
- package/dist/commands-api.js.map +1 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +4 -2
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts +165 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +125 -0
- package/dist/config.js.map +1 -0
- package/dist/daylightSimulation/cctUtil.d.ts.map +1 -1
- package/dist/daylightSimulation/cctUtil.js +23 -22
- package/dist/daylightSimulation/cctUtil.js.map +1 -1
- package/dist/daylightSimulation/constants.d.ts +10 -0
- package/dist/daylightSimulation/constants.d.ts.map +1 -1
- package/dist/daylightSimulation/constants.js +4 -0
- package/dist/daylightSimulation/constants.js.map +1 -1
- package/dist/daylightSimulation/currentCct.d.ts +42 -0
- package/dist/daylightSimulation/currentCct.d.ts.map +1 -0
- package/dist/daylightSimulation/currentCct.js +217 -0
- package/dist/daylightSimulation/currentCct.js.map +1 -0
- package/dist/daylightSimulation/curves/realistic.d.ts +6 -6
- package/dist/daylightSimulation/curves/realistic.d.ts.map +1 -1
- package/dist/daylightSimulation/curves/realistic.js +36 -6
- package/dist/daylightSimulation/curves/realistic.js.map +1 -1
- package/dist/daylightSimulation/dashboardSettings.d.ts +47 -0
- package/dist/daylightSimulation/dashboardSettings.d.ts.map +1 -0
- package/dist/daylightSimulation/dashboardSettings.js +95 -0
- package/dist/daylightSimulation/dashboardSettings.js.map +1 -0
- package/dist/daylightSimulation/dashboardStatus.d.ts +72 -0
- package/dist/daylightSimulation/dashboardStatus.d.ts.map +1 -0
- package/dist/daylightSimulation/dashboardStatus.js +191 -0
- package/dist/daylightSimulation/dashboardStatus.js.map +1 -0
- package/dist/daylightSimulation/mathUtil.d.ts.map +1 -1
- package/dist/daylightSimulation/mathUtil.js +6 -2
- package/dist/daylightSimulation/mathUtil.js.map +1 -1
- package/dist/daylightSimulation/parseUtils.d.ts +3 -0
- package/dist/daylightSimulation/parseUtils.d.ts.map +1 -0
- package/dist/daylightSimulation/parseUtils.js +25 -0
- package/dist/daylightSimulation/parseUtils.js.map +1 -0
- package/dist/daylightSimulation/scheduleMaker.d.ts +6 -2
- package/dist/daylightSimulation/scheduleMaker.d.ts.map +1 -1
- package/dist/daylightSimulation/scheduleMaker.js +9 -11
- package/dist/daylightSimulation/scheduleMaker.js.map +1 -1
- package/dist/daylightSimulation/types.d.ts +15 -2
- package/dist/daylightSimulation/types.d.ts.map +1 -1
- package/dist/daylightSimulation/types.js +0 -2
- package/dist/daylightSimulation/types.js.map +1 -1
- package/dist/daylightSimulation/weatherUtil.d.ts.map +1 -1
- package/dist/daylightSimulation/weatherUtil.js +31 -0
- package/dist/daylightSimulation/weatherUtil.js.map +1 -1
- package/dist/device-control.d.ts +8 -0
- package/dist/device-control.d.ts.map +1 -0
- package/dist/device-control.js +8 -0
- package/dist/device-control.js.map +1 -0
- package/dist/deviceControl/autostart.d.ts.map +1 -1
- package/dist/deviceControl/autostart.js +15 -16
- package/dist/deviceControl/autostart.js.map +1 -1
- package/dist/deviceControl/bleHttpControl.d.ts +129 -0
- package/dist/deviceControl/bleHttpControl.d.ts.map +1 -0
- package/dist/deviceControl/bleHttpControl.js +753 -0
- package/dist/deviceControl/bleHttpControl.js.map +1 -0
- package/dist/deviceControl/lightControl.d.ts +9 -68
- package/dist/deviceControl/lightControl.d.ts.map +1 -1
- package/dist/deviceControl/lightControl.js +25 -255
- package/dist/deviceControl/lightControl.js.map +1 -1
- package/dist/deviceControl/lightControlCommands.d.ts +68 -0
- package/dist/deviceControl/lightControlCommands.d.ts.map +1 -0
- package/dist/deviceControl/lightControlCommands.js +244 -0
- package/dist/deviceControl/lightControlCommands.js.map +1 -0
- package/dist/deviceControl/types.d.ts +83 -4
- package/dist/deviceControl/types.d.ts.map +1 -1
- package/dist/deviceControl/types.js +1 -0
- package/dist/deviceControl/types.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/test/MockLightServer.d.ts +1 -0
- package/dist/test/MockLightServer.d.ts.map +1 -1
- package/dist/test/MockLightServer.js +5 -0
- package/dist/test/MockLightServer.js.map +1 -1
- package/docs/CHANGELOG.md +171 -0
- package/docs/DIRECT_BLE.md +359 -0
- package/package.json +39 -8
- package/scripts/ble-cli-check.ts +129 -0
- package/scripts/ble-config-probe.ts +109 -0
- package/scripts/ble-effects-check.ts +82 -0
- package/scripts/ble-extended-probe.ts +44 -0
- package/scripts/ble-fan-check.ts +129 -0
- package/scripts/ble-feature-probe.ts +37 -0
- package/scripts/ble-followup-check.ts +165 -0
- package/scripts/ble-hardware-check.ts +99 -0
- package/scripts/ble-live-extended-check.ts +205 -0
- package/scripts/ble-non-ota-check.ts +193 -0
- package/scripts/ble-probe.ts +45 -0
- package/scripts/calibrate-webcam.ts +152 -0
- package/scripts/desktop-reference.ts +85 -0
- package/scripts/webcam.ts +129 -0
- package/dist/__tests__/lightControl.test.d.ts +0 -2
- package/dist/__tests__/lightControl.test.d.ts.map +0 -1
- package/dist/__tests__/lightControl.test.js +0 -424
- package/dist/__tests__/lightControl.test.js.map +0 -1
- package/dist/commands/deviceControl/discover.d.ts.map +0 -1
- package/dist/commands/deviceControl/discover.js +0 -29
- package/dist/commands/deviceControl/discover.js.map +0 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { interpolateMaxLux } from '../daylightSimulation/mathUtil.js';
|
|
5
|
+
import { FanStateSchema } from './fan.js';
|
|
6
|
+
import { SavedStateSchema } from './library.js';
|
|
7
|
+
import { atomicJson } from './storage.js';
|
|
8
|
+
export const DashboardSettingsSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
version: z.literal(1),
|
|
11
|
+
title: z.string().trim().min(1).max(80),
|
|
12
|
+
refreshSeconds: z.number().int().min(5).max(300),
|
|
13
|
+
defaultTransitionSeconds: z.number().min(0.5).max(20),
|
|
14
|
+
fixtureOrder: z.array(z.string()).max(128),
|
|
15
|
+
fixtureLabels: z.record(z.string().trim().min(1).max(80)),
|
|
16
|
+
maxBrightness: z.number().min(0).max(100).optional(),
|
|
17
|
+
})
|
|
18
|
+
.strict()
|
|
19
|
+
.transform(({ maxBrightness: _legacyMaxBrightness, ...settings }) => settings);
|
|
20
|
+
export const DashboardStatusSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
version: z.literal(1),
|
|
23
|
+
updatedAt: z.string().datetime(),
|
|
24
|
+
connected: z.boolean(),
|
|
25
|
+
lighting: z.record(SavedStateSchema),
|
|
26
|
+
fans: z.record(FanStateSchema),
|
|
27
|
+
estimatedLux: z.record(z.number().nonnegative().nullable()).default({}),
|
|
28
|
+
})
|
|
29
|
+
.strict();
|
|
30
|
+
export function estimateLux(calibration, state) {
|
|
31
|
+
if (state.sleep)
|
|
32
|
+
return 0;
|
|
33
|
+
if (state.mode !== 'cct' || state.cct === undefined || calibration === undefined)
|
|
34
|
+
return null;
|
|
35
|
+
const fullOutput = typeof calibration === 'number' ? calibration : interpolateMaxLux(state.cct, calibration);
|
|
36
|
+
return Math.round((fullOutput * state.intensity) / 1000);
|
|
37
|
+
}
|
|
38
|
+
const defaults = (keys) => ({
|
|
39
|
+
version: 1,
|
|
40
|
+
title: 'Amaran Control',
|
|
41
|
+
refreshSeconds: 10,
|
|
42
|
+
defaultTransitionSeconds: 2,
|
|
43
|
+
fixtureOrder: keys,
|
|
44
|
+
fixtureLabels: {},
|
|
45
|
+
});
|
|
46
|
+
export class DashboardStore {
|
|
47
|
+
settings;
|
|
48
|
+
status;
|
|
49
|
+
settingsFile;
|
|
50
|
+
statusFile;
|
|
51
|
+
constructor(directory, fixtureKeys = []) {
|
|
52
|
+
this.settingsFile = directory ? path.join(directory, 'dashboard-settings.json') : undefined;
|
|
53
|
+
this.statusFile = directory ? path.join(directory, 'dashboard-status.json') : undefined;
|
|
54
|
+
const settingsExisted = !!this.settingsFile && existsSync(this.settingsFile);
|
|
55
|
+
const persistedSettings = settingsExisted && this.settingsFile
|
|
56
|
+
? JSON.parse(readFileSync(this.settingsFile, 'utf8'))
|
|
57
|
+
: undefined;
|
|
58
|
+
this.settings =
|
|
59
|
+
persistedSettings === undefined ? defaults(fixtureKeys) : DashboardSettingsSchema.parse(persistedSettings);
|
|
60
|
+
if (this.statusFile && existsSync(this.statusFile))
|
|
61
|
+
this.status = DashboardStatusSchema.parse(JSON.parse(readFileSync(this.statusFile, 'utf8')));
|
|
62
|
+
this.settings = this.normalizeOrder(this.settings, fixtureKeys);
|
|
63
|
+
if (this.settingsFile && (!settingsExisted || JSON.stringify(persistedSettings) !== JSON.stringify(this.settings)))
|
|
64
|
+
atomicJson(this.settingsFile, this.settings);
|
|
65
|
+
}
|
|
66
|
+
getSettings() {
|
|
67
|
+
return structuredClone(this.settings);
|
|
68
|
+
}
|
|
69
|
+
updateSettings(value, fixtureKeys) {
|
|
70
|
+
const next = DashboardSettingsSchema.parse({ ...this.settings, ...value, version: 1 });
|
|
71
|
+
const allowed = new Set(fixtureKeys);
|
|
72
|
+
if (next.fixtureOrder.some((key) => !allowed.has(key)))
|
|
73
|
+
throw new Error('Fixture order contains an unknown key');
|
|
74
|
+
if (new Set(next.fixtureOrder).size !== next.fixtureOrder.length)
|
|
75
|
+
throw new Error('Fixture order contains duplicate keys');
|
|
76
|
+
for (const key of Object.keys(next.fixtureLabels))
|
|
77
|
+
if (!allowed.has(key))
|
|
78
|
+
throw new Error(`Fixture label contains unknown key: ${key}`);
|
|
79
|
+
this.settings = this.normalizeOrder(next, fixtureKeys);
|
|
80
|
+
if (this.settingsFile)
|
|
81
|
+
atomicJson(this.settingsFile, this.settings);
|
|
82
|
+
return this.getSettings();
|
|
83
|
+
}
|
|
84
|
+
getStatus() {
|
|
85
|
+
return this.status ? structuredClone(this.status) : undefined;
|
|
86
|
+
}
|
|
87
|
+
saveStatus(value) {
|
|
88
|
+
this.status = DashboardStatusSchema.parse(value);
|
|
89
|
+
if (this.statusFile)
|
|
90
|
+
atomicJson(this.statusFile, this.status);
|
|
91
|
+
return structuredClone(this.status);
|
|
92
|
+
}
|
|
93
|
+
normalizeOrder(settings, fixtureKeys) {
|
|
94
|
+
const allowed = new Set(fixtureKeys);
|
|
95
|
+
return {
|
|
96
|
+
...settings,
|
|
97
|
+
fixtureOrder: [
|
|
98
|
+
...settings.fixtureOrder.filter((key) => allowed.has(key)),
|
|
99
|
+
...fixtureKeys.filter((key) => !settings.fixtureOrder.includes(key)),
|
|
100
|
+
],
|
|
101
|
+
fixtureLabels: Object.fromEntries(Object.entries(settings.fixtureLabels).filter(([key]) => allowed.has(key))),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export const dashboardHtml = `<!doctype html>
|
|
106
|
+
<html lang="en">
|
|
107
|
+
<head>
|
|
108
|
+
<meta charset="utf-8">
|
|
109
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
110
|
+
<title>Amaran Control</title>
|
|
111
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
112
|
+
<link rel="stylesheet" href="/dashboard.css">
|
|
113
|
+
</head>
|
|
114
|
+
<body>
|
|
115
|
+
<header>
|
|
116
|
+
<div><p class="eyebrow">DIRECT BLUETOOTH MESH</p><h1 id="title">Amaran Control</h1></div>
|
|
117
|
+
<div class="connection"><span id="connection-dot"></span><strong id="connection">Connecting</strong><small id="updated"></small></div>
|
|
118
|
+
</header>
|
|
119
|
+
<nav>
|
|
120
|
+
<label>Target <select id="target"></select></label>
|
|
121
|
+
<button id="refresh">Refresh state</button>
|
|
122
|
+
<a href="/health" target="_blank" rel="noreferrer">Raw API</a>
|
|
123
|
+
</nav>
|
|
124
|
+
<main>
|
|
125
|
+
<section class="wide"><div class="section-head"><h2>Live status</h2><span>Readback, not assumed state</span></div><div id="fixture-cards" class="cards"></div></section>
|
|
126
|
+
|
|
127
|
+
<section class="wide"><div class="section-head"><h2>Circadian service</h2><span id="circadian-generated">Loading schedule</span></div>
|
|
128
|
+
<div class="circadian-summary">
|
|
129
|
+
<div class="metric"><small>Service</small><strong id="circadian-active">Checking</strong><span id="circadian-last-run"></span></div>
|
|
130
|
+
<div class="metric"><small>Latest target</small><strong id="circadian-target">—</strong><span id="circadian-applied"></span></div>
|
|
131
|
+
<div class="metric"><small>Schedule</small><strong id="circadian-curve">—</strong><span id="circadian-bounds"></span></div>
|
|
132
|
+
<div class="metric"><small>Weather</small><strong id="circadian-weather">—</strong><span id="circadian-weather-detail"></span></div>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="circadian-controls grid compact">
|
|
135
|
+
<label>Curve <select id="circadian-setting-curve"><option value="cie-daylight">CIE Daylight</option><option value="sun-altitude">Sun Altitude</option><option value="perez-daylight">Perez Daylight</option><option value="physics">Physics</option><option value="blackbody">Blackbody</option><option value="hazy">Hazy</option><option value="hann">Hann</option><option value="wider-middle-small">Wider Middle (Small)</option><option value="wider-middle-medium">Wider Middle (Medium)</option><option value="wider-middle-large">Wider Middle (Large)</option></select></label>
|
|
136
|
+
<label><span>Service enabled</span><input id="circadian-setting-enabled" type="checkbox"></label>
|
|
137
|
+
<label><span>Use live weather</span><input id="circadian-setting-weather" type="checkbox"></label>
|
|
138
|
+
<label>Update interval seconds <input id="circadian-setting-interval" type="number" min="10" max="86400" step="1"></label>
|
|
139
|
+
<label>Minimum Kelvin <input id="circadian-setting-cct-min" type="number" min="1000" max="20000" step="100"></label>
|
|
140
|
+
<label>Maximum Kelvin <input id="circadian-setting-cct-max" type="number" min="1000" max="20000" step="100"></label>
|
|
141
|
+
<label>Minimum intensity % <input id="circadian-setting-intensity-min" type="number" min="0" max="100" step="1"></label>
|
|
142
|
+
<label>Maximum intensity % <input id="circadian-setting-intensity-max" type="number" min="0" max="100" step="1"></label>
|
|
143
|
+
<label>Latitude <input id="circadian-setting-latitude" type="number" min="-90" max="90" step="0.0001" placeholder="Automatic"></label>
|
|
144
|
+
<label>Longitude <input id="circadian-setting-longitude" type="number" min="-180" max="180" step="0.0001" placeholder="Automatic"></label>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="button-row"><button id="circadian-save-settings">Save circadian settings</button><span id="circadian-settings-state"></span></div>
|
|
147
|
+
<div class="graph-wrap">
|
|
148
|
+
<svg id="circadian-graph" viewBox="0 0 1100 300" role="img" aria-label="Circadian Kelvin, intensity, and sunlight lux schedule"></svg>
|
|
149
|
+
<div id="circadian-tooltip" class="graph-tooltip" hidden></div>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="graph-legend"><span class="kelvin-key">Kelvin</span><span class="intensity-key">Natural intensity</span><span class="sunlight-key">Actual sunlight lux (modeled)</span><span class="capacity-key">System lux capacity</span><span>Hover or slide over the graph for exact values</span></div>
|
|
152
|
+
</section>
|
|
153
|
+
|
|
154
|
+
<section><h2>Power & brightness</h2><div class="button-row"><button data-power="on">On</button><button data-power="off">Off</button><button data-power="toggle">Toggle</button></div>
|
|
155
|
+
<label>Brightness <output id="brightness-value">5%</output><input id="brightness" type="range" min="0" max="100" value="5"></label>
|
|
156
|
+
<div class="button-row"><button id="set-brightness">Set</button><label>Fade seconds <input id="fade-seconds" type="number" min=".5" max="20" step=".5" value="2"></label><button id="fade">Fade</button></div>
|
|
157
|
+
</section>
|
|
158
|
+
|
|
159
|
+
<section><h2>White light</h2><label>Kelvin <input id="kelvin" type="number" min="2500" max="7500" step="100" value="3200"></label>
|
|
160
|
+
<label>G/M (-100 magenta, +100 green) <input id="gm" type="number" min="-100" max="100" step="10" value="0"></label>
|
|
161
|
+
<label>Brightness % <input id="cct-brightness" type="number" min="0" max="100" value="5"></label>
|
|
162
|
+
<div class="button-row"><button id="set-cct">Set CCT</button><label>Transition <input id="cct-seconds" type="number" min=".5" max="20" step=".5" value="2"></label><button id="transition-cct">Transition</button></div>
|
|
163
|
+
</section>
|
|
164
|
+
|
|
165
|
+
<section><h2>Color / HSI</h2><label>Hue <input id="hue" type="number" min="0" max="360" value="0"></label>
|
|
166
|
+
<label>Saturation % <input id="saturation" type="number" min="0" max="100" value="100"></label>
|
|
167
|
+
<label>Brightness % <input id="hsi-brightness" type="number" min="0" max="100" value="5"></label>
|
|
168
|
+
<div class="button-row"><button id="set-hsi">Set HSI</button><button id="transition-hsi">Transition</button></div>
|
|
169
|
+
<label>Named or hex color <input id="color" value="#ff6b35"></label><button id="set-color">Set color</button>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<section class="wide"><h2>Native effects</h2><div class="grid compact">
|
|
173
|
+
<label>Effect <select id="effect"></select></label><label>Brightness % <input id="effect-brightness" type="number" min="0" max="100" value="5"></label>
|
|
174
|
+
<label>Frequency 1-10 <input id="effect-frequency" type="number" min="1" max="10" value="5"></label><label>Animation speed 0-10 <input id="effect-speed" type="number" min="0" max="10" value="0"></label>
|
|
175
|
+
<label>Kelvin <input id="effect-kelvin" type="number" min="2500" max="7500" step="100" value="3200"></label><label>G/M <input id="effect-gm" type="number" min="-100" max="100" step="10" value="0"></label>
|
|
176
|
+
<label>Palette 0-2 <input id="effect-palette" type="number" min="0" max="2" value="0"></label><label>Hue <input id="effect-hue" type="number" min="0" max="360" value="0"></label>
|
|
177
|
+
<label>Saturation % <input id="effect-saturation" type="number" min="0" max="100" value="100"></label><label><input id="effect-hsi" type="checkbox"> Use 150c HSI variant</label>
|
|
178
|
+
</div><div class="button-row"><button id="set-effect">Apply effect</button><button id="effect-animation">Update animation speed</button><button id="trigger-effect">Trigger</button><button id="stop-effect">Stop & restore</button></div></section>
|
|
179
|
+
|
|
180
|
+
<section><h2>Fan & thermal state</h2><label>Mode <select id="fan-mode"><option>manual</option><option selected>smart</option><option>max</option><option>off</option><option>high</option><option>medium</option><option>low</option><option>silent</option></select></label>
|
|
181
|
+
<label>Manual RPM <input id="fan-rpm" type="number" min="0" max="65535" value="1500"></label><div class="button-row"><button id="set-fan">Set fan</button><button id="fan-info">Read fan state</button></div>
|
|
182
|
+
</section>
|
|
183
|
+
|
|
184
|
+
<section><h2>Circadian override</h2><label>Hold minutes <input id="hold-minutes" type="number" min="1" max="1440" value="30"></label>
|
|
185
|
+
<div class="button-row"><button id="hold">Hold</button><button id="resume">Resume</button><button id="override-status">Status</button></div>
|
|
186
|
+
</section>
|
|
187
|
+
|
|
188
|
+
<section class="wide"><h2>Advanced verified control</h2><div class="grid compact"><label>Action <input id="advanced-action" value="increment-brightness"></label><label>Arguments JSON <input id="advanced-args" value='{"delta":1}'></label><label>Automatic CCT brightness % <input id="automatic-brightness" type="number" min="0" max="100" value="5"></label></div>
|
|
189
|
+
<div class="button-row"><button id="advanced-run">Run on target</button><button id="advanced-batch">Verified batch</button><button id="advanced-broadcast">Mesh broadcast</button><button id="automatic-cct">Automatic CCT</button><button id="product-info">Product info</button></div>
|
|
190
|
+
</section>
|
|
191
|
+
|
|
192
|
+
<section class="wide"><h2>Groups</h2><div class="grid compact"><label>Group ID/name <input id="group-id"></label><label>New name <input id="group-name"></label><label>Fixture key <input id="group-member"></label><label>Native address (optional) <input id="group-address" placeholder="0xc100"></label></div>
|
|
193
|
+
<div class="button-row"><button id="group-list">List/show</button><button id="group-create">Create</button><button id="group-rename">Rename</button><button id="group-add">Add member</button><button id="group-remove">Remove member</button><button id="group-native-enable">Native enable</button><button id="group-native-sync">Native sync</button><button id="group-native-disable">Native disable</button><button id="group-delete">Delete</button></div>
|
|
194
|
+
</section>
|
|
195
|
+
|
|
196
|
+
<section class="wide"><h2>Scenes, presets & quickshots</h2><div class="grid compact"><label>Collection <select id="collection"><option>scenes</option><option>presets</option><option>quickshots</option></select></label><label>Name / ID <input id="saved-key"></label><label>Transition seconds (optional) <input id="saved-seconds" type="number" min=".5" max="20" step=".5"></label></div>
|
|
197
|
+
<div class="button-row"><button id="saved-list">List</button><button id="saved-show">Show</button><button id="saved-save">Save selected target</button><button id="saved-update">Update</button><button id="saved-recall">Recall</button><button id="saved-delete">Delete</button></div>
|
|
198
|
+
</section>
|
|
199
|
+
|
|
200
|
+
<section class="wide"><h2>Timeline & media programs</h2><label>Timeline JSON <textarea id="program-json" rows="5">{"kind":"timeline","duration":3,"restore":true,"steps":[{"at":0,"action":"brightness","args":{"value":1}},{"at":1,"action":"cct","args":{"kelvin":3200,"brightness":2}}]}</textarea></label>
|
|
201
|
+
<div class="button-row"><button id="program-start">Start timeline</button><button id="jobs-list">List jobs</button><input id="job-id" placeholder="Job ID"><button id="job-stop">Stop job</button></div>
|
|
202
|
+
<div class="grid compact"><label>Local audio path <input id="audio-path"></label><label>Local image path <input id="image-path"></label><label>Duration seconds <input id="media-seconds" type="number" min="1" max="1200" value="30"></label><label>Max brightness % <input id="media-max" type="number" min="0" max="100" value="100"></label></div>
|
|
203
|
+
<div class="button-row"><button id="audio-start">Run audio file</button><button id="image-start">Run image file</button><button id="camera-start">Use browser camera</button><button id="microphone-start">Use browser microphone</button><button id="capture-stop">Stop live capture</button></div>
|
|
204
|
+
<video id="camera-preview" muted playsinline></video><canvas id="camera-canvas" hidden></canvas>
|
|
205
|
+
</section>
|
|
206
|
+
|
|
207
|
+
<section><h2>Mesh diagnostics</h2><div class="button-row"><button id="mesh-inspect">Inspect mesh</button><button id="mesh-discover">Discover unprovisioned</button></div>
|
|
208
|
+
<label>Desktop database path <input id="database-path"></label><div class="button-row"><button id="desktop-preview">Preview Desktop import</button><button id="desktop-apply">Apply Desktop import</button><button id="mesh-import-keys">Import Device Keys</button></div>
|
|
209
|
+
</section>
|
|
210
|
+
|
|
211
|
+
<section><h2>Dashboard settings</h2><label>Title <input id="setting-title"></label><label>Refresh seconds <input id="setting-refresh" type="number" min="5" max="300"></label><label>Default transition seconds <input id="setting-transition" type="number" min=".5" max="20" step=".5"></label><label>Fixture order (comma-separated keys) <input id="setting-order"></label><label>Fixture labels JSON <input id="setting-labels" value="{}"></label><button id="save-settings">Save settings</button></section>
|
|
212
|
+
|
|
213
|
+
<section class="wide"><div class="section-head"><h2>Activity</h2><button id="clear-log">Clear</button></div><pre id="log">Ready.</pre></section>
|
|
214
|
+
</main>
|
|
215
|
+
<script src="/dashboard.js"></script>
|
|
216
|
+
</body>
|
|
217
|
+
</html>`;
|
|
218
|
+
export const dashboardFavicon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
219
|
+
<defs>
|
|
220
|
+
<linearGradient id="glow" x1="12" y1="8" x2="52" y2="56" gradientUnits="userSpaceOnUse">
|
|
221
|
+
<stop stop-color="#fff3a6"/>
|
|
222
|
+
<stop offset=".48" stop-color="#ffbb55"/>
|
|
223
|
+
<stop offset="1" stop-color="#ff6b35"/>
|
|
224
|
+
</linearGradient>
|
|
225
|
+
</defs>
|
|
226
|
+
<rect width="64" height="64" rx="15" fill="#101312"/>
|
|
227
|
+
<circle cx="32" cy="27" r="15" fill="url(#glow)"/>
|
|
228
|
+
<path d="M25 43h14v4H25zm2 6h10v4H27z" fill="#79f2a7"/>
|
|
229
|
+
<path d="M32 5v5M12 27H7m50 0h-5M17.9 12.9l3.5 3.5m25.2-3.5-3.5 3.5" fill="none" stroke="#fff3a6" stroke-linecap="round" stroke-width="3"/>
|
|
230
|
+
</svg>`;
|
|
231
|
+
export const dashboardCss = `:root{color-scheme:dark;--bg:#101312;--panel:#191e1c;--line:#303934;--text:#eef5f0;--muted:#9ca9a1;--green:#79f2a7;--amber:#ffbb55;--red:#ff6b6b;--sun:#62c6ff;--capacity:#c99cff;font:14px/1.35 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 15% -10%,#284032 0,transparent 28rem),var(--bg);color:var(--text)}header,nav,main{max-width:1500px;margin:auto}header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px 9px}h1{font-size:clamp(1.7rem,3vw,2.8rem);line-height:1;margin:.1rem 0}.eyebrow{color:var(--green);font-size:.75rem;font-weight:800;letter-spacing:.16em;margin:0}.connection{display:grid;grid-template-columns:auto auto;gap:1px 7px;align-items:center}.connection small{grid-column:2;color:var(--muted)}#connection-dot{width:10px;height:10px;border-radius:50%;background:var(--amber);box-shadow:0 0 12px currentColor}nav{position:sticky;top:0;z-index:4;display:flex;gap:9px;align-items:end;padding:8px 20px;background:#101312ed;backdrop-filter:blur(14px);border-block:1px solid var(--line)}nav label{display:flex;align-items:center;gap:7px;margin:0}nav select{min-width:180px}main{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;padding:12px 20px 36px}section{background:linear-gradient(145deg,#1b211e,#151917);border:1px solid var(--line);border-radius:12px;padding:13px;box-shadow:0 10px 28px #0003}.wide{grid-column:1/-1}h2{margin:0 0 9px;font-size:1.05rem}.section-head{display:flex;justify-content:space-between;gap:10px;align-items:center}.section-head span,label{color:var(--muted)}label{display:grid;gap:3px;margin:7px 0}input,select,textarea,button,a{font:inherit}input,select,textarea{width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 8px;background:#0e1210;color:var(--text)}input[type=range]{padding:0;accent-color:var(--green)}textarea{resize:vertical;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}button,a{border:1px solid #46534b;border-radius:7px;padding:6px 9px;background:#263029;color:var(--text);font-weight:700;text-decoration:none;cursor:pointer}button:hover,a:hover{border-color:var(--green);color:var(--green)}button:active{transform:translateY(1px)}.button-row{display:flex;gap:6px;flex-wrap:wrap;align-items:end;margin-top:8px}.button-row label{margin:0;min-width:105px}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px}.grid label{margin:0}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:8px}.card{border:1px solid var(--line);background:#101512;border-radius:10px;padding:11px}.card-head{display:flex;justify-content:space-between;align-items:start;gap:8px}.card h3{margin:0 0 2px}.card p{margin:2px 0;color:var(--muted)}.card .on{color:var(--green)}.card .off{color:var(--muted)}.card-controls{display:grid;gap:5px;margin-top:8px}.card-controls label{grid-template-columns:76px 1fr 48px;align-items:center;margin:0}.card-controls output{text-align:right;color:var(--text);font-variant-numeric:tabular-nums}.card-controls input{min-width:0}.circadian-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:10px}.metric{display:grid;gap:2px;background:#101512;border:1px solid var(--line);border-radius:9px;padding:9px}.metric small,.metric span{color:var(--muted)}.metric strong{font-size:1.08rem}.metric .good{color:var(--green)}.metric .bad{color:var(--red)}.metric .warn{color:var(--amber)}.graph-wrap{position:relative;min-height:260px;border:1px solid var(--line);border-radius:10px;background:#0d110f;overflow:hidden}.graph-wrap svg{display:block;width:100%;height:auto;min-height:260px;touch-action:none}.graph-tooltip{position:absolute;z-index:2;pointer-events:none;min-width:150px;padding:7px 9px;border:1px solid #536159;border-radius:7px;background:#090c0af2;color:var(--text);box-shadow:0 8px 20px #0008;font-variant-numeric:tabular-nums}.graph-legend{display:flex;gap:16px;flex-wrap:wrap;color:var(--muted);margin-top:7px}.graph-legend span:before{content:"";display:inline-block;width:16px;height:3px;margin-right:6px;vertical-align:middle;border-radius:2px}.graph-legend .kelvin-key:before{background:var(--green)}.graph-legend .intensity-key:before{background:var(--amber)}.graph-legend .sunlight-key:before{background:var(--sun)}.graph-legend .capacity-key:before{background:repeating-linear-gradient(90deg,var(--capacity) 0 5px,transparent 5px 8px)}.graph-legend span:last-child:before{display:none}pre{max-height:300px;overflow:auto;background:#090c0a;border-radius:8px;padding:10px;white-space:pre-wrap;word-break:break-word;color:#cce7d6}video{display:none;max-width:320px;margin-top:8px;border-radius:8px}video.active{display:block}@media(max-width:1050px){main{grid-template-columns:repeat(2,minmax(0,1fr))}.circadian-summary{grid-template-columns:1fr 1fr}}@media(max-width:720px){header{align-items:flex-start;gap:12px}.connection{margin-top:6px}main{grid-template-columns:1fr}.wide{grid-column:auto}.grid{grid-template-columns:1fr 1fr}nav{align-items:center;flex-wrap:wrap}}@media(max-width:520px){header{display:block}.grid,.circadian-summary{grid-template-columns:1fr}.button-row>*{flex:1 1 auto}}`;
|
|
232
|
+
export const dashboardJs = String.raw `const $=id=>document.getElementById(id);const esc=value=>String(value).replace(/[&<>"']/g,character=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[character]));let health,settings,status,circadian,capture,circadianSettingsDirty=false;
|
|
233
|
+
const log=(label,value)=>{$('log').textContent=new Date().toLocaleTimeString()+' '+label+'\n'+(value===undefined?'':JSON.stringify(value,null,2))+'\n\n'+$('log').textContent};
|
|
234
|
+
async function api(path,options={}){const response=await fetch(path,{headers:{'content-type':'application/json'},...options});const data=await response.json();if(!response.ok||data.ok===false)throw new Error(data.error||response.statusText);return data.result??data.data??data}
|
|
235
|
+
const selected=()=>$('target').value;const targets=()=>selected()==='all'?'all':[selected()];
|
|
236
|
+
async function lightAction(action,args={}){return selected()==='all'?api('/batch',{method:'POST',body:JSON.stringify({targets:'all',action,args})}):api('/lights/'+encodeURIComponent(selected())+'/'+action,{method:'POST',body:JSON.stringify(args)})}
|
|
237
|
+
async function act(label,fn){try{const value=await fn();log(label,value);await refreshStatus()}catch(error){log('ERROR: '+label,{error:error.message})}}
|
|
238
|
+
function fixtureName(key){return settings?.fixtureLabels?.[key]||health?.lights?.find(light=>light.key===key)?.name||key}
|
|
239
|
+
function interpolateLux(calibration,kelvin){if(typeof calibration==='number')return calibration;const points=Object.entries(calibration||{}).map(([cct,lux])=>({cct:+cct,lux:+lux})).filter(point=>Number.isFinite(point.cct)&&Number.isFinite(point.lux)).sort((a,b)=>a.cct-b.cct);if(!points.length)return null;if(kelvin<=points[0].cct)return points[0].lux;if(kelvin>=points.at(-1).cct)return points.at(-1).lux;for(let index=0;index<points.length-1;index++){const left=points[index],right=points[index+1];if(kelvin>=left.cct&&kelvin<=right.cct)return left.lux+(kelvin-left.cct)*(right.lux-left.lux)/(right.cct-left.cct)}return null}
|
|
240
|
+
function cardLux(light,state,kelvin,brightness){if(state?.sleep)return 0;if(state?.mode!=='cct'||!light.luxCalibration)return null;const full=interpolateLux(light.luxCalibration,kelvin);return full===null?null:Math.round(full*brightness/100)}
|
|
241
|
+
function luxLabel(value){return value===null?'Est. lux —':'Est. '+value.toLocaleString()+' lx'}
|
|
242
|
+
function statusClass(element,value){element.className=value}
|
|
243
|
+
const shortLux=value=>value>=1000?(Math.round(value/100)/10)+'k lx':Math.round(value)+' lx';
|
|
244
|
+
function renderCircadian(){if(!circadian)return;const service=circadian.service,current=circadian.current,schedule=circadian.schedule;$('circadian-generated').textContent='Updated '+new Date(circadian.generatedAt).toLocaleTimeString();$('circadian-active').textContent=service.healthy?'Active':service.active?'Loaded, waiting':'Inactive';statusClass($('circadian-active'),service.healthy?'good':service.active?'warn':'bad');$('circadian-last-run').textContent=service.lastRunAt?'Last run '+new Date(service.lastRunAt).toLocaleTimeString():'No recorded run';const target=service.lastTarget||current;$('circadian-target').textContent=target?Math.round(target.cct)+' K · '+target.intensity+'%':'—';const applied=status?.lighting?Object.values(status.lighting)[0]:undefined;$('circadian-applied').textContent=applied?.cct!==undefined?'Applied '+applied.cct+' K · '+applied.intensity/10+'%':'No fixture readback';$('circadian-curve').textContent=(current?.curve||service.curve||'Unknown').replaceAll('-',' ');if(schedule?.points?.length){const intensities=schedule.points.map(point=>point.intensity),ccts=schedule.points.map(point=>point.cct),sunlight=schedule.points.map(point=>point.sunlightLux||0),capacity=schedule.points.map(point=>point.systemCapacityLux||0);$('circadian-bounds').textContent=Math.min(...ccts)+'–'+Math.max(...ccts)+' K · '+Math.min(...intensities)+'–'+Math.max(...intensities)+'% · Sun '+shortLux(Math.max(...sunlight))+(Math.max(...capacity)>0?' · System '+shortLux(Math.max(...capacity)):'')}else $('circadian-bounds').textContent=circadian.calculationError||'Schedule unavailable';$('circadian-weather').textContent=service.weatherConfigured?(current?.weatherActive?'Active':'Configured, unavailable'):'Off';statusClass($('circadian-weather'),current?.weatherActive?'good':service.weatherConfigured?'warn':'');const weather=[];if(current?.cloudCover!==undefined)weather.push(Math.round(current.cloudCover*100)+'% cloud');if(current?.precipitation)weather.push(current.precipitation);if(current?.weatherDataSource)weather.push(current.weatherDataSource);if(current?.weatherEffect){const effect=current.weatherEffect;weather.push('effect '+(effect.intensityDelta>=0?'+':'')+effect.intensityDelta+'%'+(effect.cctDelta?(effect.cctDelta>=0?' · +':' · ')+effect.cctDelta+' K':'')+(effect.sunlightLuxDelta!==undefined?' · '+(effect.sunlightLuxDelta>=0?'+':'')+effect.sunlightLuxDelta.toLocaleString()+' lx':'')+' vs clear')} $('circadian-weather-detail').textContent=weather.join(' · ')||'No weather adjustment';if(!circadianSettingsDirty&&circadian.settings){const value=circadian.settings;$('circadian-setting-enabled').checked=value.enabled;$('circadian-setting-curve').value=value.curve;$('circadian-setting-weather').checked=value.weather;$('circadian-setting-interval').value=value.intervalSeconds;$('circadian-setting-cct-min').value=value.cctMin;$('circadian-setting-cct-max').value=value.cctMax;$('circadian-setting-intensity-min').value=value.intensityMin;$('circadian-setting-intensity-max').value=value.intensityMax;$('circadian-setting-latitude').value=value.latitude??'';$('circadian-setting-longitude').value=value.longitude??''}renderCircadianGraph(schedule)}
|
|
245
|
+
function renderCircadianGraph(schedule){const svg=$('circadian-graph'),tooltip=$('circadian-tooltip');if(!schedule?.points?.length){svg.innerHTML='<text x="550" y="150" text-anchor="middle" fill="#9ca9a1">Schedule unavailable</text>';return}const points=schedule.points,width=1100,height=300,left=64,right=112,top=22,bottom=42,plotWidth=width-left-right,plotHeight=height-top-bottom,minCct=Math.min(...points.map(point=>point.cct)),maxCct=Math.max(...points.map(point=>point.cct)),cctSpan=Math.max(1,maxCct-minCct),intensityLimit=Math.max(0,Math.min(100,schedule.intensityLimit??100)),sunlightValues=points.map(point=>point.sunlightLux??point.lightOutput??0),capacityValues=points.map(point=>point.systemCapacityLux??0),luxScale=Math.max(10000,Math.ceil(Math.max(...sunlightValues,...capacityValues)/10000)*10000);const x=index=>left+index/(points.length-1)*plotWidth,yCct=value=>top+(maxCct-value)/cctSpan*plotHeight,yIntensity=value=>top+(100-value)/100*plotHeight,yLux=value=>top+(luxScale-value)/luxScale*plotHeight,line=values=>values.map((value,index)=>x(index).toFixed(1)+','+value.toFixed(1)).join(' '),kelvin=line(points.map(point=>yCct(point.cct))),intensity=line(points.map(point=>yIntensity(point.intensity))),sunlight=line(sunlightValues.map(yLux)),capacity=line(capacityValues.map(yLux)),limitY=yIntensity(intensityLimit),now=Date.now(),currentIndex=points.reduce((best,point,index)=>Math.abs(Date.parse(point.time)-now)<Math.abs(Date.parse(points[best].time)-now)?index:best,0),grid=[0,.25,.5,.75,1].map(factor=>{const y=top+factor*plotHeight;return '<line x1="'+left+'" y1="'+y+'" x2="'+(width-right)+'" y2="'+y+'" stroke="#26302b"/>'}).join(''),labels=[0,6,12,18,24].map(hour=>{const index=Math.min(points.length-1,Math.round(hour/24*(points.length-1)));return '<text x="'+x(index)+'" y="'+(height-14)+'" text-anchor="middle" fill="#9ca9a1" font-size="12">'+String(hour).padStart(2,'0')+':00</text>'}).join(''),capacityLine=Math.max(...capacityValues)>0?'<polyline points="'+capacity+'" fill="none" stroke="#c99cff" stroke-width="2" stroke-dasharray="7 5"/>':'';svg.innerHTML=grid+'<text x="10" y="'+(top+5)+'" fill="#79f2a7" font-size="12">'+maxCct+' K</text><text x="10" y="'+(top+plotHeight)+'" fill="#79f2a7" font-size="12">'+minCct+' K</text><text x="'+(width-62)+'" y="'+(top+5)+'" text-anchor="end" fill="#ffbb55" font-size="12">100%</text><text x="'+(width-62)+'" y="'+(top+plotHeight)+'" text-anchor="end" fill="#ffbb55" font-size="12">0%</text><text x="'+(width-5)+'" y="'+(top+5)+'" text-anchor="end" fill="#62c6ff" font-size="12">'+shortLux(luxScale)+'</text><text x="'+(width-5)+'" y="'+(top+plotHeight)+'" text-anchor="end" fill="#62c6ff" font-size="12">0 lx</text>'+labels+'<line x1="'+left+'" y1="'+limitY+'" x2="'+(width-right)+'" y2="'+limitY+'" stroke="#ffbb55" stroke-width="2" stroke-dasharray="6 6" opacity=".75"/><text x="'+(width-right-4)+'" y="'+(limitY-6)+'" text-anchor="end" fill="#ffbb55" font-size="12">'+intensityLimit+'% service limit</text><polyline points="'+sunlight+'" fill="none" stroke="#62c6ff" stroke-width="3"/>'+capacityLine+'<polyline points="'+kelvin+'" fill="none" stroke="#79f2a7" stroke-width="3"/><polyline points="'+intensity+'" fill="none" stroke="#ffbb55" stroke-width="3"/><line x1="'+x(currentIndex)+'" y1="'+top+'" x2="'+x(currentIndex)+'" y2="'+(top+plotHeight)+'" stroke="#eef5f0" stroke-dasharray="5 5" opacity=".65"/><line id="circadian-crosshair" x1="0" y1="'+top+'" x2="0" y2="'+(top+plotHeight)+'" stroke="#eef5f0" opacity="0"/><circle id="circadian-k-dot" r="5" fill="#79f2a7" opacity="0"/><circle id="circadian-i-dot" r="5" fill="#ffbb55" opacity="0"/><circle id="circadian-sun-dot" r="5" fill="#62c6ff" opacity="0"/>';const show=event=>{const box=svg.getBoundingClientRect(),ratio=Math.max(0,Math.min(1,(event.clientX-box.left)/box.width)),index=Math.round(ratio*(points.length-1)),point=points[index],px=x(index),cross=$('circadian-crosshair'),kdot=$('circadian-k-dot'),idot=$('circadian-i-dot'),sdot=$('circadian-sun-dot'),sunLux=point.sunlightLux??point.lightOutput??0;cross.setAttribute('x1',px);cross.setAttribute('x2',px);cross.setAttribute('opacity','1');kdot.setAttribute('cx',px);kdot.setAttribute('cy',yCct(point.cct));kdot.setAttribute('opacity','1');idot.setAttribute('cx',px);idot.setAttribute('cy',yIntensity(point.intensity));idot.setAttribute('opacity','1');sdot.setAttribute('cx',px);sdot.setAttribute('cy',yLux(sunLux));sdot.setAttribute('opacity','1');tooltip.hidden=false;tooltip.innerHTML='<strong>'+new Date(point.time).toLocaleTimeString([],{hour:'numeric',minute:'2-digit'})+'</strong><br>'+point.cct+' K<br>Natural '+point.intensity+'%<br>Service applies '+point.appliedIntensity+'%<br>Actual sunlight '+Math.round(sunLux).toLocaleString()+' lx'+(point.systemCapacityLux!==undefined?'<br>System capacity '+Math.round(point.systemCapacityLux).toLocaleString()+' lx':'');tooltip.style.left=Math.min(box.width-210,Math.max(8,event.clientX-box.left+12))+'px';tooltip.style.top=Math.max(8,event.clientY-box.top-112)+'px'};svg.onpointermove=show;svg.onpointerdown=show;svg.onpointerleave=()=>{tooltip.hidden=true;for(const id of ['circadian-crosshair','circadian-k-dot','circadian-i-dot','circadian-sun-dot'])$(id)?.setAttribute('opacity','0')}}
|
|
246
|
+
function renderStatus(){const root=$('fixture-cards');root.textContent='';for(const light of health?.lights||[]){const state=status?.lighting?.[light.key];const fan=status?.fans?.[light.key];const brightness=state?state.intensity/10:0;const kelvin=state?.cct??Math.max(light.capabilities.cct_min,Math.min(light.capabilities.cct_max,3200));const lux=status?.estimatedLux?.[light.key]??cardLux(light,state,kelvin,brightness);const card=document.createElement('article');card.className='card';const details=state?state.mode==='cct'?(state.gm!==undefined?'G/M '+state.gm:'CCT'):state.mode==='hsi'?'H '+state.hue+'° S '+state.sat+'%':state.effect+' · F'+state.frequency:'No live readback';card.innerHTML='<div class="card-head"><div><h3>'+esc(fixtureName(light.key))+'</h3><p>'+esc(light.model)+' · '+esc(light.key)+'</p></div><button data-card-power>'+esc(state?.sleep?'Turn on':'Turn off')+'</button></div><p class="'+(state?.sleep?'off':'on')+'">'+(state?.sleep?'OFF':'ON')+' · '+brightness+'% · '+kelvin+'K · '+esc(details)+'</p><p><strong data-card-lux>'+esc(luxLabel(lux))+'</strong> · Fan '+esc(fan?.modeName??'unknown')+' · '+esc(fan?.speed??'—')+' RPM · '+esc(fan?.temperature??'—')+'°</p><div class="card-controls"><label>Brightness <input data-card-brightness type="range" min="0" max="100" step="1" value="'+brightness+'"><output data-card-brightness-output>'+brightness+'%</output></label><label>Kelvin <input data-card-kelvin type="range" min="'+light.capabilities.cct_min+'" max="'+light.capabilities.cct_max+'" step="'+light.capabilities.cct_step+'" value="'+kelvin+'"><output data-card-kelvin-output>'+kelvin+'K</output></label></div>';const brightnessInput=card.querySelector('[data-card-brightness]'),brightnessOutput=card.querySelector('[data-card-brightness-output]'),kelvinInput=card.querySelector('[data-card-kelvin]'),kelvinOutput=card.querySelector('[data-card-kelvin-output]'),luxOutput=card.querySelector('[data-card-lux]');const updateEstimate=()=>{luxOutput.textContent=luxLabel(cardLux(light,state,+kelvinInput.value,+brightnessInput.value))};brightnessInput.oninput=()=>{brightnessOutput.value=brightnessInput.value+'%';updateEstimate()};brightnessInput.onchange=()=>act('Set '+fixtureName(light.key)+' brightness',()=>api('/lights/'+encodeURIComponent(light.key)+'/brightness',{method:'POST',body:JSON.stringify({value:+brightnessInput.value})}));kelvinInput.oninput=()=>{kelvinOutput.value=kelvinInput.value+'K';updateEstimate()};kelvinInput.onchange=()=>act('Set '+fixtureName(light.key)+' Kelvin',()=>api('/lights/'+encodeURIComponent(light.key)+'/cct',{method:'POST',body:JSON.stringify({kelvin:+kelvinInput.value})}));card.querySelector('[data-card-power]').onclick=()=>act((state?.sleep?'Turn on ':'Turn off ')+fixtureName(light.key),()=>api('/lights/'+encodeURIComponent(light.key)+'/'+(state?.sleep?'on':'off'),{method:'POST',body:'{}'}));root.append(card)}$('updated').textContent=status?'Updated '+new Date(status.updatedAt).toLocaleTimeString():'No cached state'}
|
|
247
|
+
async function refreshStatus(){try{status=await api('/dashboard/status');$('connection').textContent='Connected';$('connection-dot').style.background='#79f2a7';renderStatus()}catch(error){$('connection').textContent='Disconnected';$('connection-dot').style.background='#ff6b6b';log('Status refresh failed',{error:error.message})}}
|
|
248
|
+
async function refreshCircadian(){try{circadian=await api('/dashboard/circadian');renderCircadian()}catch(error){$('circadian-active').textContent='Unavailable';statusClass($('circadian-active'),'bad');$('circadian-bounds').textContent=error.message;log('Circadian refresh failed',{error:error.message})}}
|
|
249
|
+
const circadianSettingIds=['enabled','curve','weather','interval','cct-min','cct-max','intensity-min','intensity-max','latitude','longitude'];for(const id of circadianSettingIds)$('circadian-setting-'+id).onchange=()=>{circadianSettingsDirty=true;$('circadian-settings-state').textContent='Unsaved changes'};
|
|
250
|
+
$('circadian-save-settings').onclick=async()=>{try{const nullableNumber=id=>$(id).value===''?null:+$(id).value;const body={enabled:$('circadian-setting-enabled').checked,curve:$('circadian-setting-curve').value,weather:$('circadian-setting-weather').checked,intervalSeconds:+$('circadian-setting-interval').value,cctMin:+$('circadian-setting-cct-min').value,cctMax:+$('circadian-setting-cct-max').value,intensityMin:+$('circadian-setting-intensity-min').value,intensityMax:+$('circadian-setting-intensity-max').value,latitude:nullableNumber('circadian-setting-latitude'),longitude:nullableNumber('circadian-setting-longitude')};circadian=await api('/dashboard/circadian/settings',{method:'POST',body:JSON.stringify(body)});circadianSettingsDirty=false;$('circadian-settings-state').textContent='Saved';renderCircadian();log('Saved circadian settings',body)}catch(error){$('circadian-settings-state').textContent='Save failed';log('ERROR: Save circadian settings',{error:error.message})}};
|
|
251
|
+
function rebuildTargets(){const previous=selected();const order=[...settings.fixtureOrder,...health.lights.map(x=>x.key).filter(x=>!settings.fixtureOrder.includes(x))];$('target').innerHTML='<option value="all">All fixtures</option>'+order.map(key=>'<option value="'+esc(key)+'">'+esc(fixtureName(key))+'</option>').join('')+(health.groups||[]).map(group=>'<option value="'+esc(group.id)+'">Group: '+esc(group.name)+'</option>').join('');if([...$('target').options].some(option=>option.value===previous))$('target').value=previous}
|
|
252
|
+
async function refreshMetadata(){health=await api('/health');rebuildTargets()}
|
|
253
|
+
async function boot(){settings=await api('/dashboard/settings');document.title=settings.title;$('title').textContent=settings.title;$('setting-title').value=settings.title;$('setting-refresh').value=settings.refreshSeconds;$('setting-transition').value=settings.defaultTransitionSeconds;$('setting-order').value=settings.fixtureOrder.join(',');$('setting-labels').value=JSON.stringify(settings.fixtureLabels);for(const id of ['fade-seconds','cct-seconds'])$(id).value=settings.defaultTransitionSeconds;await refreshMetadata();const effects=[...new Set(health.lights.flatMap(x=>x.capabilities.effects))];$('effect').innerHTML=effects.map(x=>'<option>'+esc(x)+'</option>').join('');try{status=await api('/dashboard/status-cache');renderStatus()}catch{}await refreshStatus();await refreshCircadian();setInterval(()=>{refreshStatus();refreshCircadian()},settings.refreshSeconds*1000)}
|
|
254
|
+
$('brightness').oninput=()=>{$('brightness-value').textContent=$('brightness').value+'%'};
|
|
255
|
+
document.querySelectorAll('[data-power]').forEach(button=>button.onclick=()=>act('Power '+button.dataset.power,()=>lightAction(button.dataset.power)));
|
|
256
|
+
$('set-brightness').onclick=()=>act('Set brightness',()=>lightAction('brightness',{value:+$('brightness').value}));
|
|
257
|
+
$('fade').onclick=()=>act('Fade',()=>api('/fade',{method:'POST',body:JSON.stringify({targets:targets(),brightness:+$('brightness').value,seconds:+$('fade-seconds').value})}));
|
|
258
|
+
const allTargetsSupport=capability=>targetKeys().every(key=>health.lights.find(light=>light.key===key)?.capabilities?.[capability]===true);
|
|
259
|
+
const cctArgs=()=>({kelvin:+$('kelvin').value,brightness:+$('cct-brightness').value,...(allTargetsSupport('gm_support')?{gm:+$('gm').value}:{})});
|
|
260
|
+
$('set-cct').onclick=()=>act('Set CCT',()=>lightAction('cct',cctArgs()));
|
|
261
|
+
$('transition-cct').onclick=()=>act('Transition CCT',()=>api('/transition',{method:'POST',body:JSON.stringify({targets:targets(),action:'cct',args:cctArgs(),seconds:+$('cct-seconds').value})}));
|
|
262
|
+
const hsiArgs=()=>({hue:+$('hue').value,saturation:+$('saturation').value,brightness:+$('hsi-brightness').value});
|
|
263
|
+
$('set-hsi').onclick=()=>act('Set HSI',()=>lightAction('hsi',hsiArgs()));
|
|
264
|
+
$('transition-hsi').onclick=()=>act('Transition HSI',()=>api('/transition',{method:'POST',body:JSON.stringify({targets:targets(),action:'hsi',args:hsiArgs(),seconds:+$('cct-seconds').value})}));
|
|
265
|
+
$('set-color').onclick=()=>act('Set color',()=>lightAction('color',{color:$('color').value,brightness:+$('hsi-brightness').value}));
|
|
266
|
+
const effectArgs=()=>{const name=$('effect').value,args={name,brightness:+$('effect-brightness').value,frequency:+$('effect-frequency').value};if(['lightning','faulty-bulb','pulsing'].includes(name))args.speed=+$('effect-speed').value;if(['tv','fire','fireworks','cop-car'].includes(name))args.palette=+$('effect-palette').value;else if(name==='party-lights')args.saturation=+$('effect-saturation').value;else if(['faulty-bulb','pulsing'].includes(name)&&$('effect-hsi').checked){args.hue=+$('effect-hue').value;args.saturation=+$('effect-saturation').value}else if(['paparazzi','lightning','faulty-bulb','pulsing','strobe','explosion'].includes(name)){args.kelvin=+$('effect-kelvin').value;if(allTargetsSupport('gm_support'))args.gm=+$('effect-gm').value}return args};
|
|
267
|
+
$('set-effect').onclick=()=>act('Apply effect',()=>lightAction('effect',effectArgs()));$('effect-animation').onclick=()=>act('Effect animation speed',()=>lightAction('effect-animation-speed',{value:+$('effect-speed').value}));$('trigger-effect').onclick=()=>act('Trigger effect',()=>lightAction('effect-trigger'));$('stop-effect').onclick=()=>act('Stop effect',()=>lightAction('effect-stop'));
|
|
268
|
+
$('set-fan').onclick=()=>act('Set fan',()=>api('/fans',{method:'POST',body:JSON.stringify({targets:targets(),mode:$('fan-mode').value,...($('fan-mode').value==='manual'?{rpm:+$('fan-rpm').value}:{})})}));$('fan-info').onclick=()=>act('Fan state',()=>api('/fans'));
|
|
269
|
+
const override=minutes=>api('/overrides',{method:'POST',body:JSON.stringify({targets:targets(),...(minutes===undefined?{}:{minutes})})});$('hold').onclick=()=>act('Hold automation',()=>override(+$('hold-minutes').value));$('resume').onclick=()=>act('Resume automation',()=>override(0));$('override-status').onclick=()=>act('Override status',()=>override());
|
|
270
|
+
const targetKeys=()=>selected()==='all'?health.lights.map(light=>light.key):((health.groups||[]).find(group=>group.id===selected())?.members||[selected()]);const advancedArgs=()=>JSON.parse($('advanced-args').value);$('advanced-run').onclick=()=>act('Advanced action',()=>lightAction($('advanced-action').value,advancedArgs()));$('advanced-batch').onclick=()=>act('Advanced batch',()=>api('/batch',{method:'POST',body:JSON.stringify({targets:targets(),action:$('advanced-action').value,args:advancedArgs()})}));$('advanced-broadcast').onclick=()=>act('Mesh broadcast',()=>api('/batch',{method:'POST',body:JSON.stringify({targets:targets(),action:$('advanced-action').value,args:advancedArgs(),broadcast:true})}));$('automatic-cct').onclick=()=>act('Automatic CCT',()=>Promise.all(targetKeys().map(key=>{const light=health.lights.find(item=>item.key===key);return api('/lights/'+encodeURIComponent(key)+'/auto-cct',{method:'POST',body:JSON.stringify({kelvin:+$('kelvin').value,brightness:+$('automatic-brightness').value,...(light?.capabilities?.gm_support?{gm:+$('gm').value}:{})})})})));$('product-info').onclick=()=>act('Product information',()=>Promise.all(targetKeys().map(key=>api('/lights/'+encodeURIComponent(key)+'/info'))));
|
|
271
|
+
const withMetadata=async operation=>{const result=await operation();await refreshMetadata();return result};const gid=()=>encodeURIComponent($('group-id').value);$('group-list').onclick=()=>act('Groups',()=>api($('group-id').value?'/groups/'+gid():'/groups'));$('group-create').onclick=()=>act('Create group',()=>withMetadata(()=>api('/groups',{method:'POST',body:JSON.stringify({name:$('group-name').value})})));$('group-rename').onclick=()=>act('Rename group',()=>withMetadata(()=>api('/groups/'+gid()+'/rename',{method:'POST',body:JSON.stringify({name:$('group-name').value})})));const member=remove=>api('/groups/'+gid()+'/members',{method:'POST',body:JSON.stringify({member:$('group-member').value,remove})});$('group-add').onclick=()=>act('Add group member',()=>withMetadata(()=>member(false)));$('group-remove').onclick=()=>act('Remove group member',()=>withMetadata(()=>member(true)));const native=action=>api('/groups/'+gid()+'/native',{method:'POST',body:JSON.stringify({action,...($('group-address').value?{address:Number($('group-address').value)}:{})})});$('group-native-enable').onclick=()=>act('Enable native group',()=>native('enable'));$('group-native-sync').onclick=()=>act('Sync native group',()=>native('sync'));$('group-native-disable').onclick=()=>act('Disable native group',()=>native('disable'));$('group-delete').onclick=()=>act('Delete group',()=>withMetadata(()=>api('/groups/'+gid(),{method:'DELETE'})));
|
|
272
|
+
const collection=()=>$('collection').value,key=()=>encodeURIComponent($('saved-key').value),base=()=>'/library/'+collection();$('saved-list').onclick=()=>act('List '+collection(),()=>api(base()));$('saved-show').onclick=()=>act('Show saved state',()=>api(base()+'/'+key()));$('saved-save').onclick=()=>act('Save state',()=>api(base(),{method:'POST',body:JSON.stringify({name:$('saved-key').value,keys:targets()})}));$('saved-update').onclick=()=>act('Update state',()=>api(base()+'/'+key(),{method:'POST',body:JSON.stringify({name:$('saved-key').value,keys:targets()})}));$('saved-recall').onclick=()=>act('Recall state',()=>api(base()+'/'+key()+'/recall',{method:'POST',body:JSON.stringify({...(collection()==='presets'&&selected()!=='all'?{target:selected()}:{}),...($('saved-seconds').value?{seconds:+$('saved-seconds').value}:{})})}));$('saved-delete').onclick=()=>act('Delete state',()=>api(base()+'/'+key(),{method:'DELETE'}));
|
|
273
|
+
const startProgram=body=>api('/programs',{method:'POST',body:JSON.stringify({...body,targets:targets()})});$('program-start').onclick=()=>act('Start timeline',()=>startProgram(JSON.parse($('program-json').value)));$('jobs-list').onclick=()=>act('Jobs',()=>api('/programs'));$('job-stop').onclick=()=>act('Stop job',()=>api('/programs/'+encodeURIComponent($('job-id').value),{method:'DELETE'}));$('audio-start').onclick=()=>act('Start audio file',()=>startProgram({kind:'audio',duration:+$('media-seconds').value,maxBrightness:+$('media-max').value,gain:3,restore:true,source:{kind:'audio-file',file:$('audio-path').value}}));$('image-start').onclick=()=>act('Start image file',()=>startProgram({kind:'picker',duration:+$('media-seconds').value,maxBrightness:+$('media-max').value,restore:true,source:{kind:'image-file',file:$('image-path').value}}));
|
|
274
|
+
async function stopCapture(){if(!capture)return;clearInterval(capture.timer);capture.stream.getTracks().forEach(x=>x.stop());if(capture.audio)await capture.audio.close();await api('/programs/'+capture.job,{method:'DELETE'}).catch(()=>{});$('camera-preview').classList.remove('active');capture=undefined;log('Live capture stopped')}
|
|
275
|
+
async function startCamera(){await stopCapture();const stream=await navigator.mediaDevices.getUserMedia({video:true});const job=await startProgram({kind:'picker',duration:+$('media-seconds').value,maxBrightness:+$('media-max').value,restore:true,source:{kind:'samples',media:'image'}});const video=$('camera-preview'),canvas=$('camera-canvas');video.srcObject=stream;await video.play();video.classList.add('active');const ctx=canvas.getContext('2d',{willReadFrequently:true});const timer=setInterval(async()=>{canvas.width=64;canvas.height=36;ctx.drawImage(video,0,0,64,36);const data=ctx.getImageData(0,0,64,36).data;let r=0,g=0,b=0,n=0;for(let i=0;i<data.length;i+=16){r+=data[i];g+=data[i+1];b+=data[i+2];n++}await api('/programs/'+job.id+'/sample',{method:'POST',body:JSON.stringify({rgb:[r/n,g/n,b/n]})}).catch(error=>log('Camera sample failed',{error:error.message}))},500);capture={stream,job:job.id,timer}}
|
|
276
|
+
async function startMicrophone(){await stopCapture();const stream=await navigator.mediaDevices.getUserMedia({audio:true});const audio=new AudioContext(),source=audio.createMediaStreamSource(stream),analyser=audio.createAnalyser();analyser.fftSize=1024;source.connect(analyser);const samples=new Float32Array(analyser.fftSize);const job=await startProgram({kind:'audio',duration:+$('media-seconds').value,maxBrightness:+$('media-max').value,gain:3,restore:true,source:{kind:'samples',media:'audio'}});const timer=setInterval(async()=>{analyser.getFloatTimeDomainData(samples);let total=0;for(const value of samples)total+=value*value;await api('/programs/'+job.id+'/sample',{method:'POST',body:JSON.stringify({rms:Math.sqrt(total/samples.length)})}).catch(error=>log('Microphone sample failed',{error:error.message}))},250);capture={stream,job:job.id,timer,audio}}
|
|
277
|
+
$('camera-start').onclick=()=>act('Start browser camera',startCamera);$('microphone-start').onclick=()=>act('Start browser microphone',startMicrophone);$('capture-stop').onclick=()=>act('Stop live capture',stopCapture);
|
|
278
|
+
$('mesh-inspect').onclick=()=>act('Inspect mesh',()=>api('/mesh/inspect'));$('mesh-discover').onclick=()=>act('Discover mesh devices',()=>api('/mesh/discover'));const database=()=>$('database-path').value;$('desktop-preview').onclick=()=>act('Preview Desktop import',()=>api('/desktop/import',{method:'POST',body:JSON.stringify({database:database()})}));$('desktop-apply').onclick=()=>act('Apply Desktop import',()=>api('/desktop/import',{method:'POST',body:JSON.stringify({database:database(),apply:true})}));$('mesh-import-keys').onclick=()=>act('Import Device Keys',()=>api('/mesh/keys',{method:'POST',body:JSON.stringify({database:database()})}));
|
|
279
|
+
$('save-settings').onclick=()=>act('Save dashboard settings',async()=>{settings=await api('/dashboard/settings',{method:'POST',body:JSON.stringify({title:$('setting-title').value,refreshSeconds:+$('setting-refresh').value,defaultTransitionSeconds:+$('setting-transition').value,fixtureOrder:$('setting-order').value.split(',').map(value=>value.trim()).filter(Boolean),fixtureLabels:JSON.parse($('setting-labels').value)})});$('title').textContent=settings.title;rebuildTargets();return settings});$('refresh').onclick=refreshStatus;$('clear-log').onclick=()=>{$('log').textContent=''};boot().catch(error=>log('Dashboard failed to start',{error:error.message}));`;
|
|
280
|
+
//# sourceMappingURL=dashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../src/ble/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,CAAC;IACN,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChD,wBAAwB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACrD,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAC1C,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;CACrD,CAAC;KACD,MAAM,EAAE;KACR,SAAS,CAAC,CAAC,EAAE,aAAa,EAAE,oBAAoB,EAAE,GAAG,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;AAEjF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC;KACnC,MAAM,CAAC;IACN,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;IACtB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC;IAC9B,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACxE,CAAC;KACD,MAAM,EAAE,CAAC;AAKZ,MAAM,UAAU,WAAW,CAAC,WAA0C,EAAE,KAAmB;IACzF,IAAI,KAAK,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IAC1B,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC9F,MAAM,UAAU,GAAG,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC7G,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,IAAc,EAAqB,EAAE,CAAC,CAAC;IACvD,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,gBAAgB;IACvB,cAAc,EAAE,EAAE;IAClB,wBAAwB,EAAE,CAAC;IAC3B,YAAY,EAAE,IAAI;IAClB,aAAa,EAAE,EAAE;CAClB,CAAC,CAAC;AAEH,MAAM,OAAO,cAAc;IACjB,QAAQ,CAAoB;IAC5B,MAAM,CAAmB;IAChB,YAAY,CAAU;IACtB,UAAU,CAAU;IAErC,YAAY,SAAkB,EAAE,cAAwB,EAAE;QACxD,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5F,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxF,MAAM,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,IAAI,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7E,MAAM,iBAAiB,GACrB,eAAe,IAAI,IAAI,CAAC,YAAY;YAClC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAa;YAClE,CAAC,CAAC,SAAS,CAAC;QAChB,IAAI,CAAC,QAAQ;YACX,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC7G,IAAI,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC;YAChD,IAAI,CAAC,MAAM,GAAG,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/F,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAChE,IAAI,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,eAAe,IAAI,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChH,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC;IAED,WAAW;QACT,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,cAAc,CAAC,KAAc,EAAE,WAAqB;QAClD,MAAM,IAAI,GAAG,uBAAuB,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAI,KAAgB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QACnG,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACjH,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,YAAY,CAAC,MAAM;YAC9D,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC3D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC;YAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,EAAE,CAAC,CAAC;QACvF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACvD,IAAI,IAAI,CAAC,YAAY;YAAE,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,CAAC;IAED,UAAU,CAAC,KAAsB;QAC/B,IAAI,CAAC,MAAM,GAAG,qBAAqB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,IAAI,CAAC,UAAU;YAAE,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9D,OAAO,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAEO,cAAc,CAAC,QAA2B,EAAE,WAAqB;QACvE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;QACrC,OAAO;YACL,GAAG,QAAQ;YACX,YAAY,EAAE;gBACZ,GAAG,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC1D,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;aACrE;YACD,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;SAC9G,CAAC;IACJ,CAAC;CACF;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgHrB,CAAC;AAET,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;OAYzB,CAAC;AAER,MAAM,CAAC,MAAM,YAAY,GAAG,qnKAAqnK,CAAC;AAElpK,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;spBA+CinB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop.d.ts","sourceRoot":"","sources":["../../src/ble/desktop.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAWtE;AAID,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,UAAU,CAyBlF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export function desktopRows(database, query) {
|
|
5
|
+
const output = execFileSync('sqlite3', ['-readonly', '-json', realpathSync(database), `PRAGMA query_only=ON; PRAGMA trusted_schema=OFF; ${query}`], {
|
|
6
|
+
encoding: 'utf8',
|
|
7
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
8
|
+
timeout: 10_000,
|
|
9
|
+
});
|
|
10
|
+
return z.array(z.unknown()).parse(JSON.parse(output.trim() || '[]'));
|
|
11
|
+
}
|
|
12
|
+
const mac = (value) => value.replace(/[^0-9a-f]/gi, '').toLowerCase();
|
|
13
|
+
export function desktopDeviceKeys(database, config) {
|
|
14
|
+
const meshes = z
|
|
15
|
+
.array(z.object({ net_key: z.string(), app_key: z.string() }))
|
|
16
|
+
.parse(desktopRows(database, 'SELECT net_key, app_key FROM mesh'));
|
|
17
|
+
if (!meshes.some((mesh) => mesh.net_key.toLowerCase() === config.netKey.toLowerCase() &&
|
|
18
|
+
mesh.app_key.toLowerCase() === config.appKey.toLowerCase()))
|
|
19
|
+
throw new Error('Desktop database does not match the configured mesh keys');
|
|
20
|
+
const rows = z
|
|
21
|
+
.array(z.object({ mac_address: z.string(), node_address: z.number(), device_key: z.string().regex(/^[0-9a-f]{32}$/i) }))
|
|
22
|
+
.parse(desktopRows(database, 'SELECT mac_address, node_address, device_key FROM fixtures'));
|
|
23
|
+
return {
|
|
24
|
+
...config,
|
|
25
|
+
lights: config.lights.map((light) => {
|
|
26
|
+
const row = rows.find((item) => mac(item.mac_address) === mac(light.mac) && item.node_address === light.address);
|
|
27
|
+
if (!row)
|
|
28
|
+
throw new Error(`No matching Device Key for ${light.name}`);
|
|
29
|
+
return { ...light, deviceKey: row.device_key };
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=desktop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop.js","sourceRoot":"","sources":["../../src/ble/desktop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAa;IACzD,MAAM,MAAM,GAAG,YAAY,CACzB,SAAS,EACT,CAAC,WAAW,EAAE,OAAO,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,oDAAoD,KAAK,EAAE,CAAC,EAC3G;QACE,QAAQ,EAAE,MAAM;QAChB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;QAC3B,OAAO,EAAE,MAAM;KAChB,CACF,CAAC;IACF,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,GAAG,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAEtF,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,MAAkB;IACpE,MAAM,MAAM,GAAG,CAAC;SACb,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;SAC7D,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,mCAAmC,CAAC,CAAC,CAAC;IACrE,IACE,CAAC,MAAM,CAAC,IAAI,CACV,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE;QAC1D,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAC7D;QAED,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,CAAC;SACX,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC,CACjH;SACA,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,4DAA4D,CAAC,CAAC,CAAC;IAC9F,OAAO;QACL,GAAG,MAAM;QACT,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAClC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC;YACjH,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACtE,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QACjD,CAAC,CAAC;KACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { desktopRows } from './desktop.js';
|
|
3
|
+
import { type LibraryImport, SavedStateSchema } from './library.js';
|
|
4
|
+
import type { MeshConfig, MeshLight } from './storage.js';
|
|
5
|
+
export declare function desktopEffectState(light: MeshLight, type: number, value: unknown, updated: number): z.infer<typeof SavedStateSchema>;
|
|
6
|
+
export declare function desktopState(light: MeshLight, mode: number, value: unknown, updated: number): z.infer<typeof SavedStateSchema>;
|
|
7
|
+
export declare function planDesktopImport(database: string, config: MeshConfig, prefix?: string, read?: typeof desktopRows): {
|
|
8
|
+
plan: LibraryImport;
|
|
9
|
+
errors: string[];
|
|
10
|
+
warnings: string[];
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=desktopLibrary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktopLibrary.d.ts","sourceRoot":"","sources":["../../src/ble/desktopLibrary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAoH1D,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,GACd,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAyClC;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,GACd,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAsBlC;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,SAAY,EAAE,IAAI,qBAAc;;;;EA0J7G"}
|