homebridge-tsvesync 1.4.7 → 1.4.9

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.
@@ -5,6 +5,10 @@ const base_accessory_1 = require("./base.accessory");
5
5
  class HumidifierAccessory extends base_accessory_1.BaseAccessory {
6
6
  constructor(platform, accessory, device) {
7
7
  super(platform, accessory, device);
8
+ // Tracks the last mode we commanded, to protect against stale API responses.
9
+ // Expires after 30s so out-of-band changes (VeSync app) are not masked.
10
+ this._lastCommandedMode = null;
11
+ this._lastCommandedModeTime = 0;
8
12
  this.device = device;
9
13
  // Initialize capabilities with default values to prevent undefined
10
14
  this.capabilities = {
@@ -18,13 +22,17 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
18
22
  hasChildLock: true,
19
23
  hasSwingMode: false,
20
24
  };
21
- // Detect device type
25
+ // Detect device type — order matters: specific types first
26
+ this.isHumidDual200S = this.detectHumidDual200S();
22
27
  this.isHumid200S = this.detectHumid200S();
23
28
  this.isHumid200300S = this.detectHumid200300S();
24
29
  this.isHumid1000S = this.detectHumid1000S();
25
30
  this.isSuperior6000S = this.detectSuperior6000S();
26
31
  // Log detected device type
27
- if (this.isHumid200S) {
32
+ if (this.isHumidDual200S) {
33
+ this.platform.log.debug(`Detected Dual200S device: ${this.device.deviceName}`);
34
+ }
35
+ else if (this.isHumid200S) {
28
36
  this.platform.log.debug(`Detected Humid200S device: ${this.device.deviceName}`);
29
37
  }
30
38
  else if (this.isHumid200300S) {
@@ -40,6 +48,13 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
40
48
  this.platform.log.debug(`Unknown humidifier type: ${this.device.deviceName}, using default implementation`);
41
49
  }
42
50
  }
51
+ /**
52
+ * Detect if the device is a HumidDual200S (or regional variant LUH-D301S)
53
+ */
54
+ detectHumidDual200S() {
55
+ const deviceType = this.device.deviceType.toUpperCase();
56
+ return deviceType.includes('DUAL200S') || deviceType.includes('LUH-D301S');
57
+ }
43
58
  /**
44
59
  * Detect if the device is a Humid200S
45
60
  */
@@ -49,7 +64,11 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
49
64
  if (deviceType.includes('CLASSIC200S')) {
50
65
  return true;
51
66
  }
52
- // Check for mist level range 1-3 which is specific to Humid200S
67
+ // Check for mist level range 1-3 which is specific to Humid200S,
68
+ // but exclude Dual200S which also has low mist levels
69
+ if (this.isHumidDual200S) {
70
+ return false;
71
+ }
53
72
  const extendedDevice = this.device;
54
73
  if (extendedDevice.mistLevel !== undefined && extendedDevice.mistLevel <= 3) {
55
74
  return true;
@@ -68,7 +87,7 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
68
87
  }
69
88
  // Check device type for Classic300S or Dual200S
70
89
  const deviceType = this.device.deviceType.toUpperCase();
71
- if (deviceType.includes('CLASSIC300S') || deviceType.includes('DUAL200S')) {
90
+ if (deviceType.includes('CLASSIC300S') || this.isHumidDual200S) {
72
91
  return true;
73
92
  }
74
93
  // Check for LUH-A601S, LUH-A602S, LUH-O451S, LUH-O601S series
@@ -123,17 +142,63 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
123
142
  }
124
143
  return false;
125
144
  }
145
+ /**
146
+ * Convert a VeSync mode string to a HomeKit TargetHumidifierDehumidifierState value.
147
+ *
148
+ * For Dual200S the mapping is swapped so that VeSync "auto" (which needs a
149
+ * humidity target) lands on HomeKit state 1 (HUMIDIFIER / "Humidity" in the
150
+ * Home app) where Apple shows the humidity slider, and VeSync "manual"
151
+ * (fixed mist, no target) lands on state 0 ("Auto", no slider).
152
+ */
153
+ modeToTargetState(mode) {
154
+ // Prefer last commanded mode to protect against stale API responses.
155
+ // Clear the override once the device reports the same mode (caught up).
156
+ let effectiveMode = mode;
157
+ if (this._lastCommandedMode !== null) {
158
+ const elapsed = Date.now() - this._lastCommandedModeTime;
159
+ if (mode === this._lastCommandedMode || elapsed > 30000) {
160
+ this._lastCommandedMode = null;
161
+ }
162
+ else {
163
+ this.platform.log.debug(`Using commanded mode '${this._lastCommandedMode}' instead of device-reported '${mode}'`);
164
+ effectiveMode = this._lastCommandedMode;
165
+ }
166
+ }
167
+ if (this.isHumidDual200S) {
168
+ return effectiveMode === 'auto' ? 1 : 0;
169
+ }
170
+ return effectiveMode === 'auto' ? 0 : 1;
171
+ }
172
+ /**
173
+ * Convert a HomeKit TargetHumidifierDehumidifierState value to a VeSync mode.
174
+ * Inverse of modeToTargetState.
175
+ */
176
+ targetStateToMode(value) {
177
+ if (this.isHumidDual200S) {
178
+ return value === 1 ? 'auto' : 'manual';
179
+ }
180
+ return value === 0 ? 'auto' : 'manual';
181
+ }
126
182
  setupService() {
127
183
  // No need to check for capabilities initialization since we set defaults in constructor
128
184
  // Get or create the humidifier service
129
185
  this.service = this.accessory.getService(this.platform.Service.HumidifierDehumidifier) ||
130
186
  this.accessory.addService(this.platform.Service.HumidifierDehumidifier);
131
- // Initialize all characteristics with explicit values
187
+ // Initialize characteristics
132
188
  this.service.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, 40);
133
189
  this.service.updateCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold, 60);
