homebridge-tsvesync 1.0.72 → 1.0.76

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.
@@ -4,16 +4,190 @@ exports.AirPurifierAccessory = void 0;
4
4
  const base_accessory_1 = require("./base.accessory");
5
5
  class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
6
6
  constructor(platform, accessory, device) {
7
- var _a;
8
7
  super(platform, accessory, device);
8
+ this.lastSetSpeed = 0; // Track the last speed we set
9
+ this.lastSetPercentage = 0; // Track the last percentage we set
10
+ this.skipNextUpdate = false; // Flag to skip the next update
9
11
  this.device = device;
10
12
  this.capabilities = this.getDeviceCapabilities();
11
- // Detect if this is an LV-series device (LV-PUR131S or LV-RH131S)
13
+ // Detect device class based on model number patterns
12
14
  const extendedDevice = this.device;
13
- this.isLVSeries = ((_a = extendedDevice.deviceType) === null || _a === void 0 ? void 0 : _a.startsWith('LV-')) || false;
14
- if (this.isLVSeries) {
15
- this.platform.log.debug(`Detected LV-series device: ${this.device.deviceName} (${extendedDevice.deviceType})`);
15
+ const deviceType = extendedDevice.deviceType || '';
16
+ // AirBypass Devices: Core series, LAP-C series, Vital series
17
+ this.isAirBypassDevice = deviceType.includes('CORE') ||
18
+ deviceType.startsWith('LAP-C') ||
19
+ deviceType.includes('VITAL');
20
+ // AirBaseV2 Devices: LAP-V series, LAP-EL series, EverestAir series
21
+ this.isAirBaseV2Device = deviceType.startsWith('LAP-V') ||
22
+ deviceType.startsWith('LAP-EL') ||
23
+ deviceType.includes('EVERESTAIR');
24
+ // Air131 Devices: LV-PUR131S, LV-RH131S
25
+ this.isAir131Device = deviceType.startsWith('LV-');
26
+ // Log device class detection
27
+ if (this.isAirBypassDevice) {
28
+ this.platform.log.debug(`Detected AirBypass device: ${this.device.deviceName} (${deviceType})`);
16
29
  }
30
+ else if (this.isAirBaseV2Device) {
31
+ this.platform.log.debug(`Detected AirBaseV2 device: ${this.device.deviceName} (${deviceType})`);
32
+ }
33
+ else if (this.isAir131Device) {
34
+ this.platform.log.debug(`Detected Air131 device: ${this.device.deviceName} (${deviceType})`);
35
+ }
36
+ else {
37
+ this.platform.log.debug(`Unknown air purifier type: ${this.device.deviceName} (${deviceType})`);
38
+ }
39
+ }
40
+ /**
41
+ * Feature detection system
42
+ */
43
+ hasFeature(feature) {
44
+ const extendedDevice = this.device;
45
+ // Use device's native hasFeature method if available
46
+ if (typeof extendedDevice.hasFeature === 'function') {
47
+ return extendedDevice.hasFeature(feature);
48
+ }
49
+ // Fallback feature detection based on device type
50
+ switch (feature) {
51
+ case 'air_quality':
52
+ // Explicitly disable air quality features
53
+ return false;
54
+ case 'child_lock':
55
+ // Explicitly disable child lock features
56
+ return false;
57
+ case 'display':
58
+ // Explicitly disable display control features
59
+ return false;
60
+ case 'filter_life':
61
+ // Explicitly disable filter life reporting
62
+ return false;
63
+ case 'fan_speed':
64
+ return true;
65
+ default:
66
+ return false;
67
+ }
68
+ }
69
+ /**
70
+ * Check if a feature is supported in the current device mode
71
+ */
72
+ isFeatureSupportedInCurrentMode(feature) {
73
+ const extendedDevice = this.device;
74
+ // Use device's native method if available
75
+ if (typeof extendedDevice.isFeatureSupportedInCurrentMode === 'function') {
76
+ return extendedDevice.isFeatureSupportedInCurrentMode(feature);
77
+ }
78
+ // Fallback logic
79
+ const mode = extendedDevice.mode || 'manual';
80
+ // Air131 only supports fan speed in manual mode
81
+ if (feature === 'fan_speed' && this.isAir131Device && mode !== 'manual') {
82
+ return false;
83
+ }
84
+ return true;
85
+ }
86
+ /**
87
+ * Get the maximum fan speed for the device
88
+ */
89
+ getMaxFanSpeed() {
90
+ const extendedDevice = this.device;
91
+ // Use device's native method if available
92
+ if (typeof extendedDevice.getMaxFanSpeed === 'function') {
93
+ const maxSpeed = extendedDevice.getMaxFanSpeed();
94
+ if (typeof maxSpeed === 'number' && maxSpeed > 0) {
95
+ return maxSpeed;
96
+ }
97
+ }
98
+ // Use maxSpeed property if available
99
+ if (typeof extendedDevice.maxSpeed === 'number' && extendedDevice.maxSpeed > 0) {
100
+ return extendedDevice.maxSpeed;
101
+ }
102
+ // Fallback logic based on device type
103
+ if (this.isAir131Device) {
104
+ return 3; // LV-series devices have 3 speed levels
105
+ }
106
+ else if (this.isAirBypassDevice) {
107
+ return 3; // All Core series have 3 speed levels
108
+ }
109
+ else if (this.isAirBaseV2Device) {
110
+ if (this.device.deviceType.includes('LAP-EL551S')) {
111
+ return 3; // LAP-EL551S has 3 speed levels
112
+ }
113
+ else {
114
+ return 4; // LAP-V series have 4 speed levels
115
+ }
116
+ }
117
+ // Default to 3 for unknown devices
118
+ return 3;
119
+ }
120
+ /**
121
+ * Convert device speed to percentage
122
+ */
123
+ speedToPercentage(speed) {
124
+ const maxSpeed = this.getMaxFanSpeed();
125
+ // Ensure we have valid numbers
126
+ if (maxSpeed <= 0 || typeof speed !== 'number' || speed <= 0) {
127
+ return 0;
128
+ }
129
+ // For devices with 3 speed levels
130
+ if (maxSpeed === 3) {
131
+ switch (speed) {
132
+ case 1: return 33; // Low -> 33%
133
+ case 2: return 67; // Medium -> 67%
134
+ case 3: return 100; // High -> 100%
135
+ default: return 0;
136
+ }
137
+ }
138
+ // For devices with 4 speed levels
139
+ else if (maxSpeed === 4) {
140
+ switch (speed) {
141
+ case 1: return 25; // Low -> 25%
142
+ case 2: return 50; // Medium-Low -> 50%
143
+ case 3: return 75; // Medium-High -> 75%
144
+ case 4: return 100; // High -> 100%
145
+ default: return 0;
146
+ }
147
+ }
148
+ // Default calculation
149
+ return Math.min(100, Math.max(0, Math.round((speed / maxSpeed) * 100)));
150
+ }
151
+ /**
152
+ * Convert percentage to device speed
153
+ */
154
+ percentageToSpeed(percentage) {
155
+ const maxSpeed = this.getMaxFanSpeed();
156
+ // Ensure we have valid numbers
157
+ if (maxSpeed <= 0 || typeof percentage !== 'number') {
158
+ return 1; // Default to lowest speed
159
+ }
160
+ // Ensure percentage is between 0 and 100
161
+ percentage = Math.min(100, Math.max(0, percentage));
162
+ // For devices with 3 speed levels
163
+ if (maxSpeed === 3) {
164
+ if (percentage <= 33) {
165
+ return 1; // Low
166
+ }
167
+ else if (percentage <= 67) {
168
+ return 2; // Medium
169
+ }
170
+ else {
171
+ return 3; // High
172
+ }
173
+ }
174
+ // For devices with 4 speed levels
175
+ else if (maxSpeed === 4) {
176
+ if (percentage <= 25) {
177
+ return 1; // Low
178
+ }
179
+ else if (percentage <= 50) {
180
+ return 2; // Medium-Low
181
+ }
182
+ else if (percentage <= 75) {
183
+ return 3; // Medium-High
184
+ }
185
+ else {
186
+ return 4; // High
187
+ }
188
+ }
189
+ // Default calculation
190
+ return Math.max(1, Math.min(Math.round((percentage / 100) * maxSpeed), maxSpeed));
17
191
  }
18
192
  /**
19
193
  * Set up the air purifier service
@@ -25,11 +199,110 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
25
199
  // Set up required characteristics
26
200
  this.setupCharacteristic(this.platform.Characteristic.Active, this.getActive.bind(this), this.setActive.bind(this));
27
201
  // Set up target state characteristic for mode mapping
28
- this.setupCharacteristic(this.platform.Characteristic.TargetAirPurifierState, this.getTargetState.bind(this), this.setTargetState.bind(this));
29
- // Set up speed control
30
- this.setupCharacteristic(this.platform.Characteristic.RotationSpeed, this.getRotationSpeed.bind(this), this.handleSetRotationSpeed.bind(this));
202
+ const targetStateChar = this.service.getCharacteristic(this.platform.Characteristic.TargetAirPurifierState);
203
+ // For Core200S, only allow manual mode
204
+ if (this.device.deviceType.includes('Core200S')) {
205
+ targetStateChar.setProps({
206
+ validValues: [0] // MANUAL only
207
+ });
208
+ }
209
+ else if (this.device.deviceType.includes('Core300S')) {
210
+ // Explicitly ensure Core300S has both AUTO and MANUAL modes
211
+ // Force the mode toggle to be visible for Core300S devices
212
+ this.platform.log.debug(`Explicitly enabling mode toggle for Core300S device: ${this.device.deviceName}`);
213
+ // First remove any existing characteristic to ensure clean setup
214
+ if (this.service.testCharacteristic(this.platform.Characteristic.TargetAirPurifierState)) {
215
+ this.service.removeCharacteristic(this.service.getCharacteristic(this.platform.Characteristic.TargetAirPurifierState));
216
+ }
217
+ // Re-add the characteristic with proper properties
218
+ const newTargetStateChar = this.service.addCharacteristic(this.platform.Characteristic.TargetAirPurifierState);
219
+ newTargetStateChar.setProps({
220
+ validValues: [0, 1],
221
+ perms: ["pr" /* this.platform.Characteristic.Perms.PAIRED_READ */, "pw" /* this.platform.Characteristic.Perms.PAIRED_WRITE */, "ev" /* this.platform.Characteristic.Perms.NOTIFY */]
222
+ });
223
+ // Set up the characteristic handlers
224
+ newTargetStateChar.onGet(async () => {
225
+ try {
226
+ const value = await this.getTargetState();
227
+ return value;
228
+ }
229
+ catch (error) {
230
+ this.platform.log.error(`Error getting target state: ${error}`);
231
+ throw error;
232
+ }
233
+ });
234
+ newTargetStateChar.onSet(async (value) => {
235
+ try {
236
+ await this.setTargetState(value);
237
+ }
238
+ catch (error) {
239
+ this.platform.log.error(`Error setting target state: ${error}`);
240
+ throw error;
241
+ }
242
+ });
243
+ }
244
+ else {
245
+ targetStateChar.setProps({
246
+ validValues: [0, 1] // MANUAL and AUTO
247
+ });
248
+ // Set up the characteristic handlers for non-Core300S devices
249
+ this.setupCharacteristic(this.platform.Characteristic.TargetAirPurifierState, this.getTargetState.bind(this), this.setTargetState.bind(this));
250
+ }
251
+ // Set up current state
252
+ this.setupCharacteristic(this.platform.Characteristic.CurrentAirPurifierState, this.getCurrentState.bind(this));
253
+ // Set up speed control with special handling for Core200S and Core300S
254
+ if (this.device.deviceType.includes('Core200S') || this.device.deviceType.includes('Core300S')) {
255
+ // For Core200S and Core300S, set up rotation speed characteristic manually
256
+ this.platform.log.debug(`Explicitly setting up rotation speed for ${this.device.deviceName}`);
257
+ // First remove any existing characteristic to ensure clean setup
258
+ if (this.service.testCharacteristic(this.platform.Characteristic.RotationSpeed)) {
259
+ this.service.removeCharacteristic(this.service.getCharacteristic(this.platform.Characteristic.RotationSpeed));
260
+ }
261
+ // Re-add the characteristic with proper properties
262
+ const rotationSpeedChar = this.service.addCharacteristic(this.platform.Characteristic.RotationSpeed);
263
+ rotationSpeedChar.setProps({
264
+ minValue: 0,
265
+ maxValue: 100,
266
+ minStep: 1,
267
+ perms: ["pr" /* this.platform.Characteristic.Perms.PAIRED_READ */, "pw" /* this.platform.Characteristic.Perms.PAIRED_WRITE */, "ev" /* this.platform.Characteristic.Perms.NOTIFY */]
268
+ });
269
+ // Set up the characteristic handlers
270
+ rotationSpeedChar.onGet(async () => {
271
+ try {
272
+ const value = await this.getRotationSpeed();
273
+ this.platform.log.debug(`${this.device.deviceName} getRotationSpeed returned: ${value}`);
274
+ return value;
275
+ }
276
+ catch (error) {
277
+ this.platform.log.error(`Error getting rotation speed: ${error}`);
278
+ throw error;
279
+ }
280
+ });
281
+ rotationSpeedChar.onSet(async (value) => {
282
+ try {
283
+ this.platform.log.debug(`${this.device.deviceName} setRotationSpeed called with: ${value}`);
284
+ await this.setRotationSpeed(value);
285
+ }
286
+ catch (error) {
287
+ this.platform.log.error(`Error setting rotation speed: ${error}`);
288
+ throw error;
289
+ }
290
+ });
291
+ }
292
+ else {
293
+ // For other devices, use the standard setup
294
+ this.setupCharacteristic(this.platform.Characteristic.RotationSpeed, this.getRotationSpeed.bind(this), this.setRotationSpeed.bind(this));
295
+ }
31
296
  // Add Name characteristic
