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.
Files changed (121) hide show
  1. package/README.md +87 -0
  2. package/dist/__tests__/autoCctMaxLux.test.d.ts +2 -0
  3. package/dist/__tests__/autoCctMaxLux.test.d.ts.map +1 -0
  4. package/dist/__tests__/autoCctMaxLux.test.js +153 -0
  5. package/dist/__tests__/autoCctMaxLux.test.js.map +1 -0
  6. package/dist/__tests__/cctUtil.test.js +63 -1
  7. package/dist/__tests__/cctUtil.test.js.map +1 -1
  8. package/dist/__tests__/configMaxLux.test.d.ts +2 -0
  9. package/dist/__tests__/configMaxLux.test.d.ts.map +1 -0
  10. package/dist/__tests__/configMaxLux.test.js +43 -0
  11. package/dist/__tests__/configMaxLux.test.js.map +1 -0
  12. package/dist/__tests__/lightControl.test.js +259 -0
  13. package/dist/__tests__/lightControl.test.js.map +1 -1
  14. package/dist/__tests__/maxLuxInterpolation.test.d.ts +2 -0
  15. package/dist/__tests__/maxLuxInterpolation.test.d.ts.map +1 -0
  16. package/dist/__tests__/maxLuxInterpolation.test.js +96 -0
  17. package/dist/__tests__/maxLuxInterpolation.test.js.map +1 -0
  18. package/dist/__tests__/weatherPhysics.test.d.ts +2 -0
  19. package/dist/__tests__/weatherPhysics.test.d.ts.map +1 -0
  20. package/dist/__tests__/weatherPhysics.test.js +72 -0
  21. package/dist/__tests__/weatherPhysics.test.js.map +1 -0
  22. package/dist/commands/__tests__/newCommands.test.d.ts +2 -0
  23. package/dist/commands/__tests__/newCommands.test.d.ts.map +1 -0
  24. package/dist/commands/__tests__/newCommands.test.js +195 -0
  25. package/dist/commands/__tests__/newCommands.test.js.map +1 -0
  26. package/dist/commands/daylightSimulation/autoCct.d.ts.map +1 -1
  27. package/dist/commands/daylightSimulation/autoCct.js +66 -4
  28. package/dist/commands/daylightSimulation/autoCct.js.map +1 -1
  29. package/dist/commands/daylightSimulation/graphSchedule.d.ts.map +1 -1
  30. package/dist/commands/daylightSimulation/graphSchedule.js +7 -2
  31. package/dist/commands/daylightSimulation/graphSchedule.js.map +1 -1
  32. package/dist/commands/daylightSimulation/printSchedule.d.ts.map +1 -1
  33. package/dist/commands/daylightSimulation/printSchedule.js +6 -1
  34. package/dist/commands/daylightSimulation/printSchedule.js.map +1 -1
  35. package/dist/commands/daylightSimulation/simulateSchedule.d.ts.map +1 -1
  36. package/dist/commands/daylightSimulation/simulateSchedule.js +6 -1
  37. package/dist/commands/daylightSimulation/simulateSchedule.js.map +1 -1
  38. package/dist/commands/deviceControl/config.d.ts.map +1 -1
  39. package/dist/commands/deviceControl/config.js +27 -0
  40. package/dist/commands/deviceControl/config.js.map +1 -1
  41. package/dist/commands/deviceControl/effect.d.ts +5 -0
  42. package/dist/commands/deviceControl/effect.d.ts.map +1 -0
  43. package/dist/commands/deviceControl/effect.js +172 -0
  44. package/dist/commands/deviceControl/effect.js.map +1 -0
  45. package/dist/commands/deviceControl/fan.d.ts +5 -0
  46. package/dist/commands/deviceControl/fan.d.ts.map +1 -0
  47. package/dist/commands/deviceControl/fan.js +105 -0
  48. package/dist/commands/deviceControl/fan.js.map +1 -0
  49. package/dist/commands/deviceControl/firmware.d.ts +5 -0
  50. package/dist/commands/deviceControl/firmware.d.ts.map +1 -0
  51. package/dist/commands/deviceControl/firmware.js +68 -0
  52. package/dist/commands/deviceControl/firmware.js.map +1 -0
  53. package/dist/commands/deviceControl/group.d.ts +5 -0
  54. package/dist/commands/deviceControl/group.d.ts.map +1 -0
  55. package/dist/commands/deviceControl/group.js +101 -0
  56. package/dist/commands/deviceControl/group.js.map +1 -0
  57. package/dist/commands/deviceControl/info.d.ts +5 -0
  58. package/dist/commands/deviceControl/info.d.ts.map +1 -0
  59. package/dist/commands/deviceControl/info.js +57 -0
  60. package/dist/commands/deviceControl/info.js.map +1 -0
  61. package/dist/commands/deviceControl/preset.d.ts +5 -0
  62. package/dist/commands/deviceControl/preset.d.ts.map +1 -0
  63. package/dist/commands/deviceControl/preset.js +55 -0
  64. package/dist/commands/deviceControl/preset.js.map +1 -0
  65. package/dist/commands/deviceControl/quickshot.d.ts +5 -0
  66. package/dist/commands/deviceControl/quickshot.d.ts.map +1 -0
  67. package/dist/commands/deviceControl/quickshot.js +51 -0
  68. package/dist/commands/deviceControl/quickshot.js.map +1 -0
  69. package/dist/commands/deviceControl/scene.d.ts +5 -0
  70. package/dist/commands/deviceControl/scene.d.ts.map +1 -0
  71. package/dist/commands/deviceControl/scene.js +101 -0
  72. package/dist/commands/deviceControl/scene.js.map +1 -0
  73. package/dist/commands.d.ts.map +1 -1
  74. package/dist/commands.js +17 -0
  75. package/dist/commands.js.map +1 -1
  76. package/dist/daylightSimulation/cctUtil.d.ts +2 -2
  77. package/dist/daylightSimulation/cctUtil.d.ts.map +1 -1
  78. package/dist/daylightSimulation/cctUtil.js +98 -28
  79. package/dist/daylightSimulation/cctUtil.js.map +1 -1
  80. package/dist/daylightSimulation/constants.d.ts +3 -1
  81. package/dist/daylightSimulation/constants.d.ts.map +1 -1
  82. package/dist/daylightSimulation/constants.js +3 -1
  83. package/dist/daylightSimulation/constants.js.map +1 -1
  84. package/dist/daylightSimulation/curves/realistic.d.ts +7 -6
  85. package/dist/daylightSimulation/curves/realistic.d.ts.map +1 -1
  86. package/dist/daylightSimulation/curves/realistic.js +42 -18
  87. package/dist/daylightSimulation/curves/realistic.js.map +1 -1
  88. package/dist/daylightSimulation/graphSchedule.d.ts +1 -1
  89. package/dist/daylightSimulation/graphSchedule.d.ts.map +1 -1
  90. package/dist/daylightSimulation/graphSchedule.js +27 -5
  91. package/dist/daylightSimulation/graphSchedule.js.map +1 -1
  92. package/dist/daylightSimulation/mathUtil.d.ts +13 -0
  93. package/dist/daylightSimulation/mathUtil.d.ts.map +1 -0
  94. package/dist/daylightSimulation/mathUtil.js +54 -0
  95. package/dist/daylightSimulation/mathUtil.js.map +1 -0
  96. package/dist/daylightSimulation/scheduleMaker.d.ts +4 -5
  97. package/dist/daylightSimulation/scheduleMaker.d.ts.map +1 -1
  98. package/dist/daylightSimulation/scheduleMaker.js +8 -0
  99. package/dist/daylightSimulation/scheduleMaker.js.map +1 -1
  100. package/dist/daylightSimulation/textSchedule.d.ts.map +1 -1
  101. package/dist/daylightSimulation/textSchedule.js +2 -2
  102. package/dist/daylightSimulation/textSchedule.js.map +1 -1
  103. package/dist/daylightSimulation/types.d.ts +6 -0
  104. package/dist/daylightSimulation/types.d.ts.map +1 -1
  105. package/dist/daylightSimulation/types.js.map +1 -1
  106. package/dist/deviceControl/discovery.d.ts.map +1 -1
  107. package/dist/deviceControl/discovery.js +0 -2
  108. package/dist/deviceControl/discovery.js.map +1 -1
  109. package/dist/deviceControl/lightControl.d.ts +67 -78
  110. package/dist/deviceControl/lightControl.d.ts.map +1 -1
  111. package/dist/deviceControl/lightControl.js +292 -203
  112. package/dist/deviceControl/lightControl.js.map +1 -1
  113. package/dist/deviceControl/types.d.ts +19 -0
  114. package/dist/deviceControl/types.d.ts.map +1 -1
  115. package/dist/test/MockLightServer.d.ts.map +1 -1
  116. package/dist/test/MockLightServer.js +134 -12
  117. package/dist/test/MockLightServer.js.map +1 -1
  118. package/package.json +8 -7
  119. package/scripts/extract-api.ts +158 -0
  120. package/scripts/extract-usage.ts +199 -0
  121. 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