134
190
  this.service.updateCharacteristic(this.platform.Characteristic.CurrentHumidifierDehumidifierState, 1);
135
191
  this.service.updateCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState, 1);
136
- this.platform.log.debug('Initialized humidifier characteristics with explicit values');
192
+ // Register humidity handlers IMMEDIATELY after updateCharacteristic — no separation.
193
+ // The updateCharacteristic creates the instance; onGet/onSet must be on that same instance.
194
+ this.service
195
+ .getCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold)
196
+ .setProps({ minValue: 30, maxValue: 80, minStep: 1 })
197
+ .onGet(this.getTargetHumidity.bind(this))
198
+ .onSet(async (value) => {
199
+ this.platform.log.info(`Setting target humidity to ${value}% for ${this.device.deviceName}`);
200
+ await this.setTargetHumidity(value);
201
+ });
137
202
  // Set up required characteristics
138
203
  this.setupCharacteristic(this.platform.Characteristic.Active, this.getActive.bind(this), this.setActive.bind(this));
139
204
  // Set up target state characteristic for mode mapping
@@ -149,38 +214,28 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
149
214
  targetStateChar.setProps({
150
215
  validValues: supportsAutoMode ? [0, 1] : [1]
151
216
  });
152
- this.setupCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState, async () => extendedDevice.mode === 'auto' ? 0 : 1, this.setTargetState.bind(this));
217
+ this.setupCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState, async () => this.modeToTargetState(extendedDevice.mode), this.setTargetState.bind(this));
153
218
  // Set up rotation speed characteristic
154
219
  this.setupCharacteristic(this.platform.Characteristic.RotationSpeed, this.getRotationSpeed.bind(this), this.handleSetRotationSpeed.bind(this));
220
+ // Constrain rotation speed to 2 steps for Dual200S (Low=50%, High=100%)
221
+ if (this.isHumidDual200S) {
222
+ const rotationSpeedChar = this.service.getCharacteristic(this.platform.Characteristic.RotationSpeed);
223
+ rotationSpeedChar.setProps({
224
+ minValue: 0,
225
+ maxValue: 100,
226
+ minStep: 50,
227
+ });
228
+ }
155
229
  // Add Name characteristic
156
230
  this.setupCharacteristic(this.platform.Characteristic.Name, async () => this.device.deviceName);