32
297
  this.setupCharacteristic(this.platform.Characteristic.Name, async () => this.device.deviceName);
298
+ // Air quality sensor service has been disabled for all devices
299
+ }
300
+ // Air quality sensor service has been removed
301
+ /**
302
+ * Get current state (INACTIVE = 0, IDLE = 1, PURIFYING_AIR = 2)
303
+ */
304
+ async getCurrentState() {
305
+ return this.device.deviceStatus === 'on' ? 2 : 0;
33
306
  }
34
307
  /**
35
308
  * Get device capabilities
@@ -37,8 +310,8 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
37
310
  getDeviceCapabilities() {
38
311
  return {
39
312
  hasSpeed: true,
40
- hasAirQuality: true,
41
- hasChildLock: true,
313
+ hasAirQuality: this.hasFeature('air_quality'),
314
+ hasChildLock: false,
42
315
  hasBrightness: false,
43
316
  hasColorTemp: false,
44
317
  hasColor: false,
@@ -58,61 +331,73 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
58
331
  this.service.updateCharacteristic(this.platform.Characteristic.CurrentAirPurifierState, isOn ? 2 : 0);
59
332
  // Get the extended device to access mode information
60
333
  const extendedDevice = this.device;
61
- // Update target state (AUTO = 0, MANUAL = 1)
62
- // If mode is available, use it to determine target state
63
- let targetState = 1; // Default to MANUAL
64
- if (extendedDevice.mode) {
65
- targetState = extendedDevice.mode === 'auto' ? 0 : 1;
334
+ // Update target state (MANUAL = 0, AUTO = 1)
335
+ // Special handling for Core200S
336
+ let targetState = 0; // Default to MANUAL
337
+ if (this.device.deviceType.includes('Core200S')) {
338
+ // Core200S always reports MANUAL mode
339
+ targetState = 0; // MANUAL
340
+ }
341
+ else if (extendedDevice.mode) {
342
+ // For other devices, including Core300S, use the reported mode
343
+ targetState = extendedDevice.mode === 'auto' ? 1 : 0;
66
344
  }
67
345
  this.service.updateCharacteristic(this.platform.Characteristic.TargetAirPurifierState, targetState);
68
346
  // Update rotation speed
69
- let rotationSpeed = 0;
70
- if (isOn && details.speed !== undefined) {
71
- if (this.isLVSeries) {
72
- // For LV-series devices, speed is limited to 1-3
73
- // Convert device speed (1-3) to HomeKit percentage (0-100)
74
- switch (details.speed) {
75
- case 1:
76
- rotationSpeed = 33;
77
- break; // Low
78
- case 2:
79
- rotationSpeed = 66;
80
- break; // Medium
81
- case 3:
82
- rotationSpeed = 100;
83
- break; // High
347
+ if (isOn && details.speed !== undefined && details.speed !== null) {
348
+ // Check if we should skip this update
349
+ if (this.skipNextUpdate) {
350
+ this.platform.log.debug(`Skipping update for ${this.device.deviceName} as requested`);
351
+ this.skipNextUpdate = false;
352
+ return;
353
+ }
354
+ // If we've recently set the speed, use the percentage we calculated
355
+ if (this.lastSetSpeed > 0 && this.lastSetPercentage > 0) {
356
+ this.platform.log.debug(`Using last set percentage for ${this.device.deviceName}: speed=${this.lastSetSpeed}, percentage=${this.lastSetPercentage}`);
357
+ // Only update if the device speed matches what we set
358
+ if (this.lastSetSpeed === details.speed) {
359
+ this.platform.log.debug(`Device speed matches last set speed: ${details.speed}`);
360
+ // Force update the characteristic to our last set percentage
361
+ this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, this.lastSetPercentage);
362
+ }
363
+ else {
364
+ // Device speed doesn't match what we set, so convert from device speed
365
+ const percentage = this.speedToPercentage(details.speed);
366
+ this.platform.log.debug(`Device speed doesn't match last set speed. Device: ${details.speed}, Last set: ${this.lastSetSpeed}, Calculated percentage: ${percentage}`);
367
+ // Update our tracking variables
368
+ this.lastSetSpeed = details.speed;
369
+ this.lastSetPercentage = percentage;
370
+ // Update the characteristic
371
+ this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, percentage);
84
372
  }
85
373
  }
86
374
  else {
87
- // For standard devices, speed can be 1-5
88
- // Convert device speed (1-5) to HomeKit percentage (0-100)
89
- switch (details.speed) {
90
- case 1:
91
- rotationSpeed = 20;
92
- break; // Sleep
93
- case 2:
94
- rotationSpeed = 40;
95
- break; // Low
96
- case 3:
97
- rotationSpeed = 60;
98
- break; // Medium
99
- case 4:
100
- rotationSpeed = 80;
101
- break; // High
102
- case 5:
103
- rotationSpeed = 100;
104
- break; // Turbo
105
- }
375
+ // No recent set speed, so convert from device speed
376
+ const percentage = this.speedToPercentage(details.speed);
377
+ this.platform.log.debug(`No recent set speed for ${this.device.deviceName}. Device speed: ${details.speed}, Calculated percentage: ${percentage}`);
378
+ // Update the characteristic
379
+ this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, percentage);
106
380
  }
107
381
  }
108
- this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, rotationSpeed);
382
+ else {
383
+ this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, 0);
384
+ // Reset tracking variables when device is off
385
+ this.lastSetSpeed = 0;
386
+ this.lastSetPercentage = 0;
387
+ }
388
+ // Air quality updates have been removed
109
389
  }
110
390
  /**
111
- * Get target state (AUTO = 0, MANUAL = 1)
391
+ * Get target state (MANUAL = 0, AUTO = 1)
112
392
  */