- * Check if a node_id looks like a light device (not a group or other entity).
140
- * Light node_ids typically follow pattern like '400J5-F2C008' (alphanumeric with dash).
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
- * Sleep utility for throttling commands
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
- ws;
155
- clientId = 'unknown_client';
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
- // Public getter for WebSocket to allow external error handling
231
- getWebSocket() {
232
- return this.ws;
232
+ getDeviceInfo(nodeId, callback) {
233
+ this.sendCommand(nodeId, 'get_device_info', {}, callback);
233
234
  }
234
- setClientId(clientId) {
235
- this.clientId = clientId;
235
+ getFirmwareVersion(nodeId, callback) {
236
+ this.sendCommand(nodeId, 'get_firmware_version', {}, callback);
236
237
  }
237
- onConnectionOpen() {
238
- this.getDeviceList();
238
+ checkForUpdates(nodeId, callback) {
239
+ this.sendCommand(nodeId, 'check_for_updates', {}, callback);
239
240
  }
240
- sendCommand(nodeId, type, args, callback) {
241
- if (this.ws.readyState === WebSocket.OPEN) {
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
- handleDeviceList(data) {
262
- this.deviceList = data.data;
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
- handleSceneList(data) {
267
- this.sceneList = data.data;
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
- handleNodeConfig(data) {
272
- const nodeId = data.node_id;
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
- getNodeConfigs() {
285
- this.deviceList.forEach((device) => {
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
- getDeviceList(callback) {
292
- this.sendCommand(undefined, 'get_device_list', {}, callback);
256
+ updateScene(sceneId, name, callback) {
257
+ this.sendCommand(undefined, 'update_scene', { id: sceneId, name }, callback);
293
258
  }
294
- getSceneList(callback) {
295
- this.sendCommand(undefined, 'get_scene_list', {}, callback);
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, 'increment_intensity', { delta }, callback);
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, 'increment_cct', args, callback);
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
- // Getters
381
- getDevices() {
382
- return this.deviceList;
383
- }
384
- getScenes() {
385
- return this.sceneList;
386
- }
387
- getNode(nodeId) {
388
- return this.nodeConfigs.get(nodeId);
389
- }
390
- log(...args) {
391
- if (this.debug) {
392
- console.log(...args);
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