tsvesync 1.3.8 → 1.3.10

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.
@@ -30,10 +30,9 @@ export declare class VeSyncAirBaseV2 extends VeSyncAirBypass {
30
30
  action: string;
31
31
  } | null);
32
32
  /**
33
- * Check response for Vital series devices
34
- * These devices return success at API level but may have inner errors
33
+ * Check response for bypassV2 purifiers that surface nested result codes.
35
34
  */
36
- protected checkVitalResponse(response: any, status: number, method: string): boolean;
35
+ protected checkV2Response(response: any, status: number, method: string): boolean;
37
36
  /**
38
37
  * Get device details
39
38
  */
@@ -43,24 +42,23 @@ export declare class VeSyncAirBaseV2 extends VeSyncAirBypass {
43
42
  */
44
43
  protected buildPurifierDict(devDict: Record<string, any>): void;
45
44
  /**
46
- * Check if the current device is a Vital series model
47
- * @returns true if this is a LAP-V102S or LAP-V201S device
45
+ * Determine whether this purifier requires the powerSwitch payload and nested response handling.
48
46
  */
49
- private isVitalSeries;
47
+ private requiresPowerSwitchPayload;
50
48
  /**
51
- * Override turn on method to handle Vital series special cases
49
+ * Override turn on to use the powerSwitch payload required by bypassV2 purifiers.
52
50
  */
53
51
  turnOn(): Promise<boolean>;
54
52
  /**
55
- * Override turn off method to handle Vital series special cases
53
+ * Override turn off to use the powerSwitch payload required by bypassV2 purifiers.
56
54
  */
57
55
  turnOff(): Promise<boolean>;
58
56
  /**
59
- * Override setMode method to handle Vital series special cases
57
+ * Override setMode to send workMode payloads for bypassV2 purifiers.
60
58
  */
61
59
  setMode(mode: string): Promise<boolean>;
62
60
  /**
63
- * Override changeFanSpeed method to handle Vital series special cases
61
+ * Override changeFanSpeed to use manualSpeedLevel for bypassV2 purifiers.
64
62
  */
65
63
  changeFanSpeed(speed: number): Promise<boolean>;
66
64
  /**
@@ -46,26 +46,21 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
46
46
  this._timer = value;
47
47
  }
48
48
  /**
49
- * Check response for Vital series devices
50
- * These devices return success at API level but may have inner errors
49
+ * Check response for bypassV2 purifiers that surface nested result codes.
51
50
  */
52
- checkVitalResponse(response, status, method) {
53
- // First check basic response
51
+ checkV2Response(response, status, method) {
52
+ // First check basic response structure
54
53
  if (!this.checkResponse([response, status], method)) {
55
54
  return false;
56
55
  }
57
- // Check if this is a Vital series device
58
- const isVitalSeries = this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
59
- if (!isVitalSeries) {
60
- return true; // Not a Vital series device, standard check is enough
56
+ // Only apply extended handling to bypassV2 purifiers (Vital/Everest)
57
+ if (!this.requiresPowerSwitchPayload()) {
58
+ return true;
61
59
  }
62
- // For Vital series, we need to check the inner result code
60
+ // These devices frequently succeed with a nested non-zero code
63
61
  if (response.result && response.result.code !== undefined && response.result.code !== 0) {
64
- // Vital series often returns code -1 but still succeeds
65
- // Let's update the state optimistically
66
- logger_1.logger.debug(`Vital series device ${this.deviceName} returned inner code ${response.result.code} for ${method}`);
67
- logger_1.logger.debug('This is normal for Vital series devices - proceeding with operation');
68
- // Keep the device online despite inner error
62
+ logger_1.logger.debug(`${this.deviceName} returned inner code ${response.result.code} for ${method}`);
63
+ logger_1.logger.debug('BypassV2 purifier reports non-zero inner code but request still succeeded');
69
64
  this.connectionStatus = 'online';
70
65
  return true;
71
66
  }
@@ -79,22 +74,20 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
79
74
  logger_1.logger.debug(`Getting details for device: ${this.deviceName}`);
80
75
  const [head, body] = this.buildApiDict('getPurifierStatus');
81
76
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
82
- // Check if this is a Vital series device (LAP-V102S or LAP-V201S)
83
- const isVitalSeries = this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
77
+ const needsV2Handling = this.requiresPowerSwitchPayload();
84
78
  // First check the outer response
85
79
  if (!response || status !== 200) {
86
80
  logger_1.logger.debug(`Error getting purifier details for ${this.deviceName}: Invalid response`);
87
81
  this.connectionStatus = 'offline';
88
82
  return false;
89
83
  }
90
- // For Vital series, we need special handling as they return inner code -1 for details
91
- // but still provide valid status information
92
- if (isVitalSeries && response.result) {
84
+ // For bypassV2 purifier families the API nests the result and may surface non-zero codes
85
+ if (needsV2Handling && response.result) {
93
86
  // Check if we have a valid getPurifierStatus response
94
87
  if (response.code === 0 && ((_a = response.result) === null || _a === void 0 ? void 0 : _a.result)) {
95
88
  const statusResponse = response.result.result;
96
89
  // If we have valid status data, use it regardless of inner result code
97
- logger_1.logger.debug(`Using status data for Vital series device: ${this.deviceName}`);
90
+ logger_1.logger.debug(`Using nested status data for bypassV2 purifier: ${this.deviceName}`);
98
91
  this.buildPurifierDict(statusResponse);
99
92
  if (statusResponse.configuration) {
100
93
  this.buildConfigDict(statusResponse.configuration);
@@ -104,7 +97,7 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
104
97
  return true;
105
98
  }
106
99
  }
107
- // Standard processing for non-Vital devices or when no status data is available
100
+ // Standard processing for non-bypassV2 devices or when no status data is available
108
101
  if (!this.checkResponse([response, status], 'getDetails') || !(response === null || response === void 0 ? void 0 : response.result)) {
109
102
  logger_1.logger.debug('Error getting purifier details');
110
103
  this.connectionStatus = 'offline';
@@ -133,7 +126,9 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
133
126
  this.enabled = powerSwitch;
134
127
  this.deviceStatus = powerSwitch ? 'on' : 'off';
135
128
  this.mode = (_b = devDict.workMode) !== null && _b !== void 0 ? _b : 'manual';
136
- this.speed = (_c = devDict.fanSpeedLevel) !== null && _c !== void 0 ? _c : 0;
129
+ const reportedFanSpeed = (_c = devDict.fanSpeedLevel) !== null && _c !== void 0 ? _c : 0;
130
+ const isAutoSentinel = reportedFanSpeed === 255;
131
+ this.speed = isAutoSentinel ? 0 : reportedFanSpeed;
137
132
  this.setSpeedLevel = (_d = devDict.manualSpeedLevel) !== null && _d !== void 0 ? _d : 1;
138
133
  // Parse filter life with proper fallback handling and logging
139
134
  let filterLife = 0;
@@ -159,7 +154,11 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
159
154
  light_detection_switch: Boolean((_g = devDict.lightDetectionSwitch) !== null && _g !== void 0 ? _g : 0),
160
155
  environment_light_state: Boolean((_h = devDict.environmentLightState) !== null && _h !== void 0 ? _h : 0),
161
156
  screen_switch: Boolean((_j = devDict.screenSwitch) !== null && _j !== void 0 ? _j : 0),
162
- screenStatus: Boolean((_k = devDict.screenSwitch) !== null && _k !== void 0 ? _k : 0) ? 'on' : 'off'
157
+ screenStatus: Boolean((_k = devDict.screenSwitch) !== null && _k !== void 0 ? _k : 0) ? 'on' : 'off',
158
+ manualSpeedLevel: this.setSpeedLevel,
159
+ mode: this.mode,
160
+ speed: this.speed,
161
+ isAutoSentinel
163
162
  };
164
163
  if (this.hasFeature('air_quality')) {
165
164
  this.details.air_quality_value = (_l = devDict.PM25) !== null && _l !== void 0 ? _l : 0;
@@ -186,29 +185,28 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
186
185
  }
187
186
  }
188
187
  /**
189
- * Check if the current device is a Vital series model
190
- * @returns true if this is a LAP-V102S or LAP-V201S device
188
+ * Determine whether this purifier requires the powerSwitch payload and nested response handling.
191
189
  */
192
- isVitalSeries() {
193
- return this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
190
+ requiresPowerSwitchPayload() {
191
+ const type = (this.deviceType || '').toUpperCase();
192
+ return type.startsWith('LAP-V') || type.startsWith('LAP-EL');
194
193
  }
195
194
  /**
196
- * Override turn on method to handle Vital series special cases
195
+ * Override turn on to use the powerSwitch payload required by bypassV2 purifiers.
197
196
  */
198
197
  async turnOn() {
199
198
  logger_1.logger.info(`Turning on device: ${this.deviceName}`);
200
199
  const [head, body] = this.buildApiDict('setSwitch');
201
- // For Vital series, use powerSwitch per YAML spec
202
- // For other models, use the parent class implementation
203
- if (this.isVitalSeries()) {
204
- logger_1.logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
200
+ // BypassV2 purifiers require powerSwitch toggles; others fall back to the base implementation
201
+ if (this.requiresPowerSwitchPayload()) {
202
+ logger_1.logger.debug(`Using bypassV2 power payload for ${this.deviceName}`);
205
203
  body.payload.data = {
206
204
  powerSwitch: 1,
207
205
  switchIdx: 0
208
206
  };
209
207
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
210
- // Use Vital-specific response checking
211
- const success = this.checkVitalResponse(response, status, 'turnOn');
208
+ // Use bypassV2 response checking
209
+ const success = this.checkV2Response(response, status, 'turnOn');
212
210
  if (success) {
213
211
  this.deviceStatus = 'on';
214
212
  // Force a details update to sync state
@@ -221,27 +219,26 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
221
219
  return success;
222
220
  }
223
221
  else {
224
- // Use parent implementation for non-Vital models
222
+ // Use parent implementation for non-bypassV2 models
225
223
  return super.turnOn();
226
224
  }
227
225
  }
228
226
  /**
229
- * Override turn off method to handle Vital series special cases
227
+ * Override turn off to use the powerSwitch payload required by bypassV2 purifiers.
230
228
  */
231
229
  async turnOff() {
232
230
  logger_1.logger.info(`Turning off device: ${this.deviceName}`);
233
231
  const [head, body] = this.buildApiDict('setSwitch');
234
- // For Vital series, use powerSwitch per YAML spec
235
- // For other models, use the parent class implementation
236
- if (this.isVitalSeries()) {
237
- logger_1.logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
232
+ // BypassV2 purifiers require powerSwitch toggles; others fall back to the base implementation
233
+ if (this.requiresPowerSwitchPayload()) {
234
+ logger_1.logger.debug(`Using bypassV2 power payload for ${this.deviceName}`);
238
235
  body.payload.data = {
239
236
  powerSwitch: 0,
240
237
  switchIdx: 0
241
238
  };
242
239
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
243
- // Use Vital-specific response checking
244
- const success = this.checkVitalResponse(response, status, 'turnOff');
240
+ // Use bypassV2 response checking
241
+ const success = this.checkV2Response(response, status, 'turnOff');
245
242
  if (success) {
246
243
  this.deviceStatus = 'off';
247
244
  // Force a details update to sync state
@@ -254,12 +251,12 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
254
251
  return success;
255
252
  }
256
253
  else {
257
- // Use parent implementation for non-Vital models
254
+ // Use parent implementation for non-bypassV2 models
258
255
  return super.turnOff();
259
256
  }
260
257
  }
261
258
  /**
262
- * Override setMode method to handle Vital series special cases
259
+ * Override setMode to send workMode payloads for bypassV2 purifiers.
263
260
  */
264
261
  async setMode(mode) {
265
262
  if (!this.modes.includes(mode)) {
@@ -272,16 +269,15 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
272
269
  logger_1.logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
273
270
  return this.setMode('manual');
274
271
  }
275
- // For Vital series, use special response handling
276
- // For other models, use the parent implementation
277
- if (this.isVitalSeries()) {
278
- logger_1.logger.debug(`Using Vital series specific response handling for ${this.deviceName}`);
272
+ // BypassV2 purifiers need the workMode payload; other models use the base implementation
273
+ if (this.requiresPowerSwitchPayload()) {
274
+ logger_1.logger.debug(`Using bypassV2 mode payload for ${this.deviceName}`);
279
275
  const [head, body] = this.buildApiDict('setPurifierMode');
280
276
  body.payload.data = {
281
277
  workMode: mode
282
278
  };
283
279
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
284
- const success = this.checkVitalResponse(response, status, 'setMode');
280
+ const success = this.checkV2Response(response, status, 'setMode');
285
281
  if (success) {
286
282
  this.details.mode = mode;
287
283
  this._mode = mode;
@@ -299,12 +295,12 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
299
295
  }
300
296
  }
301
297
  else {
302
- // Use parent implementation for non-Vital models
298
+ // Use parent implementation for non-bypassV2 models
303
299
  return super.setMode(mode);
304
300
  }
305
301
  }
306
302
  /**
307
- * Override changeFanSpeed method to handle Vital series special cases
303
+ * Override changeFanSpeed to use manualSpeedLevel for bypassV2 purifiers.
308
304
  */
309
305
  async changeFanSpeed(speed) {
310
306
  var _a;
@@ -315,17 +311,17 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
315
311
  }
316
312
  logger_1.logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
317
313
  const [head, body] = this.buildApiDict('setLevel');
318
- // Specific handling for Vital series devices
319
- if (this.isVitalSeries()) {
320
- logger_1.logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
321
- // For Vital series, use levelIdx, levelType, and manualSpeedLevel per YAML spec
314
+ // Specific handling for bypassV2 devices
315
+ if (this.requiresPowerSwitchPayload()) {
316
+ logger_1.logger.debug(`Using bypassV2 speed payload for ${this.deviceName}`);
317
+ // For these devices, use levelIdx, levelType, and manualSpeedLevel per YAML spec
322
318
  body.payload.data = {
323
319
  levelIdx: 0,
324
320
  levelType: 'wind',
325
321
  manualSpeedLevel: speed
326
322
  };
327
323
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
328
- const success = this.checkVitalResponse(response, status, 'changeFanSpeed');
324
+ const success = this.checkV2Response(response, status, 'changeFanSpeed');
329
325
  if (success) {
330
326
  this.speed = speed;
331
327
  this.details.manualSpeedLevel = speed;
@@ -343,7 +339,7 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
343
339
  }
344
340
  }
345
341
  else {
346
- // For non-Vital models, use id, level, mode, and type
342
+ // For non-bypassV2 models, use id, level, mode, and type
347
343
  body.payload.data = {
348
344
  id: 0,
349
345
  level: speed,
@@ -378,14 +374,14 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
378
374
  };
379
375
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
380
376
  // Use the appropriate response checking method based on device type
381
- const success = this.isVitalSeries()
382
- ? this.checkVitalResponse(response, status, 'setAutoPreference')
377
+ const success = this.requiresPowerSwitchPayload()
378
+ ? this.checkV2Response(response, status, 'setAutoPreference')
383
379
  : this.checkResponse([response, status], 'setAutoPreference');
384
380
  if (success) {
385
381
  this.details.auto_preference = preference;
386
382
  this.details.auto_preference_type = preference;
387
- // Force a details update to sync state for Vital series
388
- if (this.isVitalSeries()) {
383
+ // Force a details update to sync state for bypassV2 purifiers
384
+ if (this.requiresPowerSwitchPayload()) {
389
385
  setTimeout(() => {
390
386
  this.getDetails().catch(e => {
391
387
  logger_1.logger.debug(`Background state refresh failed: ${e}`);
@@ -405,13 +401,13 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
405
401
  };
406
402
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
407
403
  // Use the appropriate response checking method based on device type
408
- const success = this.isVitalSeries()
409
- ? this.checkVitalResponse(response, status, 'setLightDetection')
404
+ const success = this.requiresPowerSwitchPayload()
405
+ ? this.checkV2Response(response, status, 'setLightDetection')
410
406
  : this.checkResponse([response, status], 'setLightDetection');
411
407
  if (success) {
412
408
  this.details.light_detection_switch = enabled;
413
- // Force a details update to sync state for Vital series
414
- if (this.isVitalSeries()) {
409
+ // Force a details update to sync state for bypassV2 purifiers
410
+ if (this.requiresPowerSwitchPayload()) {
415
411
  setTimeout(() => {
416
412
  this.getDetails().catch(e => {
417
413
  logger_1.logger.debug(`Background state refresh failed: ${e}`);
@@ -486,8 +482,8 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
486
482
  };
487
483
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
488
484
  // Use the appropriate response checking method based on device type
489
- const success = this.isVitalSeries()
490
- ? this.checkVitalResponse(response, status, 'setOscillation')
485
+ const success = this.requiresPowerSwitchPayload()
486
+ ? this.checkV2Response(response, status, 'setOscillation')
491
487
  : this.checkResponse([response, status], 'setOscillation');
492
488
  if (success) {
493
489
  this.details.oscillationState = toggle;
@@ -3,6 +3,11 @@
3
3
  */
4
4
  import { VeSyncBaseDevice } from './vesyncBaseDevice';
5
5
  import { VeSync } from './vesync';
6
+ interface RgbValues {
7
+ red: number;
8
+ green: number;
9
+ blue: number;
10
+ }
6
11
  interface BulbConfig {
7
12
  [key: string]: {
8
13
  module: string;
@@ -22,12 +27,11 @@ export declare abstract class VeSyncBulb extends VeSyncBaseDevice {
22
27
  protected colorSaturation: number;
23
28
  protected colorMode: string;
24
29
  protected features: string[];
25
- protected rgbValues: {
26
- red: number;
27
- green: number;
28
- blue: number;
29
- };
30
+ protected rgbValues: RgbValues;
30
31
  constructor(details: Record<string, any>, manager: VeSync);
32
+ hasFeature(feature: string): boolean;
33
+ getColorModel(): string;
34
+ getBrightness(): number;
31
35
  /**
32
36
  * Get bulb details
33
37
  */
@@ -51,7 +55,6 @@ export declare abstract class VeSyncBulb extends VeSyncBaseDevice {
51
55
  /**
52
56
  * Get bulb brightness
53
57
  */
54
- getBrightness(): number;
55
58
  /**
56
59
  * Get color temperature in Kelvin
57
60
  */
@@ -80,9 +83,23 @@ export declare abstract class VeSyncBulb extends VeSyncBaseDevice {
80
83
  green: number;
81
84
  blue: number;
82
85
  };
86
+ /**
87
+ * Set color temperature wrapper to maintain compatibility with consumers
88
+ */
89
+ setColorTemperature(colorTemp: number): Promise<boolean>;
90
+ /**
91
+ * Set color via hue/saturation/value when supported
92
+ */
93
+ setColor(hue: number, saturation: number, value?: number): Promise<boolean>;
94
+ /**
95
+ * Process standard bypass (v1) detail responses shared by multiple bulbs
96
+ */
97
+ protected processStandardBypassDetails(response: any, statusCode: number): boolean;
83
98
  /**
84
99
  * Set color temperature - Abstract method to be implemented by subclasses
85
100
  */
86
101
  abstract setColorTemp(colorTemp: number): Promise<boolean>;
102
+ protected static clamp(value: number, min: number, max: number): number;
103
+ protected static hsvToRgb(h: number, s: number, v: number): RgbValues;
87
104
  }
88
105
  export {};
@@ -50,6 +50,16 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
50
50
  blue: 0
51
51
  };
52
52
  }
53
+ hasFeature(feature) {
54
+ return this.features.includes(feature);
55
+ }
56
+ getColorModel() {
57
+ var _a, _b;
58
+ return (_b = (_a = exports.bulbConfig[this.deviceType]) === null || _a === void 0 ? void 0 : _a.colorModel) !== null && _b !== void 0 ? _b : 'none';
59
+ }
60
+ getBrightness() {
61
+ return this.brightness;
62
+ }
53
63
  /**
54
64
  * Get bulb details
55
65
  */
@@ -75,30 +85,7 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
75
85
  }
76
86
  };
77
87
  const [response, statusCode] = await this.callApi(endpoint, 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
78
- const success = this.checkResponse([response, statusCode], 'getDetails');
79
- if (success && (response === null || response === void 0 ? void 0 : response.result)) {
80
- this.deviceStatus = response.result.enabled ? 'on' : 'off';
81
- this.brightness = response.result.brightness || this.brightness;
82
- if (this.features.includes('color_temp')) {
83
- this.colorTemp = response.result.colorTemp || this.colorTemp;
84
- }
85
- if (this.features.includes('rgb_shift')) {
86
- if (exports.bulbConfig[this.deviceType].colorModel === 'rgb') {
87
- this.rgbValues = {
88
- red: response.result.red || this.rgbValues.red,
89
- green: response.result.green || this.rgbValues.green,
90
- blue: response.result.blue || this.rgbValues.blue
91
- };
92
- }
93
- else {
94
- this.colorHue = response.result.hue || this.colorHue;
95
- this.colorSaturation = response.result.saturation || this.colorSaturation;
96
- this.colorValue = response.result.value || this.colorValue;
97
- }
98
- }
99
- logger_1.logger.debug(`[${this.deviceName}] Successfully retrieved bulb details`);
100
- }
101
- return success;
88
+ return this.processStandardBypassDetails(response, statusCode);
102
89
  }
103
90
  /**
104
91
  * Update bulb details
@@ -193,7 +180,8 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
193
180
  logger_1.logger.error(`[${this.deviceName}] Dimming not supported`);
194
181
  return false;
195
182
  }
196
- logger_1.logger.debug(`[${this.deviceName}] Setting brightness to ${brightness}`);
183
+ const brightnessLevel = VeSyncBulb.clamp(Math.round(brightness), 0, 100);
184
+ logger_1.logger.debug(`[${this.deviceName}] Setting brightness to ${brightnessLevel}`);
197
185
  const isV2Device = this.deviceType === 'XYD0001';
198
186
  const endpoint = isV2Device ? '/cloud/v2/deviceManaged/bypassV2' : '/cloud/v1/deviceManaged/bypass';
199
187
  const body = isV2Device ? {
@@ -202,7 +190,7 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
202
190
  configModule: this.configModule,
203
191
  payload: {
204
192
  data: {
205
- brightness: brightness,
193
+ brightness: brightnessLevel,
206
194
  colorMode: '',
207
195
  colorTemp: '',
208
196
  force: 0,
@@ -219,14 +207,14 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
219
207
  configModule: this.configModule,
220
208
  jsonCmd: {
221
209
  light: {
222
- brightness: brightness
210
+ brightness: brightnessLevel
223
211
  }
224
212
  }
225
213
  };
226
214
  const [response] = await this.callApi(endpoint, 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
227
215
  if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
228
- this.brightness = brightness;
229
- logger_1.logger.info(`[${this.deviceName}] Successfully set brightness to ${brightness}`);
216
+ this.brightness = brightnessLevel;
217
+ logger_1.logger.info(`[${this.deviceName}] Successfully set brightness to ${brightnessLevel}`);
230
218
  return true;
231
219
  }
232
220
  logger_1.logger.error(`[${this.deviceName}] Failed to set brightness: ${JSON.stringify(response)}`);
@@ -235,9 +223,6 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
235
223
  /**
236
224
  * Get bulb brightness
237
225
  */
238
- getBrightness() {
239
- return this.brightness;
240
- }
241
226
  /**
242
227
  * Get color temperature in Kelvin
243
228
  */
@@ -292,5 +277,132 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
292
277
  }
293
278
  return this.rgbValues;
294
279
  }
280
+ /**
281
+ * Set color temperature wrapper to maintain compatibility with consumers
282
+ */
283
+ async setColorTemperature(colorTemp) {
284
+ return this.setColorTemp(colorTemp);
285
+ }
286
+ /**
287
+ * Set color via hue/saturation/value when supported
288
+ */
289
+ async setColor(hue, saturation, value = 100) {
290
+ var _a;
291
+ if (!this.features.includes('rgb_shift')) {
292
+ logger_1.logger.error(`[${this.deviceName}] Color control not supported`);
293
+ return false;
294
+ }
295
+ const colorModel = (_a = exports.bulbConfig[this.deviceType]) === null || _a === void 0 ? void 0 : _a.colorModel;
296
+ if (colorModel === 'rgb' && typeof this.setRgb === 'function') {
297
+ const rgb = VeSyncBulb.hsvToRgb(hue, saturation, value);
298
+ return this.setRgb(rgb.red, rgb.green, rgb.blue);
299
+ }
300
+ if (typeof this.setHsv === 'function') {
301
+ return this.setHsv(hue, saturation, value);
302
+ }
303
+ logger_1.logger.error(`[${this.deviceName}] No compatible color handler found`);
304
+ return false;
305
+ }
306
+ /**
307
+ * Process standard bypass (v1) detail responses shared by multiple bulbs
308
+ */
309
+ processStandardBypassDetails(response, statusCode) {
310
+ var _a, _b, _c, _d, _e, _f, _g, _h;
311
+ const success = this.checkResponse([response, statusCode], 'getDetails');
312
+ if (success && (response === null || response === void 0 ? void 0 : response.result)) {
313
+ const innerResult = ((_a = response.result) === null || _a === void 0 ? void 0 : _a.result) || response.result;
314
+ if (innerResult) {
315
+ if (typeof innerResult.enabled === 'boolean') {
316
+ this.deviceStatus = innerResult.enabled ? 'on' : 'off';
317
+ }
318
+ else if (innerResult.action) {
319
+ this.deviceStatus = innerResult.action === 'on' ? 'on' : 'off';
320
+ }
321
+ if (innerResult.connectionStatus) {
322
+ this.connectionStatus = innerResult.connectionStatus;
323
+ }
324
+ if (this.features.includes('dimmable') && innerResult.brightness !== undefined) {
325
+ this.brightness = Number(innerResult.brightness);
326
+ }
327
+ if (this.features.includes('color_temp')) {
328
+ if (innerResult.colorTemp !== undefined) {
329
+ this.colorTemp = Number(innerResult.colorTemp);
330
+ }
331
+ else if (innerResult.colorTempe !== undefined) {
332
+ this.colorTemp = Number(innerResult.colorTempe);
333
+ }
334
+ }
335
+ if (this.features.includes('rgb_shift')) {
336
+ if (((_b = exports.bulbConfig[this.deviceType]) === null || _b === void 0 ? void 0 : _b.colorModel) === 'rgb') {
337
+ this.rgbValues = {
338
+ red: Number((_c = innerResult.red) !== null && _c !== void 0 ? _c : this.rgbValues.red),
339
+ green: Number((_d = innerResult.green) !== null && _d !== void 0 ? _d : this.rgbValues.green),
340
+ blue: Number((_e = innerResult.blue) !== null && _e !== void 0 ? _e : this.rgbValues.blue)
341
+ };
342
+ }
343
+ else {
344
+ this.colorHue = Number((_f = innerResult.hue) !== null && _f !== void 0 ? _f : this.colorHue);
345
+ this.colorSaturation = Number((_g = innerResult.saturation) !== null && _g !== void 0 ? _g : this.colorSaturation);
346
+ this.colorValue = Number((_h = innerResult.value) !== null && _h !== void 0 ? _h : this.colorValue);
347
+ }
348
+ }
349
+ }
350
+ logger_1.logger.debug(`[${this.deviceName}] Successfully retrieved bulb details`);
351
+ }
352
+ return success;
353
+ }
354
+ static clamp(value, min, max) {
355
+ return Math.min(Math.max(value, min), max);
356
+ }
357
+ static hsvToRgb(h, s, v) {
358
+ const saturation = VeSyncBulb.clamp(s, 0, 100) / 100;
359
+ const value = VeSyncBulb.clamp(v, 0, 100) / 100;
360
+ const hue = ((h % 360) + 360) % 360 / 60;
361
+ const i = Math.floor(hue);
362
+ const f = hue - i;
363
+ const p = value * (1 - saturation);
364
+ const q = value * (1 - saturation * f);
365
+ const t = value * (1 - saturation * (1 - f));
366
+ let r = 0;
367
+ let g = 0;
368
+ let b = 0;
369
+ switch (i) {
370
+ case 0:
371
+ r = value;
372
+ g = t;
373
+ b = p;
374
+ break;
375
+ case 1:
376
+ r = q;
377
+ g = value;
378
+ b = p;
379
+ break;
380
+ case 2:
381
+ r = p;
382
+ g = value;
383
+ b = t;
384
+ break;
385
+ case 3:
386
+ r = p;
387
+ g = q;
388
+ b = value;
389
+ break;
390
+ case 4:
391
+ r = t;
392
+ g = p;
393
+ b = value;
394
+ break;
395
+ default:
396
+ r = value;
397
+ g = p;
398
+ b = q;
399
+ break;
400
+ }
401
+ return {
402
+ red: Math.round(r * 255),
403
+ green: Math.round(g * 255),
404
+ blue: Math.round(b * 255)
405
+ };
406
+ }
295
407
  }
296
408
  exports.VeSyncBulb = VeSyncBulb;