157
- // Add Current Relative Humidity if supported
231
+ // CurrentRelativeHumidity getter
158
232
  if (this.capabilities && this.capabilities.hasHumidity) {
159
- if (!this.service.testCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)) {
160
- this.service.addCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity);
161
- }
162
- // Add Target Relative Humidity characteristic
163
- if (!this.service.testCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold)) {
164
- this.service.addCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold);
165
- }
166
- // Configure the target humidity characteristic with proper properties
167
- const targetHumidityChar = this.service.getCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold);
168
- targetHumidityChar.setProps({
169
- minValue: 30,
170
- maxValue: 80,
171
- minStep: 1
172
- });
173
- // Set an initial value to ensure the characteristic is not blank
174
- targetHumidityChar.updateValue(45);
175
- this.platform.log.debug(`Configured RelativeHumidityHumidifierThreshold characteristic with initial value 45%`);
176
- // Set up handlers for target humidity
177
- this.setupCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold, this.getTargetHumidity.bind(this), this.setTargetHumidity.bind(this));
178
- // Immediately get and set the actual target humidity to ensure it's displayed correctly
179
- this.getTargetHumidity().then(targetHumidity => {
180
- this.platform.log.debug(`Initial target humidity value: ${targetHumidity}%`);
181
- targetHumidityChar.updateValue(targetHumidity);
182
- }).catch(error => {
183
- this.platform.log.warn(`Failed to get initial target humidity: ${error}`);
233
+ this.service
234
+ .getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
235
+ .onGet(async () => {
236
+ var _a, _b, _c;
237
+ const extDev = this.device;
238
+ return (_c = (_a = extDev.currentHumidity) !== null && _a !== void 0 ? _a : (_b = extDev.details) === null || _b === void 0 ? void 0 : _b.current_humidity) !== null && _c !== void 0 ? _c : 0;
184
239
  });
185
240
  }
186
241
  // Add Water Level characteristic if supported (mapping inferred from water_lacks/water_tank_lifted)
@@ -346,7 +401,6 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
346
401
  * Update device states based on the latest details
347
402
  */
348
403
  async updateDeviceSpecificStates(details) {
349
- var _a, _b, _c, _d, _e, _f;
350
404
  // Log the relevant details for debugging without using JSON.stringify on the entire object
351
405
  this.platform.log.debug(`Updating device states for ${this.device.deviceName}`);
352
406
  // Cast to extended device type
@@ -374,65 +428,49 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
374
428
  catch (error) {
375
429
  this.platform.log.warn(`Failed to refresh device details: ${error}`);
376
430
  }
377
- // Ensure we're using the most up-to-date status
378
- // According to the API documentation, deviceStatus should be 'on' or 'off'
431
+ this.applyDeviceStatesToHomeKit(details);
432
+ }
433
+ applyDeviceStatesToHomeKit(details) {
434
+ var _a, _b, _c, _d, _e, _f;
435
+ const extendedDevice = this.device;
379
436
  const isActive = this.device.deviceStatus === 'on';
380
437
  this.platform.log.debug(`Device ${this.device.deviceName} active state: ${isActive}, Status: ${this.device.deviceStatus}`);
381
- // Update active state - this is critical for HomeKit to show the correct state
382
438
  this.service.updateCharacteristic(this.platform.Characteristic.Active, isActive ? 1 : 0);
383
- // Update current state based on API mapping
384
- // HomeKit states: 0 = INACTIVE, 1 = IDLE, 2 = HUMIDIFYING, 3 = DEHUMIDIFYING
385
- // Mapping:
386
- // - If not active -> INACTIVE (0)
387
- // - If active and mode is 'manual' -> HUMIDIFYING (2)
388
- // - If active and mode is 'auto' or 'sleep' -> Check if we need to be humidifying
389
- let currentState = 0; // Default to INACTIVE
439
+ let currentState = 0;
390
440
  if (isActive) {
391
441
  if (extendedDevice.mode === 'manual') {
392
- currentState = 2; // HUMIDIFYING - will show "Rising to X%"
442
+ currentState = 2;
393
443
  }
394
444
  else if (extendedDevice.mode === 'auto' || extendedDevice.mode === 'sleep') {
395
- // Get current humidity reading (actual humidity in the room)
396
445
  const currentHumidity = extendedDevice.currentHumidity !== undefined ?
397
446
  extendedDevice.currentHumidity :
398
447
  ((_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.current_humidity) ||
399
448
  0;
400
- // Get target humidity (humidity setting)
401
449
  const targetHumidity = extendedDevice.humidity ||
402
450
  ((_b = extendedDevice.configuration) === null || _b === void 0 ? void 0 : _b.auto_target_humidity) ||
403
451
  ((_c = extendedDevice.details) === null || _c === void 0 ? void 0 : _c.target_humidity) ||
404
- 45; // Default to 45% if not available
405
- // In auto mode, check if we need to be humidifying
452
+ 45;
406
453
  if (currentHumidity < targetHumidity) {
407
- currentState = 2; // HUMIDIFYING - will show "Rising to X%"
454
+ currentState = 2;
408
455
  this.platform.log.debug(`Auto mode: Current humidity ${currentHumidity}% is below target ${targetHumidity}%, setting state to HUMIDIFYING`);
409
456
  }
410
457
  else {
411
- currentState = 1; // IDLE - will show "Set to X%"
458
+ currentState = 1;
412
459
  this.platform.log.debug(`Auto mode: Current humidity ${currentHumidity}% has reached target ${targetHumidity}%, setting state to IDLE`);
413
460
  }
414
461
  }
415
462
  }
416
463
  this.service.updateCharacteristic(this.platform.Characteristic.CurrentHumidifierDehumidifierState, currentState);
417
- // Update target state based on device mode
418
- // HomeKit Target State: 0 = HUMIDIFIER_OR_DEHUMIDIFIER (Auto), 1 = HUMIDIFIER, 2 = DEHUMIDIFIER
419
- // Mapping:
420
- // - 'auto' -> HUMIDIFIER_OR_DEHUMIDIFIER (0)
421
- // - 'manual' -> HUMIDIFIER (1)
422
- // - 'sleep' -> HUMIDIFIER (1) (no direct mapping, default to HUMIDIFIER)
423
- let targetState = 1; // Default to HUMIDIFIER
424
- if (extendedDevice.mode === 'auto') {
425
- targetState = 0; // HUMIDIFIER_OR_DEHUMIDIFIER
426
- }
427
- this.service.updateCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState, targetState);
428
- // Update rotation speed - convert device speed (1-9) to HomeKit percentage (0-100)
464
+ this.service.updateCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState, this.modeToTargetState(extendedDevice.mode));
429
465
  let rotationSpeed = 0;
430
- // For Humid200300S devices, use mistLevel if available
431
466
  if (isActive) {
432
- if (this.isHumid200300S && extendedDevice.mistLevel !== undefined) {
467
+ if (this.isHumidDual200S && extendedDevice.mistLevel !== undefined) {
468
+ // Dual200S has 2 levels: 1 (Low) = 50%, 2 (High) = 100%
469
+ rotationSpeed = extendedDevice.mistLevel === 1 ? 50 : 100;
470
+ }
471
+ else if (this.isHumid200300S && extendedDevice.mistLevel !== undefined) {
433
472
  const mistLevel = extendedDevice.mistLevel;
434
473
  this.platform.log.debug(`Device ${this.device.deviceName} mist level: ${mistLevel}`);
435
- // Convert mist level (1-9) to HomeKit percentage (0-100)
436
474
  switch (mistLevel) {
437
475
  case 1:
438
476
  rotationSpeed = 11;
@@ -464,7 +502,6 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
464
502
  }
465
503
  }
466
504
  else if (this.device.speed !== undefined) {
467
- // Use speed for other humidifier types
468
505
  this.platform.log.debug(`Device ${this.device.deviceName} speed: ${this.device.speed}`);
469
506
  switch (this.device.speed) {
470
507
  case 1:
@@ -499,67 +536,51 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
499
536
  }
500
537
  this.platform.log.debug(`Setting rotation speed to ${rotationSpeed}% for device: ${this.device.deviceName}`);
501
538
  this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, rotationSpeed);
502
- // Update relative humidity if supported
503
539
  if (this.capabilities && this.capabilities.hasHumidity) {
504
- // Get current humidity reading (actual humidity in the room)
505
540
  const currentHumidity = extendedDevice.currentHumidity !== undefined ?
506
541
  extendedDevice.currentHumidity :
507
542
  ((_d = extendedDevice.details) === null || _d === void 0 ? void 0 : _d.current_humidity) ||
508
543
  0;
509
- // Update current humidity characteristic if we have a valid reading
510
544
  if (currentHumidity > 0) {
511
545
  this.service.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, currentHumidity);
512
546
  }
513
- // Get target humidity (humidity setting)
514
- // According to API docs, humidifier.humidity returns auto_target_humidity from configuration
515
547
  const targetHumidity = extendedDevice.humidity ||
516
548
  ((_e = extendedDevice.configuration) === null || _e === void 0 ? void 0 : _e.auto_target_humidity) ||
517
549
  ((_f = extendedDevice.details) === null || _f === void 0 ? void 0 : _f.target_humidity) ||
518
- 45; // Default to 45% if not available
550
+ 45;
519
551
  if (this.service.testCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold)) {
520
552
  this.service.updateCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold, targetHumidity);
521
553
  }
522
554
  }
