amaran-light-cli 1.5.0 → 1.7.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 +87 -0
- package/dist/__tests__/autoCctMaxLux.test.d.ts +2 -0
- package/dist/__tests__/autoCctMaxLux.test.d.ts.map +1 -0
- package/dist/__tests__/autoCctMaxLux.test.js +153 -0
- package/dist/__tests__/autoCctMaxLux.test.js.map +1 -0
- package/dist/__tests__/cctUtil.test.js +63 -1
- package/dist/__tests__/cctUtil.test.js.map +1 -1
- package/dist/__tests__/configMaxLux.test.d.ts +2 -0
- package/dist/__tests__/configMaxLux.test.d.ts.map +1 -0
- package/dist/__tests__/configMaxLux.test.js +43 -0
- package/dist/__tests__/configMaxLux.test.js.map +1 -0
- package/dist/__tests__/lightControl.test.js +259 -0
- package/dist/__tests__/lightControl.test.js.map +1 -1
- package/dist/__tests__/maxLuxInterpolation.test.d.ts +2 -0
- package/dist/__tests__/maxLuxInterpolation.test.d.ts.map +1 -0
- package/dist/__tests__/maxLuxInterpolation.test.js +96 -0
- package/dist/__tests__/maxLuxInterpolation.test.js.map +1 -0
- package/dist/__tests__/weatherPhysics.test.d.ts +2 -0
- package/dist/__tests__/weatherPhysics.test.d.ts.map +1 -0
- package/dist/__tests__/weatherPhysics.test.js +72 -0
- package/dist/__tests__/weatherPhysics.test.js.map +1 -0
- package/dist/commands/__tests__/newCommands.test.d.ts +2 -0
- package/dist/commands/__tests__/newCommands.test.d.ts.map +1 -0
- package/dist/commands/__tests__/newCommands.test.js +195 -0
- package/dist/commands/__tests__/newCommands.test.js.map +1 -0
- package/dist/commands/daylightSimulation/autoCct.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/autoCct.js +66 -4
- package/dist/commands/daylightSimulation/autoCct.js.map +1 -1
- package/dist/commands/daylightSimulation/graphSchedule.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/graphSchedule.js +7 -2
- package/dist/commands/daylightSimulation/graphSchedule.js.map +1 -1
- package/dist/commands/daylightSimulation/printSchedule.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/printSchedule.js +6 -1
- package/dist/commands/daylightSimulation/printSchedule.js.map +1 -1
- package/dist/commands/daylightSimulation/simulateSchedule.d.ts.map +1 -1
- package/dist/commands/daylightSimulation/simulateSchedule.js +6 -1
- package/dist/commands/daylightSimulation/simulateSchedule.js.map +1 -1
- package/dist/commands/deviceControl/config.d.ts.map +1 -1
- package/dist/commands/deviceControl/config.js +27 -0
- package/dist/commands/deviceControl/config.js.map +1 -1
- package/dist/commands/deviceControl/effect.d.ts +5 -0
- package/dist/commands/deviceControl/effect.d.ts.map +1 -0
- package/dist/commands/deviceControl/effect.js +172 -0
- package/dist/commands/deviceControl/effect.js.map +1 -0
- package/dist/commands/deviceControl/fan.d.ts +5 -0
- package/dist/commands/deviceControl/fan.d.ts.map +1 -0
- package/dist/commands/deviceControl/fan.js +105 -0
- package/dist/commands/deviceControl/fan.js.map +1 -0
- package/dist/commands/deviceControl/firmware.d.ts +5 -0
- package/dist/commands/deviceControl/firmware.d.ts.map +1 -0
- package/dist/commands/deviceControl/firmware.js +68 -0
- package/dist/commands/deviceControl/firmware.js.map +1 -0
- package/dist/commands/deviceControl/group.d.ts +5 -0
- package/dist/commands/deviceControl/group.d.ts.map +1 -0
- package/dist/commands/deviceControl/group.js +101 -0
- package/dist/commands/deviceControl/group.js.map +1 -0
- package/dist/commands/deviceControl/info.d.ts +5 -0
- package/dist/commands/deviceControl/info.d.ts.map +1 -0
- package/dist/commands/deviceControl/info.js +57 -0
- package/dist/commands/deviceControl/info.js.map +1 -0
- package/dist/commands/deviceControl/preset.d.ts +5 -0
- package/dist/commands/deviceControl/preset.d.ts.map +1 -0
- package/dist/commands/deviceControl/preset.js +55 -0
- package/dist/commands/deviceControl/preset.js.map +1 -0
- package/dist/commands/deviceControl/quickshot.d.ts +5 -0
- package/dist/commands/deviceControl/quickshot.d.ts.map +1 -0
- package/dist/commands/deviceControl/quickshot.js +51 -0
- package/dist/commands/deviceControl/quickshot.js.map +1 -0
- package/dist/commands/deviceControl/scene.d.ts +5 -0
- package/dist/commands/deviceControl/scene.d.ts.map +1 -0
- package/dist/commands/deviceControl/scene.js +101 -0
- package/dist/commands/deviceControl/scene.js.map +1 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +17 -0
- package/dist/commands.js.map +1 -1
- package/dist/daylightSimulation/cctUtil.d.ts +2 -2
- package/dist/daylightSimulation/cctUtil.d.ts.map +1 -1
- package/dist/daylightSimulation/cctUtil.js +98 -28
- package/dist/daylightSimulation/cctUtil.js.map +1 -1
- package/dist/daylightSimulation/constants.d.ts +3 -1
- package/dist/daylightSimulation/constants.d.ts.map +1 -1
- package/dist/daylightSimulation/constants.js +3 -1
- package/dist/daylightSimulation/constants.js.map +1 -1
- package/dist/daylightSimulation/curves/realistic.d.ts +7 -6
- package/dist/daylightSimulation/curves/realistic.d.ts.map +1 -1
- package/dist/daylightSimulation/curves/realistic.js +42 -18
- package/dist/daylightSimulation/curves/realistic.js.map +1 -1
- package/dist/daylightSimulation/graphSchedule.d.ts +1 -1
- package/dist/daylightSimulation/graphSchedule.d.ts.map +1 -1
- package/dist/daylightSimulation/graphSchedule.js +27 -5
- package/dist/daylightSimulation/graphSchedule.js.map +1 -1
- package/dist/daylightSimulation/mathUtil.d.ts +13 -0
- package/dist/daylightSimulation/mathUtil.d.ts.map +1 -0
- package/dist/daylightSimulation/mathUtil.js +54 -0
- package/dist/daylightSimulation/mathUtil.js.map +1 -0
- package/dist/daylightSimulation/scheduleMaker.d.ts +4 -5
- package/dist/daylightSimulation/scheduleMaker.d.ts.map +1 -1
- package/dist/daylightSimulation/scheduleMaker.js +8 -0
- package/dist/daylightSimulation/scheduleMaker.js.map +1 -1
- package/dist/daylightSimulation/textSchedule.d.ts.map +1 -1
- package/dist/daylightSimulation/textSchedule.js +2 -2
- package/dist/daylightSimulation/textSchedule.js.map +1 -1
- package/dist/daylightSimulation/types.d.ts +6 -0
- package/dist/daylightSimulation/types.d.ts.map +1 -1
- package/dist/daylightSimulation/types.js.map +1 -1
- package/dist/deviceControl/discovery.d.ts.map +1 -1
- package/dist/deviceControl/discovery.js +0 -2
- package/dist/deviceControl/discovery.js.map +1 -1
- package/dist/deviceControl/lightControl.d.ts +67 -78
- package/dist/deviceControl/lightControl.d.ts.map +1 -1
- package/dist/deviceControl/lightControl.js +292 -203
- package/dist/deviceControl/lightControl.js.map +1 -1
- package/dist/deviceControl/types.d.ts +19 -0
- package/dist/deviceControl/types.d.ts.map +1 -1
- package/dist/test/MockLightServer.d.ts.map +1 -1
- package/dist/test/MockLightServer.js +134 -12
- package/dist/test/MockLightServer.js.map +1 -1
- package/package.json +8 -7
- package/scripts/extract-api.ts +158 -0
- package/scripts/extract-usage.ts +199 -0
- package/scripts/release.js +9 -9
|
@@ -26,15 +26,135 @@ Downloaded from: https://gist.github.com/zsprackett/29334b9be1e2bd90c1737bd0ba0e
|
|
|
26
26
|
import chalk from 'chalk';
|
|
27
27
|
import WebSocket from 'ws';
|
|
28
28
|
import { DEVICE_DEFAULTS } from './constants.js';
|
|
29
|
+
/**
|
|
30
|
+
* Control Aputure Amaran Lights via websocket to the amaran Desktop application.
|
|
31
|
+
*
|
|
32
|
+
* @class LightController
|
|
33
|
+
*/
|
|
29
34
|
class LightController {
|
|
35
|
+
ws;
|
|
36
|
+
clientId = 'unknown_client';
|
|
37
|
+
deviceList = [];
|
|
38
|
+
sceneList = [];
|
|
39
|
+
nodeConfigs = new Map();
|
|
40
|
+
commandCallbacks = new Map();
|
|
41
|
+
pendingQueue = [];
|
|
42
|
+
onInitializedCallback;
|
|
43
|
+
debug = false;
|
|
44
|
+
constructor(wsUrl, clientId, onInitialized, debug = false) {
|
|
45
|
+
this.ws = new WebSocket(wsUrl);
|
|
46
|
+
if (clientId) {
|
|
47
|
+
this.clientId = clientId;
|
|
48
|
+
}
|
|
49
|
+
this.debug = debug;
|
|
50
|
+
if (onInitialized) {
|
|
51
|
+
this.onInitializedCallback = onInitialized;
|
|
52
|
+
}
|
|
53
|
+
this.ws.on('open', () => {
|
|
54
|
+
this.log('Connected to WebSocket server');
|
|
55
|
+
this.flushPending();
|
|
56
|
+
this.onConnectionOpen();
|
|
57
|
+
});
|
|
58
|
+
this.ws.on('message', (data) => this.handleMessage(data));
|
|
59
|
+
this.ws.on('error', (error) => {
|
|
60
|
+
if (this.debug) {
|
|
61
|
+
console.error('WebSocket error:', error);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const addressMatch = error.message.match(/(\S+:\d+)/);
|
|
65
|
+
const addressPort = addressMatch ? addressMatch[1] : this.ws.url;
|
|
66
|
+
console.error(chalk.red(`WebSocket connection failed to ${addressPort}`));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
this.ws.on('close', () => {
|
|
70
|
+
this.log('Disconnected from WebSocket server');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// --- Initialization & Message Handling ---
|
|
74
|
+
onConnectionOpen() {
|
|
75
|
+
this.getDeviceList();
|
|
76
|
+
}
|
|
77
|
+
handleMessage(data) {
|
|
78
|
+
try {
|
|
79
|
+
const parsedData = JSON.parse(data.toString());
|
|
80
|
+
this.log('Received message from server:', parsedData);
|
|
81
|
+
const action = parsedData.action || parsedData.request?.type;
|
|
82
|
+
if (parsedData.code !== 0) {
|
|
83
|
+
console.error('Error from server:', parsedData.message);
|
|
84
|
+
if (action && this.commandCallbacks.has(action)) {
|
|
85
|
+
this.commandCallbacks.get(action)?.(false, parsedData.message);
|
|
86
|
+
this.commandCallbacks.delete(action);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (action && this.commandCallbacks.has(action)) {
|
|
91
|
+
this.commandCallbacks.get(action)?.(true, parsedData.message, parsedData.data);
|
|
92
|
+
this.commandCallbacks.delete(action);
|
|
93
|
+
}
|
|
94
|
+
if (action) {
|
|
95
|
+
this.processResponseType(action, parsedData.data, parsedData.node_id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('Error parsing message:', error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic data from server needs flexible typing before validation
|
|
103
|
+
processResponseType(type, data, nodeId) {
|
|
104
|
+
switch (type) {
|
|
105
|
+
case 'get_device_list':
|
|
106
|
+
case 'get_fixture_list':
|
|
107
|
+
this.handleDeviceList(data);
|
|
108
|
+
break;
|
|
109
|
+
case 'get_scene_list':
|
|
110
|
+
this.handleSceneList(data);
|
|
111
|
+
break;
|
|
112
|
+
case 'get_node_config':
|
|
113
|
+
this.handleNodeConfig({ node_id: nodeId, data });
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
this.log('Response type processed:', type);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
handleDeviceList(data) {
|
|
120
|
+
this.deviceList = data.data;
|
|
121
|
+
this.log('Device List:', JSON.stringify(this.deviceList, null, 2));
|
|
122
|
+
this.getSceneList();
|
|
123
|
+
}
|
|
124
|
+
handleSceneList(data) {
|
|
125
|
+
this.sceneList = data.data;
|
|
126
|
+
this.log('Scene List:', JSON.stringify(this.sceneList, null, 2));
|
|
127
|
+
this.getNodeConfigs();
|
|
128
|
+
}
|
|
129
|
+
// biome-ignore lint/suspicious/noExplicitAny: Payload can be flat or nested from different server versions
|
|
130
|
+
handleNodeConfig(payload) {
|
|
131
|
+
const nodeId = payload.node_id || payload.data?.node_id;
|
|
132
|
+
if (nodeId) {
|
|
133
|
+
let config = payload.data;
|
|
134
|
+
// If server returned nested data: { node_id, data: { ...config } }
|
|
135
|
+
if (config && typeof config === 'object' && 'data' in config && ('node_id' in config || 'id' in config)) {
|
|
136
|
+
config = config.data;
|
|
137
|
+
}
|
|
138
|
+
this.nodeConfigs.set(nodeId, config);
|
|
139
|
+
this.log('Node Config:', JSON.stringify(config, null, 2));
|
|
140
|
+
if (this.nodeConfigs.size === this.deviceList.length) {
|
|
141
|
+
this.log('All node configurations have been gathered');
|
|
142
|
+
if (this.onInitializedCallback) {
|
|
143
|
+
this.onInitializedCallback();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
getNodeConfigs() {
|
|
149
|
+
this.deviceList.forEach((device) => {
|
|
150
|
+
if (device.node_id) {
|
|
151
|
+
this.getNodeConfig(device.node_id);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// --- Convenience Methods (Applied to All Lights) ---
|
|
30
156
|
/**
|
|
31
157
|
* Apply a command to all light devices with throttling.
|
|
32
|
-
* Only targets devices with node_ids matching the light pattern (e.g., '400J5-F2C008').
|
|
33
|
-
* Throttles commands with ${DEVICE_DEFAULTS.commandThrottleDelay}ms delay between each to avoid overwhelming the server.
|
|
34
|
-
*
|
|
35
|
-
* @param commandFn - Function that takes (nodeId, callback) and executes the command
|
|
36
|
-
* @param commandName - Display name for the command (for logging)
|
|
37
|
-
* @param getDisplayArgs - Optional function to get display arguments for logging
|
|
38
158
|
*/
|
|
39
159
|
async applyToAllLights(commandFn, commandName, getDisplayArgs) {
|
|
40
160
|
try {
|
|
@@ -42,7 +162,6 @@ class LightController {
|
|
|
42
162
|
this.log('No devices found');
|
|
43
163
|
return;
|
|
44
164
|
}
|
|
45
|
-
// Filter for light devices (node_ids that look like light IDs)
|
|
46
165
|
const lightDevices = this.deviceList.filter((device) => device.node_id ? this.isLightNodeId(device.node_id) : false);
|
|
47
166
|
if (lightDevices.length === 0) {
|
|
48
167
|
this.log('No light devices found');
|
|
@@ -50,19 +169,16 @@ class LightController {
|
|
|
50
169
|
}
|
|
51
170
|
this.log(`${commandName} for ${lightDevices.length} light(s)`);
|
|
52
171
|
const waitTimeMs = DEVICE_DEFAULTS.commandThrottleDelay;
|
|
53
|
-
// Send commands with waitTimeMs throttling between each
|
|
54
172
|
for (let i = 0; i < lightDevices.length; i++) {
|
|
55
173
|
const device = lightDevices[i];
|
|
56
174
|
const displayName = device.device_name || device.name || device.node_id || 'Unknown';
|
|
57
175
|
const displayArgs = getDisplayArgs ? getDisplayArgs(device) : '';
|
|
58
|
-
// Only log if we're not in test environment
|
|
59
176
|
if (process.env.NODE_ENV !== 'test') {
|
|
60
177
|
console.log(` ${commandName} ${displayName} (${device.node_id})${displayArgs}`);
|
|
61
178
|
}
|
|
62
179
|
if (device.node_id) {
|
|
63
180
|
commandFn(device.node_id);
|
|
64
181
|
}
|
|
65
|
-
// Wait waitTimeMs before sending next command (skip delay after last one)
|
|
66
182
|
if (i < lightDevices.length - 1) {
|
|
67
183
|
await this.sleep(waitTimeMs);
|
|
68
184
|
}
|
|
@@ -73,226 +189,105 @@ class LightController {
|
|
|
73
189
|
throw error;
|
|
74
190
|
}
|
|
75
191
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Set CCT & Intensity for all lights in deviceList.
|
|
78
|
-
* Only targets devices with node_ids matching the light pattern (e.g., '400J5-F2C008').
|
|
79
|
-
* Throttles commands with ${DEVICE_DEFAULTS.commandThrottleDelay}ms delay between each to avoid overwhelming the server.
|
|
80
|
-
*/
|
|
81
192
|
async setCCTAndIntensityForAllLights(cct, intensity, callback) {
|
|
82
193
|
await this.applyToAllLights((nodeId) => this.setCCT(nodeId, cct, intensity, callback), 'Setting CCT', () => ` to ${cct}K${intensity !== undefined ? ` at ${intensity / 10}%` : ''}`);
|
|
83
194
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Turn on all lights.
|
|
86
|
-
*/
|
|
87
195
|
async turnOnAllLights(callback) {
|
|
88
196
|
await this.applyToAllLights((nodeId) => this.turnLightOn(nodeId, callback), 'Turning on');
|
|
89
197
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Turn off all lights.
|
|
92
|
-
*/
|
|
93
198
|
async turnOffAllLights(callback) {
|
|
94
199
|
await this.applyToAllLights((nodeId) => this.turnLightOff(nodeId, callback), 'Turning off');
|
|
95
200
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Toggle all lights.
|
|
98
|
-
*/
|
|
99
201
|
async toggleAllLights(callback) {
|
|
100
202
|
await this.applyToAllLights((nodeId) => this.toggleLight(nodeId, callback), 'Toggling');
|
|
101
203
|
}
|
|
102
|
-
/**
|
|
103
|
-
* Set intensity for all lights.
|
|
104
|
-
*/
|
|
105
204
|
async setIntensityForAllLights(intensity, callback) {
|
|
106
205
|
await this.applyToAllLights((nodeId) => this.setIntensity(nodeId, intensity, callback), 'Setting intensity', () => ` to ${intensity / 10}%`);
|
|
107
206
|
}
|
|
108
|
-
/**
|
|
109
|
-
* Increment intensity for all lights.
|
|
110
|
-
*/
|
|
111
207
|
async incrementIntensityForAllLights(delta, callback) {
|
|
112
208
|
await this.applyToAllLights((nodeId) => this.incrementIntensity(nodeId, delta, callback), 'Incrementing intensity', () => ` by ${delta > 0 ? '+' : ''}${delta / 10}%`);
|
|
113
209
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Increment CCT for all lights.
|
|
116
|
-
*/
|
|
117
210
|
async incrementCCTForAllLights(delta, intensity, callback) {
|
|
118
211
|
await this.applyToAllLights((nodeId) => this.incrementCCT(nodeId, delta, intensity, callback), 'Incrementing CCT', () => ` by ${delta > 0 ? '+' : ''}${delta}K${intensity !== undefined ? ` at ${intensity / 10}%` : ''}`);
|
|
119
212
|
}
|
|
120
|
-
/**
|
|
121
|
-
* Set HSI color for all lights.
|
|
122
|
-
*/
|
|
123
213
|
async setHSIForAllLights(hue, sat, intensity, cct, gm, callback) {
|
|
124
214
|
await this.applyToAllLights((nodeId) => this.setHSI(nodeId, hue, sat, intensity, cct, gm, callback), 'Setting HSI', () => ` to H:${hue} S:${sat} I:${intensity / 10}%`);
|
|
125
215
|
}
|
|
126
|
-
/**
|
|
127
|
-
* Set color for all lights.
|
|
128
|
-
*/
|
|
129
216
|
async setColorForAllLights(color, intensity, callback) {
|
|
130
217
|
await this.applyToAllLights((nodeId) => this.setColor(nodeId, color, intensity, callback), 'Setting color', () => ` to ${color}${intensity !== undefined ? ` at ${intensity / 10}%` : ''}`);
|
|
131
218
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Set system effect for all lights.
|
|
134
|
-
*/
|
|
135
219
|
async setSystemEffectForAllLights(effectType, intensity, callback) {
|
|
136
220
|
await this.applyToAllLights((nodeId) => this.setSystemEffect(nodeId, effectType, intensity, callback), 'Setting effect', () => ` to ${effectType}${intensity !== undefined ? ` at ${intensity / 10}%` : ''}`);
|
|
137
221
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*/
|
|
142
|
-
isLightNodeId(nodeId) {
|
|
143
|
-
// Pattern: contains at least one dash and alphanumeric characters
|
|
144
|
-
// Excludes things like 'all', 'group1', etc.
|
|
145
|
-
const lightPattern = /^[A-Z0-9]+-[A-Z0-9]+$/i;
|
|
146
|
-
return lightPattern.test(nodeId);
|
|
222
|
+
// --- Individual Light Control Methods ---
|
|
223
|
+
getProtocolVersions(callback) {
|
|
224
|
+
this.sendCommand(undefined, 'get_protocol_versions', {}, callback);
|
|
147
225
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
*/
|
|
151
|
-
sleep(ms) {
|
|
152
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
226
|
+
getFixtureList(callback) {
|
|
227
|
+
this.sendCommand(undefined, 'get_fixture_list', {}, callback);
|
|
153
228
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
deviceList = [];
|
|
157
|
-
sceneList = [];
|
|
158
|
-
nodeConfigs = new Map();
|
|
159
|
-
debug;
|
|
160
|
-
onInitializedCallback;
|
|
161
|
-
commandCallbacks = new Map();
|
|
162
|
-
pendingQueue = [];
|
|
163
|
-
constructor(wsUrl, clientId, onInitialized, debug = false) {
|
|
164
|
-
this.ws = new WebSocket(wsUrl);
|
|
165
|
-
if (clientId) {
|
|
166
|
-
this.clientId = clientId;
|
|
167
|
-
}
|
|
168
|
-
this.debug = debug;
|
|
169
|
-
if (onInitialized) {
|
|
170
|
-
this.onInitializedCallback = onInitialized;
|
|
171
|
-
}
|
|
172
|
-
this.ws.on('open', () => {
|
|
173
|
-
this.log('Connected to WebSocket server');
|
|
174
|
-
// Flush any queued commands that were issued before the socket was open
|
|
175
|
-
this.flushPending();
|
|
176
|
-
this.onConnectionOpen();
|
|
177
|
-
});
|
|
178
|
-
this.ws.on('message', (data) => {
|
|
179
|
-
try {
|
|
180
|
-
const parsedData = JSON.parse(data.toString());
|
|
181
|
-
this.log('Received message from server:', parsedData);
|
|
182
|
-
const requestId = parsedData.request?.type;
|
|
183
|
-
if (parsedData.code !== 0) {
|
|
184
|
-
console.error('Error from server:', parsedData.message);
|
|
185
|
-
if (requestId && this.commandCallbacks.has(requestId)) {
|
|
186
|
-
this.commandCallbacks.get(requestId)?.(false, parsedData.message);
|
|
187
|
-
this.commandCallbacks.delete(requestId);
|
|
188
|
-
}
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (requestId && this.commandCallbacks.has(requestId)) {
|
|
192
|
-
this.commandCallbacks.get(requestId)?.(true, parsedData.message, parsedData.data);
|
|
193
|
-
this.commandCallbacks.delete(requestId);
|
|
194
|
-
}
|
|
195
|
-
if (parsedData.request?.type) {
|
|
196
|
-
switch (parsedData.request.type) {
|
|
197
|
-
case 'get_device_list':
|
|
198
|
-
this.handleDeviceList(parsedData.data);
|
|
199
|
-
break;
|
|
200
|
-
case 'get_scene_list':
|
|
201
|
-
this.handleSceneList(parsedData.data);
|
|
202
|
-
break;
|
|
203
|
-
case 'get_node_config':
|
|
204
|
-
this.handleNodeConfig(parsedData.data);
|
|
205
|
-
break;
|
|
206
|
-
default:
|
|
207
|
-
this.log('Unknown response type');
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
console.error('Error parsing message:', error);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
this.ws.on('error', (error) => {
|
|
216
|
-
if (this.debug) {
|
|
217
|
-
console.error('WebSocket error:', error);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
// Extract address and port from the error message for cleaner output
|
|
221
|
-
const addressMatch = error.message.match(/(\S+:\d+)/);
|
|
222
|
-
const addressPort = addressMatch ? addressMatch[1] : this.ws.url;
|
|
223
|
-
console.error(chalk.red(`WebSocket connection failed to ${addressPort}`));
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
this.ws.on('close', () => {
|
|
227
|
-
this.log('Disconnected from WebSocket server');
|
|
228
|
-
});
|
|
229
|
+
getDeviceList(callback) {
|
|
230
|
+
this.sendCommand(undefined, 'get_device_list', {}, callback);
|
|
229
231
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return this.ws;
|
|
232
|
+
getDeviceInfo(nodeId, callback) {
|
|
233
|
+
this.sendCommand(nodeId, 'get_device_info', {}, callback);
|
|
233
234
|
}
|
|
234
|
-
|
|
235
|
-
this.
|
|
235
|
+
getFirmwareVersion(nodeId, callback) {
|
|
236
|
+
this.sendCommand(nodeId, 'get_firmware_version', {}, callback);
|
|
236
237
|
}
|
|
237
|
-
|
|
238
|
-
this.
|
|
238
|
+
checkForUpdates(nodeId, callback) {
|
|
239
|
+
this.sendCommand(nodeId, 'check_for_updates', {}, callback);
|
|
239
240
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const command = {
|
|
243
|
-
version: 1,
|
|
244
|
-
client_id: this.clientId,
|
|
245
|
-
type,
|
|
246
|
-
node_id: nodeId,
|
|
247
|
-
args,
|
|
248
|
-
};
|
|
249
|
-
if (callback) {
|
|
250
|
-
this.commandCallbacks.set(type, callback);
|
|
251
|
-
}
|
|
252
|
-
this.ws.send(JSON.stringify(command));
|
|
253
|
-
this.log(`Sent command: ${type}`);
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
// Queue the command to be sent when the socket opens
|
|
257
|
-
this.pendingQueue.push({ nodeId, type, args, callback });
|
|
258
|
-
this.log(`Queued command (socket not open yet): ${type}`);
|
|
259
|
-
}
|
|
241
|
+
updateFirmware(nodeId, callback) {
|
|
242
|
+
this.sendCommand(nodeId, 'update_firmware', {}, callback);
|
|
260
243
|
}
|
|
261
|
-
|
|
262
|
-
this.
|
|
263
|
-
this.log('Device List:', JSON.stringify(this.deviceList, null, 2));
|
|
264
|
-
this.getSceneList();
|
|
244
|
+
getSceneList(callback) {
|
|
245
|
+
this.sendCommand(undefined, 'get_scene_list', {}, callback);
|
|
265
246
|
}
|
|
266
|
-
|
|
267
|
-
this.
|
|
268
|
-
this.log('Scene List:', JSON.stringify(this.sceneList, null, 2));
|
|
269
|
-
this.getNodeConfigs();
|
|
247
|
+
saveScene(name, callback) {
|
|
248
|
+
this.sendCommand(undefined, 'save_scene', { name }, callback);
|
|
270
249
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (nodeId) {
|
|
274
|
-
this.nodeConfigs.set(nodeId, data.data);
|
|
275
|
-
this.log('Node Config:', JSON.stringify(data.data, null, 2));
|
|
276
|
-
if (this.nodeConfigs.size === this.deviceList.length) {
|
|
277
|
-
this.log('All node configurations have been gathered');
|
|
278
|
-
if (this.onInitializedCallback) {
|
|
279
|
-
this.onInitializedCallback();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
250
|
+
deleteScene(sceneId, callback) {
|
|
251
|
+
this.sendCommand(undefined, 'delete_scene', { id: sceneId }, callback);
|
|
283
252
|
}
|
|
284
|
-
|
|
285
|
-
this.
|
|
286
|
-
if (device.node_id) {
|
|
287
|
-
this.getNodeConfig(device.node_id);
|
|
288
|
-
}
|
|
289
|
-
});
|
|
253
|
+
recallScene(sceneId, callback) {
|
|
254
|
+
this.sendCommand(undefined, 'recall_scene', { id: sceneId }, callback);
|
|
290
255
|
}
|
|
291
|
-
|
|
292
|
-
this.sendCommand(undefined, '
|
|
256
|
+
updateScene(sceneId, name, callback) {
|
|
257
|
+
this.sendCommand(undefined, 'update_scene', { id: sceneId, name }, callback);
|
|
293
258
|
}
|
|
294
|
-
|
|
295
|
-
this.sendCommand(undefined, '
|
|
259
|
+
getPresetList(callback) {
|
|
260
|
+
this.sendCommand(undefined, 'get_preset_list', {}, callback);
|
|
261
|
+
}
|
|
262
|
+
recallPreset(nodeId, presetId, callback) {
|
|
263
|
+
this.sendCommand(nodeId, 'recall_preset', { id: presetId }, callback);
|
|
264
|
+
}
|
|
265
|
+
setPreset(nodeId, presetId, callback) {
|
|
266
|
+
this.sendCommand(nodeId, 'set_preset', { preset_id: presetId }, callback);
|
|
267
|
+
}
|
|
268
|
+
getSystemEffectList(callback) {
|
|
269
|
+
this.sendCommand(undefined, 'get_system_effect_list', {}, callback);
|
|
270
|
+
}
|
|
271
|
+
getQuickshotList(callback) {
|
|
272
|
+
this.sendCommand(undefined, 'get_quickshot_list', {}, callback);
|
|
273
|
+
}
|
|
274
|
+
setQuickshot(quickshotId, callback) {
|
|
275
|
+
this.sendCommand(undefined, 'set_quickshot', { quickshot_id: quickshotId }, callback);
|
|
276
|
+
}
|
|
277
|
+
getGroupList(callback) {
|
|
278
|
+
this.sendCommand(undefined, 'get_group_list', {}, callback);
|
|
279
|
+
}
|
|
280
|
+
createGroup(name, callback) {
|
|
281
|
+
this.sendCommand(undefined, 'create_group', { name }, callback);
|
|
282
|
+
}
|
|
283
|
+
deleteGroup(groupId, callback) {
|
|
284
|
+
this.sendCommand(undefined, 'delete_group', { id: groupId }, callback);
|
|
285
|
+
}
|
|
286
|
+
addToGroup(groupId, nodeId, callback) {
|
|
287
|
+
this.sendCommand(undefined, 'add_to_group', { group_id: groupId, node_id: nodeId }, callback);
|
|
288
|
+
}
|
|
289
|
+
removeFromGroup(groupId, nodeId, callback) {
|
|
290
|
+
this.sendCommand(undefined, 'remove_from_group', { group_id: groupId, node_id: nodeId }, callback);
|
|
296
291
|
}
|
|
297
292
|
getNodeConfig(nodeId, callback) {
|
|
298
293
|
this.sendCommand(nodeId, 'get_node_config', {}, callback);
|
|
@@ -309,11 +304,17 @@ class LightController {
|
|
|
309
304
|
toggleLight(nodeId, callback) {
|
|
310
305
|
this.sendCommand(nodeId, 'toggle_sleep', undefined, callback);
|
|
311
306
|
}
|
|
307
|
+
getIntensity(nodeId, callback) {
|
|
308
|
+
this.sendCommand(nodeId, 'get_intensity', {}, callback);
|
|
309
|
+
}
|
|
312
310
|
setIntensity(nodeId, intensity, callback) {
|
|
313
311
|
this.sendCommand(nodeId, 'set_intensity', { intensity }, callback);
|
|
314
312
|
}
|
|
315
313
|
incrementIntensity(nodeId, delta, callback) {
|
|
316
|
-
this.sendCommand(nodeId, '
|
|
314
|
+
this.sendCommand(nodeId, 'increase_intensity', { delta }, callback);
|
|
315
|
+
}
|
|
316
|
+
getCCT(nodeId, callback) {
|
|
317
|
+
this.sendCommand(nodeId, 'get_cct', {}, callback);
|
|
317
318
|
}
|
|
318
319
|
setCCT(nodeId, cct, intensity, callback) {
|
|
319
320
|
const args = { cct };
|
|
@@ -327,7 +328,10 @@ class LightController {
|
|
|
327
328
|
if (intensity !== undefined) {
|
|
328
329
|
args.intensity = intensity;
|
|
329
330
|
}
|
|
330
|
-
this.sendCommand(nodeId, '
|
|
331
|
+
this.sendCommand(nodeId, 'increase_cct', args, callback);
|
|
332
|
+
}
|
|
333
|
+
getHSI(nodeId, callback) {
|
|
334
|
+
this.sendCommand(nodeId, 'get_hsi', {}, callback);
|
|
331
335
|
}
|
|
332
336
|
setHSI(nodeId, hue, sat, intensity, cct, gm, callback) {
|
|
333
337
|
const args = { hue, sat, intensity };
|
|
@@ -339,6 +343,26 @@ class LightController {
|
|
|
339
343
|
}
|
|
340
344
|
this.sendCommand(nodeId, 'set_hsi', args, callback);
|
|
341
345
|
}
|
|
346
|
+
getRGB(nodeId, callback) {
|
|
347
|
+
this.sendCommand(nodeId, 'get_rgb', {}, callback);
|
|
348
|
+
}
|
|
349
|
+
setRGB(nodeId, r, g, b, intensity, callback) {
|
|
350
|
+
const args = { r, g, b };
|
|
351
|
+
if (intensity !== undefined) {
|
|
352
|
+
args.intensity = intensity;
|
|
353
|
+
}
|
|
354
|
+
this.sendCommand(nodeId, 'set_rgb', args, callback);
|
|
355
|
+
}
|
|
356
|
+
getXY(nodeId, callback) {
|
|
357
|
+
this.sendCommand(nodeId, 'get_xy', {}, callback);
|
|
358
|
+
}
|
|
359
|
+
setXY(nodeId, x, y, intensity, callback) {
|
|
360
|
+
const args = { x, y };
|
|
361
|
+
if (intensity !== undefined) {
|
|
362
|
+
args.intensity = intensity;
|
|
363
|
+
}
|
|
364
|
+
this.sendCommand(nodeId, 'set_xy', args, callback);
|
|
365
|
+
}
|
|
342
366
|
setColor(nodeId, color, intensity, callback) {
|
|
343
367
|
const args = { color };
|
|
344
368
|
if (intensity !== undefined) {
|
|
@@ -346,6 +370,9 @@ class LightController {
|
|
|
346
370
|
}
|
|
347
371
|
this.sendCommand(nodeId, 'set_color', args, callback);
|
|
348
372
|
}
|
|
373
|
+
getSystemEffect(nodeId, callback) {
|
|
374
|
+
this.sendCommand(nodeId, 'get_system_effect', {}, callback);
|
|
375
|
+
}
|
|
349
376
|
setSystemEffect(nodeId, effectType, intensity, callback) {
|
|
350
377
|
const args = { effect_type: effectType };
|
|
351
378
|
if (intensity !== undefined) {
|
|
@@ -353,6 +380,32 @@ class LightController {
|
|
|
353
380
|
}
|
|
354
381
|
this.sendCommand(nodeId, 'set_system_effect', args, callback);
|
|
355
382
|
}
|
|
383
|
+
getEffect(nodeId, callback) {
|
|
384
|
+
this.sendCommand(nodeId, 'get_effect', {}, callback);
|
|
385
|
+
}
|
|
386
|
+
setEffect(nodeId, effectName, args, callback) {
|
|
387
|
+
const combinedArgs = { name: effectName, ...args };
|
|
388
|
+
this.sendCommand(nodeId, 'set_effect', combinedArgs, callback);
|
|
389
|
+
}
|
|
390
|
+
getFanMode(nodeId, callback) {
|
|
391
|
+
this.sendCommand(nodeId, 'get_fan_mode', {}, callback);
|
|
392
|
+
}
|
|
393
|
+
setFanMode(nodeId, mode, callback) {
|
|
394
|
+
this.sendCommand(nodeId, 'set_fan_mode', { mode }, callback);
|
|
395
|
+
}
|
|
396
|
+
getFanSpeed(nodeId, callback) {
|
|
397
|
+
this.sendCommand(nodeId, 'get_fan_speed', {}, callback);
|
|
398
|
+
}
|
|
399
|
+
setFanSpeed(nodeId, speed, callback) {
|
|
400
|
+
this.sendCommand(nodeId, 'set_fan_speed', { speed }, callback);
|
|
401
|
+
}
|
|
402
|
+
setEffectSpeed(nodeId, speed, callback) {
|
|
403
|
+
this.sendCommand(nodeId, 'set_effect_speed', { speed }, callback);
|
|
404
|
+
}
|
|
405
|
+
setEffectIntensity(nodeId, intensity, callback) {
|
|
406
|
+
this.sendCommand(nodeId, 'set_effect_intensity', { intensity }, callback);
|
|
407
|
+
}
|
|
408
|
+
// --- Utility & Infrastructure ---
|
|
356
409
|
async disconnect() {
|
|
357
410
|
if (this.ws.readyState === WebSocket.OPEN) {
|
|
358
411
|
await this.waitForPendingCommands(5000);
|
|
@@ -377,19 +430,27 @@ class LightController {
|
|
|
377
430
|
checkPendingCommands();
|
|
378
431
|
});
|
|
379
432
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
433
|
+
sendCommand(nodeId, type, args, callback) {
|
|
434
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
435
|
+
const command = {
|
|
436
|
+
version: 1,
|
|
437
|
+
client_id: this.clientId,
|
|
438
|
+
type,
|
|
439
|
+
action: type,
|
|
440
|
+
node_id: nodeId,
|
|
441
|
+
args,
|
|
442
|
+
};
|
|
443
|
+
// biome-ignore lint/suspicious/noExplicitAny: Backward compatibility for mock server
|
|
444
|
+
command.request = { type };
|
|
445
|
+
if (callback) {
|
|
446
|
+
this.commandCallbacks.set(type, callback);
|
|
447
|
+
}
|
|
448
|
+
this.ws.send(JSON.stringify(command));
|
|
449
|
+
this.log(`Sent command: ${type}`);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
this.pendingQueue.push({ nodeId, type, args, callback });
|
|
453
|
+
this.log(`Queued command (socket not open yet): ${type}`);
|
|
393
454
|
}
|
|
394
455
|
}
|
|
395
456
|
flushPending() {
|
|
@@ -401,6 +462,34 @@ class LightController {
|
|
|
401
462
|
this.sendCommand(p.nodeId, p.type, p.args, p.callback);
|
|
402
463
|
}
|
|
403
464
|
}
|
|
465
|
+
isLightNodeId(nodeId) {
|
|
466
|
+
const lightPattern = /^[A-Z0-9]+-[A-Z0-9]+$/i;
|
|
467
|
+
return lightPattern.test(nodeId);
|
|
468
|
+
}
|
|
469
|
+
sleep(ms) {
|
|
470
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
471
|
+
}
|
|
472
|
+
log(...args) {
|
|
473
|
+
if (this.debug) {
|
|
474
|
+
console.log(...args);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Getters & Public Utilities
|
|
478
|
+
getWebSocket() {
|
|
479
|
+
return this.ws;
|
|
480
|
+
}
|
|
481
|
+
setClientId(clientId) {
|
|
482
|
+
this.clientId = clientId;
|
|
483
|
+
}
|
|
484
|
+
getDevices() {
|
|
485
|
+
return this.deviceList;
|
|
486
|
+
}
|
|
487
|
+
getScenes() {
|
|
488
|
+
return this.sceneList;
|
|
489
|
+
}
|
|
490
|
+
getNode(nodeId) {
|
|
491
|
+
return this.nodeConfigs.get(nodeId);
|
|
492
|
+
}
|
|
404
493
|
}
|
|
405
494
|
export default LightController;
|
|
406
495
|
//# sourceMappingURL=lightControl.js.map
|