amaran-light-cli 1.0.2
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/LICENSE +22 -0
- package/README.md +374 -0
- package/dist/__tests__/autoCct.test.d.ts +2 -0
- package/dist/__tests__/autoCct.test.d.ts.map +1 -0
- package/dist/__tests__/autoCct.test.js +65 -0
- package/dist/__tests__/autoCct.test.js.map +1 -0
- package/dist/__tests__/cctUtil.test.d.ts +2 -0
- package/dist/__tests__/cctUtil.test.d.ts.map +1 -0
- package/dist/__tests__/cctUtil.test.js +195 -0
- package/dist/__tests__/cctUtil.test.js.map +1 -0
- package/dist/__tests__/cli.smoke.test.d.ts +2 -0
- package/dist/__tests__/cli.smoke.test.d.ts.map +1 -0
- package/dist/__tests__/cli.smoke.test.js +18 -0
- package/dist/__tests__/cli.smoke.test.js.map +1 -0
- package/dist/__tests__/intensityApi.test.d.ts +2 -0
- package/dist/__tests__/intensityApi.test.d.ts.map +1 -0
- package/dist/__tests__/intensityApi.test.js +105 -0
- package/dist/__tests__/intensityApi.test.js.map +1 -0
- package/dist/__tests__/lightControl.test.d.ts +2 -0
- package/dist/__tests__/lightControl.test.d.ts.map +1 -0
- package/dist/__tests__/lightControl.test.js +185 -0
- package/dist/__tests__/lightControl.test.js.map +1 -0
- package/dist/cctUtil.d.ts +26 -0
- package/dist/cctUtil.d.ts.map +1 -0
- package/dist/cctUtil.js +93 -0
- package/dist/cctUtil.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +274 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/autoCct.d.ts +5 -0
- package/dist/commands/autoCct.d.ts.map +1 -0
- package/dist/commands/autoCct.js +270 -0
- package/dist/commands/autoCct.js.map +1 -0
- package/dist/commands/cct.d.ts +5 -0
- package/dist/commands/cct.d.ts.map +1 -0
- package/dist/commands/cct.js +68 -0
- package/dist/commands/cct.js.map +1 -0
- package/dist/commands/color.d.ts +5 -0
- package/dist/commands/color.d.ts.map +1 -0
- package/dist/commands/color.js +63 -0
- package/dist/commands/color.js.map +1 -0
- package/dist/commands/discover.d.ts +5 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +30 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/hsi.d.ts +5 -0
- package/dist/commands/hsi.d.ts.map +1 -0
- package/dist/commands/hsi.js +65 -0
- package/dist/commands/hsi.js.map +1 -0
- package/dist/commands/intensity.d.ts +5 -0
- package/dist/commands/intensity.d.ts.map +1 -0
- package/dist/commands/intensity.js +56 -0
- package/dist/commands/intensity.js.map +1 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +36 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/power.d.ts +5 -0
- package/dist/commands/power.d.ts.map +1 -0
- package/dist/commands/power.js +126 -0
- package/dist/commands/power.js.map +1 -0
- package/dist/commands/schedule.d.ts +5 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +188 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/service.d.ts +5 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +350 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +54 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands.d.ts +5 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +31 -0
- package/dist/commands.js.map +1 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +130 -0
- package/dist/discovery.js.map +1 -0
- package/dist/geoipUtil.d.ts +8 -0
- package/dist/geoipUtil.d.ts.map +1 -0
- package/dist/geoipUtil.js +26 -0
- package/dist/geoipUtil.js.map +1 -0
- package/dist/lightControl.d.ts +114 -0
- package/dist/lightControl.d.ts.map +1 -0
- package/dist/lightControl.js +385 -0
- package/dist/lightControl.js.map +1 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +75 -0
- package/scripts/create-launchd-service.sh +114 -0
- package/scripts/run-auto-cct.sh +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 S. Zachariah Sprackett <zac@sprackett.com>
|
|
4
|
+
Copyright (c) 2025 Mahyar McDonald <github@hmmfn.com>
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Amaran Light CLI
|
|
2
|
+
|
|
3
|
+
A command line tool for controlling Aputure Amaran lights via WebSocket connection to the Amaran Desktop application. Not an offical Amaran command line tool.
|
|
4
|
+
|
|
5
|
+
Also has a circadian lighting command called `auto-cct` that will set the CCT & intensity according to the time of day it is currently at your location, and a service that will run the command every minute to automate it.
|
|
6
|
+
|
|
7
|
+
Written with AI mostly, including this documentation. Thanks to [Zac for his core websocket gist that enabled this](https://gist.github.com/zsprackett/29334b9be1e2bd90c1737bd0ba0eaf5c).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
1. Clone this repository or download the files
|
|
12
|
+
2. Install dependencies:
|
|
13
|
+
```bash
|
|
14
|
+
npm install
|
|
15
|
+
```
|
|
16
|
+
3. Build the project:
|
|
17
|
+
```bash
|
|
18
|
+
npm run build
|
|
19
|
+
```
|
|
20
|
+
4. Optionally, install globally:
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
- Aputure Amaran Desktop application must be running
|
|
28
|
+
- WebSocket server should be accessible (default: ws://localhost:60124)
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
Configure the WebSocket URL and other settings:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Set WebSocket URL
|
|
36
|
+
amaran-cli config -u ws://localhost:60124
|
|
37
|
+
|
|
38
|
+
# Set client ID
|
|
39
|
+
amaran-cli config -c my-cli-client
|
|
40
|
+
|
|
41
|
+
# Enable debug mode
|
|
42
|
+
amaran-cli config -d
|
|
43
|
+
|
|
44
|
+
# Set default location for auto-cct (overrides geoip)
|
|
45
|
+
amaran-cli config --lat 40.7128 --lon -74.0060
|
|
46
|
+
|
|
47
|
+
# Show current configuration
|
|
48
|
+
amaran-cli config --show
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Configuration is stored in `~/.amaran-cli.json`.
|
|
52
|
+
|
|
53
|
+
## Discovery
|
|
54
|
+
|
|
55
|
+
The CLI can automatically discover the WebSocket endpoint from a running Amaran Desktop application:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Discover and save WebSocket URL
|
|
59
|
+
amaran-cli discover
|
|
60
|
+
|
|
61
|
+
# Discover with debug output
|
|
62
|
+
amaran-cli discover -d
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This command uses `lsof` to find the WebSocket port that the Amaran Desktop application is listening on, then saves it to your configuration file for future use.
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
### List all available lights
|
|
70
|
+
```bash
|
|
71
|
+
amaran-cli list
|
|
72
|
+
# or
|
|
73
|
+
amaran-cli ls
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Control lights
|
|
77
|
+
```bash
|
|
78
|
+
# Turn light on
|
|
79
|
+
amaran-cli on "Light Name"
|
|
80
|
+
amaran-cli on node_id_123
|
|
81
|
+
|
|
82
|
+
# Turn on ALL lights
|
|
83
|
+
amaran-cli on
|
|
84
|
+
|
|
85
|
+
# Turn light off
|
|
86
|
+
amaran-cli off "Light Name"
|
|
87
|
+
|
|
88
|
+
# Turn off ALL lights
|
|
89
|
+
amaran-cli off
|
|
90
|
+
|
|
91
|
+
# Toggle light on/off
|
|
92
|
+
amaran-cli toggle "Light Name"
|
|
93
|
+
|
|
94
|
+
# Toggle ALL lights
|
|
95
|
+
amaran-cli toggle
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Set light properties
|
|
99
|
+
```bash
|
|
100
|
+
# Set intensity (0-100%)
|
|
101
|
+
amaran-cli intensity 75 "Light Name"
|
|
102
|
+
|
|
103
|
+
# Set intensity for ALL lights
|
|
104
|
+
amaran-cli intensity 75
|
|
105
|
+
|
|
106
|
+
# Set color temperature (2000-6500K)
|
|
107
|
+
amaran-cli cct 5600 "Light Name"
|
|
108
|
+
|
|
109
|
+
# Set CCT for ALL lights
|
|
110
|
+
amaran-cli cct 5600
|
|
111
|
+
|
|
112
|
+
# Set color temperature with intensity
|
|
113
|
+
amaran-cli cct 3200 "Light Name" -i 80
|
|
114
|
+
|
|
115
|
+
# Set CCT with intensity for ALL lights
|
|
116
|
+
amaran-cli cct 3200 -i 80
|
|
117
|
+
|
|
118
|
+
# Set HSI color
|
|
119
|
+
amaran-cli hsi 240 100 75 "Light Name" # Blue at full saturation, 75% intensity
|
|
120
|
+
|
|
121
|
+
# Set HSI for ALL lights
|
|
122
|
+
amaran-cli hsi 240 100 75
|
|
123
|
+
|
|
124
|
+
# Set color by name or hex
|
|
125
|
+
amaran-cli color red "Light Name"
|
|
126
|
+
amaran-cli color "#ff0000" "Light Name"
|
|
127
|
+
amaran-cli color blue "Light Name" -i 50
|
|
128
|
+
|
|
129
|
+
# Set color for ALL lights
|
|
130
|
+
amaran-cli color red
|
|
131
|
+
amaran-cli color "#ff0000"
|
|
132
|
+
amaran-cli color blue -i 50
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Note:** For all commands that support device names, you can omit the device name or use `all` to apply the command to all lights. Commands are throttled with 250ms delay between each device to prevent overwhelming the server.
|
|
136
|
+
|
|
137
|
+
### Auto CCT (Circadian Lighting)
|
|
138
|
+
```bash
|
|
139
|
+
# Set CCT for all lights based on current time and location (auto-detected via geoip)
|
|
140
|
+
amaran-cli auto-cct
|
|
141
|
+
|
|
142
|
+
# Override IP address for geolocation lookup
|
|
143
|
+
amaran-cli auto-cct --ip 8.8.8.8
|
|
144
|
+
|
|
145
|
+
# Manual latitude and longitude
|
|
146
|
+
amaran-cli auto-cct --lat 40.7128 --lon -74.0060
|
|
147
|
+
|
|
148
|
+
# Manual time (ISO 8601 format)
|
|
149
|
+
amaran-cli auto-cct --time 2025-10-26T14:30:00
|
|
150
|
+
|
|
151
|
+
# Combine manual location and time
|
|
152
|
+
amaran-cli auto-cct --lat 40.7128 --lon -74.0060 --time 2025-10-26T06:00:00
|
|
153
|
+
|
|
154
|
+
# Use with debug mode to see calculated values
|
|
155
|
+
amaran-cli auto-cct -d
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The `auto-cct` command automatically adjusts color temperature based on sunrise/sunset times for your location. By default, it maps the circadian curve to:
|
|
159
|
+
- **Before sunrise / After sunset**: 2000K at 5% intensity (warm, dim night lighting)
|
|
160
|
+
- **Solar noon**: 6500K at 100% intensity (cool, bright daylight)
|
|
161
|
+
- **Between sunrise and sunset**: Smooth bell curve transition from 2000K/5% → 6500K/100% → 2000K/5%
|
|
162
|
+
|
|
163
|
+
This mimics natural daylight changes throughout the day for more comfortable, circadian-friendly lighting. Both color temperature and brightness follow the same curve, providing natural dimming at dawn/dusk.
|
|
164
|
+
|
|
165
|
+
**Location Priority:**
|
|
166
|
+
1. Command-line `--lat` and `--lon` arguments (highest priority)
|
|
167
|
+
2. Config file defaults (set with `amaran-cli config --lat <lat> --lon <lon>`)
|
|
168
|
+
3. GeoIP lookup based on public IP address (fallback)
|
|
169
|
+
|
|
170
|
+
**Bounds (optional):**
|
|
171
|
+
You can constrain the auto-cct curve to your preferred ranges via config:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# CCT bounds in Kelvin (defaults 2000–6500)
|
|
175
|
+
amaran-cli config --cct-min 2500 --cct-max 6000
|
|
176
|
+
|
|
177
|
+
# Intensity bounds in percent (defaults 5–100)
|
|
178
|
+
amaran-cli config --intensity-min 10 --intensity-max 80
|
|
179
|
+
|
|
180
|
+
# Show the saved configuration
|
|
181
|
+
amaran-cli config --show
|
|
182
|
+
```
|
|
183
|
+
If a bound is not set, the default is used. When both min and max are set, min must be <= max.
|
|
184
|
+
|
|
185
|
+
### Running Auto-CCT as a Circadian Lighting Service
|
|
186
|
+
|
|
187
|
+
You can set up auto-cct to run automatically every minute as a circadian lighting background service. The service works with both global and local installations:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# For global installation (recommended for production use)
|
|
191
|
+
npm install -g .
|
|
192
|
+
|
|
193
|
+
# Install and start the circadian lighting service (runs every 60 seconds by default)
|
|
194
|
+
amaran-cli circadian-service install
|
|
195
|
+
|
|
196
|
+
# Install with custom interval (minimum 10 seconds)
|
|
197
|
+
amaran-cli circadian-service install --interval 120
|
|
198
|
+
|
|
199
|
+
# Check service status
|
|
200
|
+
amaran-cli circadian-service status
|
|
201
|
+
|
|
202
|
+
# View logs
|
|
203
|
+
amaran-cli circadian-service logs
|
|
204
|
+
|
|
205
|
+
# Follow logs in real-time
|
|
206
|
+
amaran-cli circadian-service logs -f
|
|
207
|
+
|
|
208
|
+
# View error logs
|
|
209
|
+
amaran-cli circadian-service logs -e
|
|
210
|
+
|
|
211
|
+
# Stop the service temporarily
|
|
212
|
+
amaran-cli circadian-service stop
|
|
213
|
+
|
|
214
|
+
# Start the service
|
|
215
|
+
amaran-cli circadian-service start
|
|
216
|
+
|
|
217
|
+
# Uninstall the service completely
|
|
218
|
+
amaran-cli circadian-service uninstall
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Backward Compatibility:**
|
|
222
|
+
The `service` command is still available as an alias to `circadian-service`:
|
|
223
|
+
```bash
|
|
224
|
+
# These commands work the same way
|
|
225
|
+
amaran-cli service install
|
|
226
|
+
amaran-cli circadian-service install
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Installation Detection:**
|
|
230
|
+
- The service automatically detects if amaran-cli is installed globally (via `npm install -g`) or running from a local development build
|
|
231
|
+
- Global installations run the executable directly for better performance
|
|
232
|
+
- Local development builds use Node.js to run the built JavaScript files
|
|
233
|
+
|
|
234
|
+
The service uses macOS launchd and will automatically start on login. Logs are stored in `~/Library/Logs/amaran-circadian-service.log`.
|
|
235
|
+
|
|
236
|
+
**Alternative: Using Cron**
|
|
237
|
+
|
|
238
|
+
If you prefer using cron instead of launchd, the included script handles both global and local installations:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Make the script executable
|
|
242
|
+
chmod +x scripts/run-auto-cct.sh
|
|
243
|
+
|
|
244
|
+
# Add to crontab (runs every minute)
|
|
245
|
+
crontab -e
|
|
246
|
+
# Add this line:
|
|
247
|
+
# * * * * * /absolute/path/to/amaran-cli/scripts/run-auto-cct.sh
|
|
248
|
+
|
|
249
|
+
# View logs
|
|
250
|
+
tail -f ~/.amaran-circadian-service.log
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Manual launchd Setup:**
|
|
254
|
+
|
|
255
|
+
For advanced users who want to create the service manually:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
chmod +x scripts/create-launchd-service.sh
|
|
259
|
+
./scripts/create-launchd-service.sh
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Preview Auto-CCT Schedule
|
|
263
|
+
|
|
264
|
+
View how color temperature and intensity will change throughout the day:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# Preview schedule for today at your current location
|
|
268
|
+
amaran-cli schedule
|
|
269
|
+
|
|
270
|
+
# Preview with manual location
|
|
271
|
+
amaran-cli schedule --lat 40.7128 --lon -74.0060
|
|
272
|
+
|
|
273
|
+
# Preview for a specific date
|
|
274
|
+
amaran-cli schedule --date 2025-12-21
|
|
275
|
+
|
|
276
|
+
# Change time interval (default: 30 minutes)
|
|
277
|
+
amaran-cli schedule --interval 15
|
|
278
|
+
|
|
279
|
+
# Combine options
|
|
280
|
+
amaran-cli schedule --lat 51.5074 --lon -0.1278 --date 2025-06-21 --interval 60
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The schedule shows CCT and intensity values from 30 minutes before sunrise to 30 minutes after sunset, with special highlighting for:
|
|
284
|
+
- **Sunrise** (yellow)
|
|
285
|
+
- **Solar Noon** (green, bold)
|
|
286
|
+
- **Sunset** (magenta)
|
|
287
|
+
|
|
288
|
+
This helps you visualize and plan your automated lighting schedule before implementing it.
|
|
289
|
+
|
|
290
|
+
Note: The schedule respects any configured bounds set via `amaran-cli config --cct-min/--cct-max` and `--intensity-min/--intensity-max`. If no bounds are set, it uses the default curve from `calculateCCT`.
|
|
291
|
+
|
|
292
|
+
### Get light status
|
|
293
|
+
```bash
|
|
294
|
+
amaran-cli status "Light Name"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Global options
|
|
298
|
+
All commands support these options:
|
|
299
|
+
- `-u, --url <url>`: Override WebSocket URL
|
|
300
|
+
- `-c, --client-id <id>`: Override client ID
|
|
301
|
+
- `-d, --debug`: Enable debug mode
|
|
302
|
+
|
|
303
|
+
Example:
|
|
304
|
+
```bash
|
|
305
|
+
amaran-cli list -u ws://192.168.1.100:60124 -d
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Examples
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# Configure for remote Amaran Desktop
|
|
312
|
+
amaran-cli config -u ws://192.168.1.100:60124
|
|
313
|
+
|
|
314
|
+
# List all lights
|
|
315
|
+
amaran-cli list
|
|
316
|
+
|
|
317
|
+
# Turn on all lights
|
|
318
|
+
amaran-cli on
|
|
319
|
+
|
|
320
|
+
# Turn on the main key light
|
|
321
|
+
amaran-cli on "Key Light"
|
|
322
|
+
|
|
323
|
+
# Set all lights to 5600K at 80% intensity
|
|
324
|
+
amaran-cli cct 5600 -i 80
|
|
325
|
+
|
|
326
|
+
# Set key light to 5600K at 80% intensity
|
|
327
|
+
amaran-cli cct 5600 "Key Light" -i 80
|
|
328
|
+
|
|
329
|
+
# Set all lights to warm white at 60%
|
|
330
|
+
amaran-cli cct 3200 -i 60
|
|
331
|
+
|
|
332
|
+
# Set background light to blue at 40%
|
|
333
|
+
amaran-cli color blue "Background" -i 40
|
|
334
|
+
|
|
335
|
+
# Set all lights to 50% intensity
|
|
336
|
+
amaran-cli intensity 50
|
|
337
|
+
|
|
338
|
+
# Check status of key light
|
|
339
|
+
amaran-cli status "Key Light"
|
|
340
|
+
|
|
341
|
+
# Turn off all lights
|
|
342
|
+
amaran-cli off
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Development
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# Run in development mode
|
|
349
|
+
npm run dev
|
|
350
|
+
|
|
351
|
+
# Build the project
|
|
352
|
+
npm run build
|
|
353
|
+
|
|
354
|
+
# Clean build artifacts
|
|
355
|
+
npm run clean
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Device Identification
|
|
359
|
+
|
|
360
|
+
Devices can be identified by:
|
|
361
|
+
- **Device name**: Case-insensitive partial matching (e.g., "key" matches "Key Light")
|
|
362
|
+
- **Node ID**: Exact match of the device's node_id
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
## License
|
|
366
|
+
|
|
367
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
368
|
+
|
|
369
|
+
## Credits
|
|
370
|
+
|
|
371
|
+
- Original WebSocket control implementation by S. Zachariah Sprackett
|
|
372
|
+
- CLI, auto-CCT, and additional features by Mahyar McDonald
|
|
373
|
+
- Built for use with Aputure Amaran lights and the Amaran Desktop application. This is not an official Amaran app and is not built by them. This was built as a quick hobby utility.
|
|
374
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoCct.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/autoCct.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const globals_1 = require("@jest/globals");
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const commands_1 = __importDefault(require("../commands"));
|
|
9
|
+
globals_1.jest.mock('../geoipUtil', () => ({
|
|
10
|
+
getLocationFromIP: globals_1.jest.fn(() => ({ ll: [37.7749, -122.4194] })),
|
|
11
|
+
}));
|
|
12
|
+
globals_1.jest.mock('../cctUtil', () => ({
|
|
13
|
+
calculateCCT: globals_1.jest.fn(() => ({ cct: 5600, intensity: 500 })),
|
|
14
|
+
}));
|
|
15
|
+
describe('auto-cct command', () => {
|
|
16
|
+
const originalFetch = global.fetch;
|
|
17
|
+
const mockFetch = globals_1.jest.fn();
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
mockFetch.mockImplementation(async () => ({
|
|
20
|
+
json: async () => ({ ip: '1.2.3.4' }),
|
|
21
|
+
}));
|
|
22
|
+
global.fetch = mockFetch;
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
mockFetch.mockClear();
|
|
26
|
+
});
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
global.fetch = originalFetch;
|
|
29
|
+
});
|
|
30
|
+
test('skips sleeping lights when running auto-cct', async () => {
|
|
31
|
+
const setCCT = globals_1.jest.fn();
|
|
32
|
+
const disconnect = globals_1.jest.fn(async () => Promise.resolve());
|
|
33
|
+
const sleepState = {
|
|
34
|
+
'400J5-F2C008': false,
|
|
35
|
+
'400J5-F2C009': true,
|
|
36
|
+
};
|
|
37
|
+
const controllerStub = {
|
|
38
|
+
getDevices: globals_1.jest.fn(() => [
|
|
39
|
+
{ node_id: '400J5-F2C008', device_name: 'On Light' },
|
|
40
|
+
{ node_id: '400J5-F2C009', device_name: 'Off Light' },
|
|
41
|
+
]),
|
|
42
|
+
getLightSleepStatus: globals_1.jest.fn((nodeId, cb) => {
|
|
43
|
+
setImmediate(() => cb(true, 'ok', { sleep: sleepState[nodeId] }));
|
|
44
|
+
}),
|
|
45
|
+
setCCT,
|
|
46
|
+
disconnect,
|
|
47
|
+
};
|
|
48
|
+
const deps = {
|
|
49
|
+
createController: async () => controllerStub,
|
|
50
|
+
findDevice: (_controller, _deviceQuery) => null,
|
|
51
|
+
asyncCommand: (fn) => (...args) => fn(...args),
|
|
52
|
+
loadConfig: () => ({}),
|
|
53
|
+
saveWsUrl: undefined,
|
|
54
|
+
};
|
|
55
|
+
const program = new commander_1.Command();
|
|
56
|
+
program.exitOverride();
|
|
57
|
+
(0, commands_1.default)(program, deps);
|
|
58
|
+
await program.parseAsync(['node', 'test', 'auto-cct']);
|
|
59
|
+
expect(setCCT).toHaveBeenCalledTimes(1);
|
|
60
|
+
// auto-cct should pass through calculateCCT result
|
|
61
|
+
expect(setCCT).toHaveBeenCalledWith('400J5-F2C008', 5600, 500);
|
|
62
|
+
expect(disconnect).toHaveBeenCalledTimes(1);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=autoCct.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoCct.test.js","sourceRoot":"","sources":["../../src/__tests__/autoCct.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cctUtil.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cctUtil.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const suncalc_1 = require("suncalc");
|
|
4
|
+
const cctUtil_1 = require("../cctUtil");
|
|
5
|
+
describe('calculateCCT', () => {
|
|
6
|
+
const NYC_LAT = 40.7128;
|
|
7
|
+
const NYC_LON = -74.006;
|
|
8
|
+
describe('NYC circadian lighting tests', () => {
|
|
9
|
+
let testDate;
|
|
10
|
+
let sunrise;
|
|
11
|
+
let sunset;
|
|
12
|
+
let solarNoon;
|
|
13
|
+
const MIN_INTENSITY_PCT = 5;
|
|
14
|
+
const MAX_INTENSITY_PCT = 100;
|
|
15
|
+
const MIN_CCT = 2000;
|
|
16
|
+
const MAX_CCT = 6500;
|
|
17
|
+
const MIN_INTENSITY_API = MIN_INTENSITY_PCT * 10; // API format (50)
|
|
18
|
+
const MAX_INTENSITY_API = MAX_INTENSITY_PCT * 10; // API format (1000)
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
// Use a fixed date for consistent test results
|
|
21
|
+
testDate = new Date('2024-06-21T12:00:00Z'); // Summer solstice
|
|
22
|
+
const times = (0, suncalc_1.getTimes)(testDate, NYC_LAT, NYC_LON);
|
|
23
|
+
sunrise = times.sunrise;
|
|
24
|
+
sunset = times.sunset;
|
|
25
|
+
solarNoon = times.solarNoon;
|
|
26
|
+
});
|
|
27
|
+
it('should return minimum CCT and intensity before sunrise', () => {
|
|
28
|
+
const beforeSunrise = new Date(sunrise.getTime() - 60000); // 1 minute before
|
|
29
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, beforeSunrise, {
|
|
30
|
+
intensityMinPct: MIN_INTENSITY_PCT,
|
|
31
|
+
intensityMaxPct: MAX_INTENSITY_PCT,
|
|
32
|
+
cctMinK: MIN_CCT,
|
|
33
|
+
cctMaxK: MAX_CCT,
|
|
34
|
+
});
|
|
35
|
+
expect(result.cct).toBe(MIN_CCT);
|
|
36
|
+
expect(result.intensity).toBe(MIN_INTENSITY_API);
|
|
37
|
+
});
|
|
38
|
+
it('should return minimum CCT and intensity at sunrise edge', () => {
|
|
39
|
+
const atSunrise = new Date(sunrise.getTime());
|
|
40
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, atSunrise, {
|
|
41
|
+
intensityMinPct: MIN_INTENSITY_PCT,
|
|
42
|
+
intensityMaxPct: MAX_INTENSITY_PCT,
|
|
43
|
+
cctMinK: MIN_CCT,
|
|
44
|
+
cctMaxK: MAX_CCT,
|
|
45
|
+
});
|
|
46
|
+
// At the exact sunrise moment, should be at minimum
|
|
47
|
+
expect(result.cct).toBe(MIN_CCT);
|
|
48
|
+
expect(result.intensity).toBe(MIN_INTENSITY_API);
|
|
49
|
+
});
|
|
50
|
+
it('should return maximum CCT and intensity at solar noon', () => {
|
|
51
|
+
const atNoon = new Date(solarNoon.getTime());
|
|
52
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, atNoon, {
|
|
53
|
+
intensityMinPct: MIN_INTENSITY_PCT,
|
|
54
|
+
intensityMaxPct: MAX_INTENSITY_PCT,
|
|
55
|
+
cctMinK: MIN_CCT,
|
|
56
|
+
cctMaxK: MAX_CCT,
|
|
57
|
+
});
|
|
58
|
+
// At solar noon (middle of the day), should be at maximum
|
|
59
|
+
expect(result.cct).toBe(MAX_CCT);
|
|
60
|
+
expect(result.intensity).toBe(MAX_INTENSITY_API);
|
|
61
|
+
});
|
|
62
|
+
it('should return minimum CCT and intensity at sunset edge', () => {
|
|
63
|
+
const atSunset = new Date(sunset.getTime());
|
|
64
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, atSunset, {
|
|
65
|
+
intensityMinPct: MIN_INTENSITY_PCT,
|
|
66
|
+
intensityMaxPct: MAX_INTENSITY_PCT,
|
|
67
|
+
cctMinK: MIN_CCT,
|
|
68
|
+
cctMaxK: MAX_CCT,
|
|
69
|
+
});
|
|
70
|
+
// At the exact sunset moment, should be at minimum
|
|
71
|
+
expect(result.cct).toBe(MIN_CCT);
|
|
72
|
+
expect(result.intensity).toBe(MIN_INTENSITY_API);
|
|
73
|
+
});
|
|
74
|
+
it('should return minimum CCT and intensity after sunset', () => {
|
|
75
|
+
const afterSunset = new Date(sunset.getTime() + 60000); // 1 minute after
|
|
76
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, afterSunset, {
|
|
77
|
+
intensityMinPct: MIN_INTENSITY_PCT,
|
|
78
|
+
intensityMaxPct: MAX_INTENSITY_PCT,
|
|
79
|
+
cctMinK: MIN_CCT,
|
|
80
|
+
cctMaxK: MAX_CCT,
|
|
81
|
+
});
|
|
82
|
+
expect(result.cct).toBe(MIN_CCT);
|
|
83
|
+
expect(result.intensity).toBe(MIN_INTENSITY_API);
|
|
84
|
+
});
|
|
85
|
+
it('should have smooth progression from sunrise to noon', () => {
|
|
86
|
+
const quarterWay = new Date(sunrise.getTime() + (solarNoon.getTime() - sunrise.getTime()) / 4);
|
|
87
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, quarterWay, {
|
|
88
|
+
intensityMinPct: MIN_INTENSITY_PCT,
|
|
89
|
+
intensityMaxPct: MAX_INTENSITY_PCT,
|
|
90
|
+
cctMinK: MIN_CCT,
|
|
91
|
+
cctMaxK: MAX_CCT,
|
|
92
|
+
});
|
|
93
|
+
// Should be between minimum and maximum values
|
|
94
|
+
expect(result.cct).toBeGreaterThan(MIN_CCT);
|
|
95
|
+
expect(result.cct).toBeLessThan(MAX_CCT);
|
|
96
|
+
expect(result.intensity).toBeGreaterThan(MIN_INTENSITY_API);
|
|
97
|
+
expect(result.intensity).toBeLessThan(MAX_INTENSITY_API);
|
|
98
|
+
});
|
|
99
|
+
it('should have smooth progression from noon to sunset', () => {
|
|
100
|
+
const threeQuartersWay = new Date(solarNoon.getTime() + (sunset.getTime() - solarNoon.getTime()) / 2);
|
|
101
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, threeQuartersWay, {
|
|
102
|
+
intensityMinPct: MIN_INTENSITY_PCT,
|
|
103
|
+
intensityMaxPct: MAX_INTENSITY_PCT,
|
|
104
|
+
cctMinK: MIN_CCT,
|
|
105
|
+
cctMaxK: MAX_CCT,
|
|
106
|
+
});
|
|
107
|
+
// Should be between minimum and maximum values
|
|
108
|
+
expect(result.cct).toBeGreaterThan(MIN_CCT);
|
|
109
|
+
expect(result.cct).toBeLessThan(MAX_CCT);
|
|
110
|
+
expect(result.intensity).toBeGreaterThan(MIN_INTENSITY_API);
|
|
111
|
+
expect(result.intensity).toBeLessThan(MAX_INTENSITY_API);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('Edge cases', () => {
|
|
115
|
+
it('should handle midnight times', () => {
|
|
116
|
+
const midnight = new Date('2024-06-21T04:00:00Z'); // Midnight in NYC
|
|
117
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, midnight);
|
|
118
|
+
expect(result.cct).toBe(2000);
|
|
119
|
+
expect(result.intensity).toBe(50); // 5% with default
|
|
120
|
+
});
|
|
121
|
+
it('should return valid results for different locations', () => {
|
|
122
|
+
const LONDON_LAT = 51.5074;
|
|
123
|
+
const LONDON_LON = -0.1278;
|
|
124
|
+
const testDate = new Date('2024-06-21T12:00:00Z');
|
|
125
|
+
const result = (0, cctUtil_1.calculateCCT)(LONDON_LAT, LONDON_LON, testDate);
|
|
126
|
+
expect(result.cct).toBeGreaterThanOrEqual(2000);
|
|
127
|
+
expect(result.cct).toBeLessThanOrEqual(6500);
|
|
128
|
+
expect(result.intensity).toBeGreaterThanOrEqual(50); // 5% minimum
|
|
129
|
+
expect(result.intensity).toBeLessThanOrEqual(1000);
|
|
130
|
+
});
|
|
131
|
+
it('should use default date if not provided', () => {
|
|
132
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON);
|
|
133
|
+
expect(result.cct).toBeGreaterThanOrEqual(2000);
|
|
134
|
+
expect(result.cct).toBeLessThanOrEqual(6500);
|
|
135
|
+
expect(result.intensity).toBeGreaterThanOrEqual(50); // 5% minimum
|
|
136
|
+
expect(result.intensity).toBeLessThanOrEqual(1000);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('Return value format', () => {
|
|
140
|
+
it('should return rounded integer values', () => {
|
|
141
|
+
const testDate = new Date('2024-06-21T12:00:00Z');
|
|
142
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, testDate);
|
|
143
|
+
expect(Number.isInteger(result.cct)).toBe(true);
|
|
144
|
+
expect(Number.isInteger(result.intensity)).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
it('should have correct properties', () => {
|
|
147
|
+
const testDate = new Date('2024-06-21T12:00:00Z');
|
|
148
|
+
const result = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, testDate);
|
|
149
|
+
expect(result).toHaveProperty('cct');
|
|
150
|
+
expect(result).toHaveProperty('intensity');
|
|
151
|
+
expect(typeof result.cct).toBe('number');
|
|
152
|
+
expect(typeof result.intensity).toBe('number');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('Intensity ranges', () => {
|
|
156
|
+
const NYC_LAT = 40.7128;
|
|
157
|
+
const NYC_LON = -74.006;
|
|
158
|
+
it('intensity stays within [50, 1000] across a whole day (hourly samples)', () => {
|
|
159
|
+
const base = new Date('2024-06-21T00:00:00Z'); // summer solstice
|
|
160
|
+
for (let hour = 0; hour < 24; hour++) {
|
|
161
|
+
const sample = new Date(base.getTime() + hour * 60 * 60 * 1000);
|
|
162
|
+
const { intensity } = (0, cctUtil_1.calculateCCT)(NYC_LAT, NYC_LON, sample);
|
|
163
|
+
expect(intensity).toBeGreaterThanOrEqual(50); // 5% minimum
|
|
164
|
+
expect(intensity).toBeLessThanOrEqual(1000);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
it('intensity stays within range across seasons and locations (spot samples)', () => {
|
|
168
|
+
const cases = [
|
|
169
|
+
// Dates: solstices and equinoxes
|
|
170
|
+
'2024-03-20T00:00:00Z', // vernal equinox
|
|
171
|
+
'2024-06-21T00:00:00Z', // summer solstice
|
|
172
|
+
'2024-09-22T00:00:00Z', // autumnal equinox
|
|
173
|
+
'2024-12-21T00:00:00Z', // winter solstice
|
|
174
|
+
];
|
|
175
|
+
const locations = [
|
|
176
|
+
{ name: 'NYC', lat: 40.7128, lon: -74.006 },
|
|
177
|
+
{ name: 'London', lat: 51.5074, lon: -0.1278 },
|
|
178
|
+
{ name: 'Sydney', lat: -33.8688, lon: 151.2093 },
|
|
179
|
+
];
|
|
180
|
+
const offsetsHours = [0, 6, 12, 18]; // midnight, morning, noon, evening (UTC)
|
|
181
|
+
for (const iso of cases) {
|
|
182
|
+
const base = new Date(iso);
|
|
183
|
+
for (const { lat, lon } of locations) {
|
|
184
|
+
for (const h of offsetsHours) {
|
|
185
|
+
const d = new Date(base.getTime() + h * 60 * 60 * 1000);
|
|
186
|
+
const { intensity } = (0, cctUtil_1.calculateCCT)(lat, lon, d);
|
|
187
|
+
expect(intensity).toBeGreaterThanOrEqual(50); // 5% minimum
|
|
188
|
+
expect(intensity).toBeLessThanOrEqual(1000);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
//# sourceMappingURL=cctUtil.test.js.map
|