523
- // Update water level if supported (inferred from water_lacks/water_tank_lifted)
524
555
  if (this.capabilities && this.capabilities.hasWaterLevel) {
525
- // Check for water_lacks or water_tank_lifted in details or device.details
526
556
  const waterLacks = (details === null || details === void 0 ? void 0 : details.water_lacks) ||
527
557
  (extendedDevice.details && extendedDevice.details.water_lacks);
528
558
  const waterTankLifted = (details === null || details === void 0 ? void 0 : details.water_tank_lifted) ||
529
559
  (extendedDevice.details && extendedDevice.details.water_tank_lifted);
530
560
  const waterLow = waterLacks || waterTankLifted;
531
- // Note: VeSync devices only support a boolean water_lacks property, not a percentage water level
532
- // We map this to 0% or 100% for HomeKit's WaterLevel characteristic
533
561
  if (this.service.getCharacteristic(this.platform.Characteristic.WaterLevel)) {
534
- // Map water level as 0 for low water, 100 for sufficient water
535
562
  this.service.updateCharacteristic(this.platform.Characteristic.WaterLevel, waterLow ? 0 : 100);
536
563
  this.platform.log.debug(`Water level for ${this.device.deviceName}: ${waterLow ? 'Low (0%)' : 'OK (100%)'}`);
537
564
  }
538
565
  }