113
393
  async getTargetState() {
394
+ // For Core200S, always return MANUAL
395
+ if (this.device.deviceType.includes('Core200S')) {
396
+ return 0; // MANUAL
397
+ }
114
398
  const extendedDevice = this.device;
115
- return extendedDevice.mode === 'auto' ? 0 : 1;
399
+ const targetState = extendedDevice.mode === 'auto' ? 1 : 0;
400
+ return targetState;
116
401
  }
117
402
  /**
118
403
  * Set target state (AUTO = 0, MANUAL = 1)
@@ -121,15 +406,22 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
121
406
  try {
122
407
  const targetState = value;
123
408
  const extendedDevice = this.device;
124
- // Map HomeKit target states to device modes
125
- const mode = targetState === 0 ? 'auto' : 'manual';
409
+ // For Core200S, don't allow mode changes
410
+ if (this.device.deviceType.includes('Core200S')) {
411
+ this.platform.log.warn(`Mode changes not supported on ${this.device.deviceName}, only fan speed control is available`);
412
+ // Update the characteristic back to MANUAL (0)
413
+ this.service.updateCharacteristic(this.platform.Characteristic.TargetAirPurifierState, 0);
414
+ return;
415
+ }
416
+ // For all other devices, including Core300S, normal mode handling
417
+ const mode = targetState === 1 ? 'auto' : 'manual';
126
418
  this.platform.log.debug(`Setting mode to ${mode} for device: ${this.device.deviceName}`);
127
419
  let success = false;
128
420
  // Use the appropriate method to set the mode
129
- if (targetState === 0 && typeof extendedDevice.autoMode === 'function') {
421
+ if (targetState === 1 && typeof extendedDevice.autoMode === 'function') {
130
422
  success = await extendedDevice.autoMode();
131
423
  }
132
- else if (targetState === 1 && typeof extendedDevice.manualMode === 'function') {
424
+ else if (targetState === 0 && typeof extendedDevice.manualMode === 'function') {
133
425
  success = await extendedDevice.manualMode();
134
426
  }
135
427
  else if (typeof extendedDevice.setMode === 'function') {
@@ -152,11 +444,38 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
152
444
  }
153
445
  }
154
446
  /**
155
- * Handle set rotation speed
447
+ * Get rotation speed
156
448
  */