539
- // Update night light characteristics for devices that support it
540
566
  if ((this.isHumid200300S || this.isHumid1000S) && this.lightService) {
541
567
  const brightness = extendedDevice.nightLightBrightness ||
542
568
  (extendedDevice.details && extendedDevice.details.night_light_brightness) || 0;
543
- // Update on/off state
544
569
  this.lightService.updateCharacteristic(this.platform.Characteristic.On, brightness > 0);
545
- // Update brightness
546
570
  this.lightService.updateCharacteristic(this.platform.Characteristic.Brightness, brightness);
547
571
  }
548
- // Update temperature for Superior6000S
549
572
  if (this.isSuperior6000S && this.temperatureService) {
550
573
  const temperature = extendedDevice.temperature ||
551
574
  (extendedDevice.details && extendedDevice.details.temperature) ||
552
- 20; // Default to 20°C if not available
575
+ 20;
553
576
  this.temperatureService.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, temperature);
554
577
  }
555
- // Update filter life for Superior6000S
556
578
  if (this.isSuperior6000S && this.filterService) {
557
579
  const filterLife = extendedDevice.filterLifePercentage ||
558
580
  (extendedDevice.details && extendedDevice.details.filter_life) ||
559
- 100; // Default to 100% if not available
581
+ 100;
560
582
  this.filterService.updateCharacteristic(this.platform.Characteristic.FilterLifeLevel, filterLife);
561
- this.filterService.updateCharacteristic(this.platform.Characteristic.FilterChangeIndication, filterLife < 10 ? 1 : 0 // 1 = CHANGE_FILTER, 0 = FILTER_OK
562
- );
583
+ this.filterService.updateCharacteristic(this.platform.Characteristic.FilterChangeIndication, filterLife < 10 ? 1 : 0);
563
584
  }
564
585
  }
565
586
  getDeviceCapabilities() {
@@ -600,7 +621,6 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
600
621
  return isActive ? 1 : 0;
601
622
  }
602
623
  async setActive(value) {
603
- var _a, _b, _c;
604
624
  try {
605
625
  const isOn = value === 1;
606
626
  this.platform.log.debug(`Setting device ${this.device.deviceName} to ${isOn ? 'on' : 'off'}`);
@@ -662,63 +682,24 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
662
682
  success = await this.device.turnOff();
663
683
  }
664
684
  if (!success) {
665
- this.platform.log.warn(`API call to turn ${isOn ? 'on' : 'off'} device returned false`);
666
- }
667
- // Refresh device details to get the latest state
668
- if (typeof extendedDevice.getDetails === 'function') {
669
- await extendedDevice.getDetails();
670
- this.platform.log.debug(`Device ${this.device.deviceName} status after setting: ` +
671
- `Status: ${this.device.deviceStatus}, ` +
672
- `Mode: ${extendedDevice.mode}, ` +
673
- `Speed: ${this.device.speed}, ` +
674
- `MistLevel: ${extendedDevice.mistLevel}`);
675
- }
676
- // Verify the device is actually in the desired state
677
- const actuallyOn = this.device.deviceStatus === 'on';
678
- if (actuallyOn !== isOn) {
679
- this.platform.log.warn(`Device ${this.device.deviceName} did not change to desired state. ` +
680
- `Wanted: ${isOn ? 'on' : 'off'}, Actual: ${actuallyOn ? 'on' : 'off'}`);
685
+ throw new Error(`Failed to turn ${isOn ? 'on' : 'off'} device`);
681
686
  }
682
- // Always update HomeKit with the actual device state, not the requested state
683
- this.service.updateCharacteristic(this.platform.Characteristic.Active, actuallyOn ? 1 : 0);
684
- // Update current state based on the actual state and mode
685
- let currentState = 0; // Default to INACTIVE
686
- if (actuallyOn) {
687
- if (extendedDevice.mode === 'manual') {
688
- currentState = 2; // HUMIDIFYING - will show "Rising to X%"
689
- }
690
- else if (extendedDevice.mode === 'auto' || extendedDevice.mode === 'sleep') {
691
- // Get current humidity reading (actual humidity in the room)
692
- const currentHumidity = extendedDevice.currentHumidity !== undefined ?
693
- extendedDevice.currentHumidity :
694
- ((_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.current_humidity) ||
695
- 0;
696
- // Get target humidity (humidity setting)
697
- const targetHumidity = extendedDevice.humidity ||
698
- ((_b = extendedDevice.configuration) === null || _b === void 0 ? void 0 : _b.auto_target_humidity) ||
699
- ((_c = extendedDevice.details) === null || _c === void 0 ? void 0 : _c.target_humidity) ||
700
- 45; // Default to 45% if not available
701
- // In auto mode, check if we need to be humidifying
702
- if (currentHumidity < targetHumidity) {
703
- currentState = 2; // HUMIDIFYING - will show "Rising to X%"
704
- this.platform.log.debug(`Auto mode: Current humidity ${currentHumidity}% is below target ${targetHumidity}%, setting state to HUMIDIFYING`);
705
- }
706
- else {
707
- currentState = 1; // IDLE - will show "Set to X%"
708
- this.platform.log.debug(`Auto mode: Current humidity ${currentHumidity}% has reached target ${targetHumidity}%, setting state to IDLE`);
709
- }
710
- }
711
- }
712
- this.service.updateCharacteristic(this.platform.Characteristic.CurrentHumidifierDehumidifierState, currentState);
713
- // Update device state and characteristics
714
- await this.updateDeviceSpecificStates(this.device);
715
- await this.persistDeviceState('deviceStatus', actuallyOn ? 'on' : 'off');
687
+ this.applyDeviceStatesToHomeKit(this.device);
688
+ await this.persistDeviceState('deviceStatus', isOn ? 'on' : 'off');
716
689
  }
717
690
  catch (error) {
718
691
  this.handleDeviceError('set active state', error);
719
692
  }
720
693
  }
721
694
  async getRotationSpeed() {
695
+ const extendedDevice = this.device;
696
+ if (this.isHumidDual200S) {
697
+ const level = extendedDevice.mistLevel;
698
+ if (level === undefined || level === null || level === 0) {
699
+ return 0;
700
+ }
701
+ return level === 1 ? 50 : 100;
702
+ }
722
703
  if (this.device.speed === undefined || this.device.speed === null) {
723
704
  return 0;
724
705
  }
@@ -738,7 +719,6 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
738
719
  }
739
720
  }