157
- async handleSetRotationSpeed(value) {
449
+ async getRotationSpeed() {
450
+ // If device is off or speed is not defined, return 0
451
+ if (this.device.deviceStatus !== 'on' ||
452
+ this.device.speed === undefined ||
453
+ this.device.speed === null) {
454
+ return 0;
455
+ }
456
+ // If we have a last set percentage, use that instead of calculating from device speed
457
+ if (this.lastSetPercentage > 0 && this.lastSetSpeed === this.device.speed) {
458
+ this.platform.log.debug(`getRotationSpeed returning last set percentage: ${this.lastSetPercentage} for speed: ${this.device.speed}`);
459
+ return this.lastSetPercentage;
460
+ }
461
+ // Convert device speed to percentage using our consistent conversion method
462
+ const percentage = this.speedToPercentage(this.device.speed);
463
+ this.platform.log.debug(`getRotationSpeed calculated percentage: ${percentage} for speed: ${this.device.speed}`);
464
+ return percentage;
465
+ }
466
+ /**
467
+ * Set rotation speed
468
+ */
469
+ async setRotationSpeed(value) {
158
470
  try {
159
- const percentage = value;
471
+ // Ensure value is a valid number
472
+ let percentage = value;
473
+ if (isNaN(percentage) || percentage === null || percentage === undefined) {
474
+ this.platform.log.warn(`Invalid rotation speed value: ${value} for device: ${this.device.deviceName}`);
475
+ return;
476
+ }
477
+ // Ensure percentage is between 0 and 100
478
+ percentage = Math.min(100, Math.max(0, percentage));
160
479
  this.platform.log.debug(`Setting rotation speed to ${percentage}% for device: ${this.device.deviceName}`);
161
480
  if (percentage === 0) {
162
481
  // Turn off the device instead of setting speed to 0
@@ -165,62 +484,56 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
165
484
  if (!success) {
166
485
  throw new Error('Failed to turn off device');
167
486
  }
487
+ // Reset tracking variables when turning off
488
+ this.lastSetSpeed = 0;
489
+ this.lastSetPercentage = 0;
168
490
  return;
169
491
  }
170
492
  const extendedDevice = this.device;
171
- // For LV-series devices, ensure the device is in manual mode before changing fan speed
172
- if (this.isLVSeries && extendedDevice.mode !== 'manual') {
173
- this.platform.log.debug(`Setting device to manual mode before changing fan speed: ${this.device.deviceName}`);
174
- // Use the appropriate method to set manual mode
175
- if (typeof extendedDevice.manualMode === 'function') {
176
- const modeSuccess = await extendedDevice.manualMode();
177
- this.platform.log.debug(`Set manual mode result: ${modeSuccess ? 'success' : 'failed'}`);
178
- }
179
- else if (typeof extendedDevice.setMode === 'function') {
180
- const modeSuccess = await extendedDevice.setMode('manual');
181
- this.platform.log.debug(`Set manual mode result: ${modeSuccess ? 'success' : 'failed'}`);
493
+ // Check if fan speed control is supported in current mode
494
+ if (!this.isFeatureSupportedInCurrentMode('fan_speed')) {
495
+ this.platform.log.warn(`Fan speed control not supported in ${extendedDevice.mode} mode for device: ${this.device.deviceName}`);
496
+ // For Air131 devices, switch to manual mode first
497
+ if (this.isAir131Device && extendedDevice.mode !== 'manual') {
498
+ this.platform.log.debug(`Setting device to manual mode before changing fan speed: ${this.device.deviceName}`);
499
+ // Use the appropriate method to set manual mode
500
+ if (typeof extendedDevice.manualMode === 'function') {
501
+ const modeSuccess = await extendedDevice.manualMode();
502
+ this.platform.log.debug(`Set manual mode result: ${modeSuccess ? 'success' : 'failed'}`);
503
+ }
504
+ else if (typeof extendedDevice.setMode === 'function') {
505
+ const modeSuccess = await extendedDevice.setMode('manual');
506
+ this.platform.log.debug(`Set manual mode result: ${modeSuccess ? 'success' : 'failed'}`);
507
+ }
182
508
  }
183
509
  }
184
- let speed;
185
- if (this.isLVSeries) {
186
- // For LV-series devices, speed is limited to 1-3
187
- // Convert HomeKit percentage (0-100) to device speed (1-3)
188
- if (percentage <= 33) {
189
- speed = 1; // Low
190
- }
191
- else if (percentage <= 66) {
192
- speed = 2; // Medium
193
- }
194
- else {
195
- speed = 3; // High
196
- }
197
- }
198
- else {
199
- // For standard devices, speed can be 1-5
200
- // Convert HomeKit percentage (0-100) to device speed (1-5)
201
- if (percentage <= 20) {
202
- speed = 1; // Sleep
203
- }
204
- else if (percentage <= 40) {
205
- speed = 2; // Low
206
- }
207
- else if (percentage <= 60) {
208
- speed = 3; // Medium
209
- }
210
- else if (percentage <= 80) {
211
- speed = 4; // High
212
- }
213
- else {
214
- speed = 5; // Turbo
215
- }
510
+ // Convert percentage to device speed using our consistent conversion method
511
+ const speed = this.percentageToSpeed(percentage);
512
+ // Final validation to ensure speed is a valid number
513
+ if (isNaN(speed) || speed === null || speed === undefined) {
514
+ this.platform.log.warn(`Calculated invalid speed: ${speed} from percentage: ${percentage} for device: ${this.device.deviceName}`);
515
+ return;
216
516
  }
217
517
  this.platform.log.debug(`Setting fan speed to ${speed} for device: ${this.device.deviceName}`);
518
+ // Store the values we're setting before making the API call
519
+ this.lastSetSpeed = speed;
520
+ this.lastSetPercentage = percentage;
521
+ // Set the flag to skip the next update
522
+ this.skipNextUpdate = true;
523
+ // Immediately update the characteristic to show the correct percentage
524
+ // This ensures the slider shows the value we set, not what the device reports
525
+ this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, percentage);
218
526
  const success = await this.device.changeFanSpeed(speed);
219
527
  if (!success) {
528
+ // If the API call failed, reset our tracking variables
529
+ this.lastSetSpeed = 0;
530
+ this.lastSetPercentage = 0;
220
531
  throw new Error(`Failed to set speed to ${speed}`);
221
532
  }
533
+ this.platform.log.debug(`Successfully set fan speed to ${speed} (${percentage}%) for device: ${this.device.deviceName}`);
222
534
  // Update device state and characteristics
223
- await this.updateDeviceSpecificStates(this.device);
535
+ // Skip this to avoid overriding our UI update
536
+ // await this.updateDeviceSpecificStates(this.device);
224
537
  }
225
538
  catch (error) {
226
539
  this.handleDeviceError('set rotation speed', error);
@@ -237,6 +550,11 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
237
550
  if (!success) {
238
551
  throw new Error(`Failed to turn ${isOn ? 'on' : 'off'} device`);
239
552
  }
553
+ // Reset tracking variables when turning off
554
+ if (!isOn) {
555
+ this.lastSetSpeed = 0;
556
+ this.lastSetPercentage = 0;
557
+ }
240
558
  // Update device state and characteristics
241
559
  await this.updateDeviceSpecificStates(this.device);
242
560
  await this.persistDeviceState('deviceStatus', isOn ? 'on' : 'off');
@@ -245,35 +563,6 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
245
563
  this.handleDeviceError('set active state', error);
246
564
  }
247
565
  }
248
- async getRotationSpeed() {
249
- if (this.device.speed === undefined || this.device.speed === null) {
250
- return 0;
251
- }
252
- if (this.isLVSeries) {
253
- // For LV-series devices, speed is limited to 1-3
254
- // Convert device speed (1-3) to HomeKit percentage (0-100)
255
- switch (this.device.speed) {
256
- case 0: return 0; // Off
257
- case 1: return 33; // Low
258
- case 2: return 66; // Medium
259
- case 3: return 100; // High
260
- default: return 0;
261
- }
262
- }
263
- else {
264
- // For standard devices, speed can be 1-5
265
- // Convert device speed (1-5) to HomeKit percentage (0-100)
266
- switch (this.device.speed) {
267
- case 0: return 0; // Off
268
- case 1: return 20; // Sleep
269
- case 2: return 40; // Low
270
- case 3: return 60; // Medium
271
- case 4: return 80; // High
272
- case 5: return 100; // Turbo
273
- default: return 0;
274
- }
275
- }
276
- }
277
566
  }
278
567
  exports.AirPurifierAccessory = AirPurifierAccessory;
279
568
  //# sourceMappingURL=air-purifier.accessory.js.map