740
721
  async setTargetState(value) {
741
- var _a, _b, _c;
742
722
  try {
743
723
  // HomeKit Target State: 0 = HUMIDIFIER_OR_DEHUMIDIFIER (Auto), 1 = HUMIDIFIER, 2 = DEHUMIDIFIER
744
724
  this.platform.log.debug(`Setting target state to ${value} for device: ${this.device.deviceName}`);
@@ -750,34 +730,24 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
750
730
  if (!turnOnSuccess) {
751
731
  throw new Error('Failed to turn on device before changing mode');
752
732
  }
753
- // Refresh device details after turning on
754
- if (typeof device.getDetails === 'function') {
755
- await device.getDetails();
756
- this.platform.log.debug(`Device status after turning on: ${this.device.deviceStatus}`);
757
- }
758
733
  }
759
- // Map HomeKit target states to device modes
760
- let mode = 'manual'; // Default to manual mode
761
- switch (value) {
762
- case 0: // Auto
763
- mode = 'auto';
764
- break;
765
- case 1: // Humidifier
766
- mode = 'manual';
767
- break;
768
- case 2: // Dehumidifier (not supported, but we'll handle it gracefully)
769
- this.platform.log.warn(`Dehumidifier mode not supported for device: ${this.device.deviceName}, using manual mode instead`);
770
- mode = 'manual';
771
- break;
772
- default:
773
- this.platform.log.warn(`Unknown target state value: ${value}, using manual mode as default`);
774
- mode = 'manual';
734
+ let mode;
735
+ if (value === 2) {
736
+ this.platform.log.warn(`Dehumidifier mode not supported for device: ${this.device.deviceName}, using manual mode instead`);
737
+ mode = 'manual';
738
+ }
739
+ else if (value === 0 || value === 1) {
740
+ mode = this.targetStateToMode(value);
741
+ }
742
+ else {
743
+ this.platform.log.warn(`Unknown target state value: ${value}, using manual mode as default`);
744
+ mode = 'manual';
775
745
  }
776
746
  // Check current mode first
777
747
  if (device.mode === mode) {
778
748
  this.platform.log.debug(`Device ${this.device.deviceName} is already in ${mode} mode, skipping API call`);
779
749
  // Update target state to reflect the current mode
780
- const targetState = mode === 'auto' ? 0 : 1;
750
+ const targetState = this.modeToTargetState(mode);
781
751
  this.updateCharacteristicValue(this.platform.Characteristic.TargetHumidifierDehumidifierState, targetState);
782
752
  return;
783
753
  }
@@ -821,51 +791,10 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
821
791
  if (!success) {
822
792
  throw new Error(`Failed to set device mode to ${mode}`);
823
793
  }
824
- // Refresh device details to get the latest state
825
- if (typeof device.getDetails === 'function') {
826
- await device.getDetails();
827
- this.platform.log.debug(`Device ${this.device.deviceName} mode after setting: ${device.mode}`);
828
- }
829
- // Verify the device is actually in the desired mode
830
- const actualMode = device.mode;
831
- if (actualMode !== mode) {
832
- this.platform.log.warn(`Device ${this.device.deviceName} did not change to desired mode. ` +
833
- `Wanted: ${mode}, Actual: ${actualMode}`);
834
- }
835
- // Update the target state based on the actual mode
836
- const targetState = actualMode === 'auto' ? 0 : 1;
837
- this.updateCharacteristicValue(this.platform.Characteristic.TargetHumidifierDehumidifierState, targetState);
838
- // Update current state based on the active state and mode
839
- let currentState = 0; // Default to INACTIVE
840
- if (this.device.deviceStatus === 'on') {
841
- if (actualMode === 'manual') {
842
- currentState = 2; // HUMIDIFYING - will show "Rising to X%"
843
- }
844
- else if (actualMode === 'auto' || actualMode === 'sleep') {
845
- // Get current humidity reading (actual humidity in the room)
846
- const currentHumidity = device.currentHumidity !== undefined ?
847
- device.currentHumidity :
848
- ((_a = device.details) === null || _a === void 0 ? void 0 : _a.current_humidity) ||
849
- 0;
850
- // Get target humidity (humidity setting)
851
- const targetHumidity = device.humidity ||
852
- ((_b = device.configuration) === null || _b === void 0 ? void 0 : _b.auto_target_humidity) ||
853
- ((_c = device.details) === null || _c === void 0 ? void 0 : _c.target_humidity) ||
854
- 45; // Default to 45% if not available
855
- // In auto mode, check if we need to be humidifying
856
- if (currentHumidity < targetHumidity) {
857
- currentState = 2; // HUMIDIFYING - will show "Rising to X%"
858
- this.platform.log.debug(`Auto mode: Current humidity ${currentHumidity}% is below target ${targetHumidity}%, setting state to HUMIDIFYING`);
859
- }
860
- else {
861
- currentState = 1; // IDLE - will show "Set to X%"
862
- this.platform.log.debug(`Auto mode: Current humidity ${currentHumidity}% has reached target ${targetHumidity}%, setting state to IDLE`);
863
- }
864
- }
865
- }
866
- this.updateCharacteristicValue(this.platform.Characteristic.CurrentHumidifierDehumidifierState, currentState);
867
- // Update device state and characteristics
868
- await this.updateDeviceSpecificStates(this.device);
794
+ // Track commanded mode so stale getDetails() responses don't revert HomeKit
795
+ this._lastCommandedMode = mode;
796
+ this._lastCommandedModeTime = Date.now();
797
+ this.applyDeviceStatesToHomeKit(this.device);
869
798
  }
870
799
  catch (error) {
871
800
  this.handleDeviceError('set target state', error);
@@ -884,9 +813,13 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
884
813
  }
885
814
  return;
886
815
  }
887
- // Convert HomeKit percentage (0-100) to device speed (1-9)
816
+ // Convert percentage to mist level based on device type
888
817
  let speed;
889
- if (percentage <= 11) {
818
+ if (this.isHumidDual200S) {
819
+ // Dual200S: 2 levels — 1-50% = Low (1), 51-100% = High (2)
820
+ speed = percentage <= 50 ? 1 : 2;
821
+ }
822
+ else if (percentage <= 11) {
890
823
  speed = 1;
891
824
  }
892
825
  else if (percentage <= 22) {
@@ -916,6 +849,18 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
916
849
  // Adjust speed based on device type
917
850
  const extendedDevice = this.device;
918
851
  let success = false;
852
+ // Dual200S: adjusting speed while in auto mode implies the user wants manual control
853
+ if (this.isHumidDual200S && extendedDevice.mode === 'auto') {
854
+ this.platform.log.debug(`Switching Dual200S to manual mode before setting mist level: ${this.device.deviceName}`);
855
+ if (typeof extendedDevice.setManualMode === 'function') {
856
+ await extendedDevice.setManualMode();
857
+ }
858
+ else if (typeof extendedDevice.setMode === 'function') {
859
+ await extendedDevice.setMode('manual');
860
+ }
861
+ this._lastCommandedMode = 'manual';
862
+ this._lastCommandedModeTime = Date.now();
863
+ }
919
864
  // For Humid200S devices, limit mist level to 1-3
920
865
  if (this.isHumid200S) {
921
866
  // Limit speed to 1-3 for Humid200S devices
@@ -982,7 +927,6 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
982
927
  * Set target humidity level
983
928
  */
984
929
  async setTargetHumidity(value) {
985
- var _a;
986
930
  try {
987
931
  const targetHumidity = value;
988
932
  this.platform.log.debug(`Setting target humidity to ${targetHumidity}% for device: ${this.device.deviceName}`);
@@ -994,6 +938,8 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
994
938
  if (!turnOnSuccess) {
995
939
  throw new Error('Failed to turn on device before setting humidity');
996
940
  }
941
+ // Sync Active state immediately so HomeKit tile reflects power-on
942
+ this.service.updateCharacteristic(this.platform.Characteristic.Active, 1);
997
943
  }
998
944
  let success = false;
999
945
  // Try to set humidity using the appropriate method
@@ -1013,18 +959,16 @@ class HumidifierAccessory extends base_accessory_1.BaseAccessory {
1013
959
  if (!success) {
1014
960
  throw new Error(`Failed to set target humidity to ${targetHumidity}%`);
1015
961
  }
1016
- // Refresh device details to get the latest state
1017
- if (typeof extendedDevice.getDetails === 'function') {
1018
- await extendedDevice.getDetails();
1019
- // Log the target humidity after setting
1020
- const currentTarget = ((_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.target_humidity) || extendedDevice.targetHumidity;
1021
- this.platform.log.debug(`Device ${this.device.deviceName} target humidity after setting: ${currentTarget}%`);
962
+ // Push the commanded value to HomeKit and update in-memory state.
963
+ // Do NOT call getDetails/updateDeviceSpecificStates here — the API
964
+ // returns stale target humidity, causing the slider to snap back.
965
+ // The next polling cycle will eventually sync the actual value.
966
+ if (extendedDevice.details) {
967
+ extendedDevice.details.target_humidity = targetHumidity;
968
+ // Humid200300S's humidity getter reads auto_target_humidity
969
+ extendedDevice.details.auto_target_humidity = targetHumidity;
1022
970
  }
1023
- // Update the target humidity characteristic
1024
971
  this.service.updateCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold, targetHumidity);
1025
- // Update device state and characteristics
1026
- await this.updateDeviceSpecificStates(this.device);
1027
- // Persist the target humidity
1028
972
  await this.persistDeviceState('targetHumidity', targetHumidity);
1029
973
  }
1030
974
  catch (error) {