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.
- package/AGENTS.md +31 -0
- package/CHANGELOG.md +27 -0
- package/dist/accessories/humidifier.accessory.d.ts +22 -0
- package/dist/accessories/humidifier.accessory.d.ts.map +1 -1
- package/dist/accessories/humidifier.accessory.js +163 -219
- package/dist/accessories/humidifier.accessory.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/accessories/humidifier-write-consistency.test.ts +516 -0
- package/src/accessories/humidifier.accessory.ts +219 -313
- package/tsconfig.json +2 -1
- package/dist/__tests__/setup.d.ts +0 -10
- package/dist/__tests__/setup.d.ts.map +0 -1
- package/dist/__tests__/setup.js +0 -82
- package/dist/__tests__/setup.js.map +0 -1
- package/dist/__tests__/utils/test-helpers.d.ts +0 -279
- package/dist/__tests__/utils/test-helpers.d.ts.map +0 -1
- package/dist/__tests__/utils/test-helpers.js +0 -831
- package/dist/__tests__/utils/test-helpers.js.map +0 -1
|
@@ -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.
|
|
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') ||
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
231
|
+
// CurrentRelativeHumidity getter
|
|
158
232
|
if (this.capabilities && this.capabilities.hasHumidity) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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
|
-
|
|
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;
|
|
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;
|
|
405
|
-
// In auto mode, check if we need to be humidifying
|
|
452
|
+
45;
|
|
406
453
|
if (currentHumidity < targetHumidity) {
|
|
407
|
-
currentState = 2;
|
|
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;
|
|
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
|
-
|
|
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.
|
|
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;
|
|
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;
|
|
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;
|
|
581
|
+
100;
|
|
560
582
|
this.filterService.updateCharacteristic(this.platform.Characteristic.FilterLifeLevel, filterLife);
|
|
561
|
-
this.filterService.updateCharacteristic(this.platform.Characteristic.FilterChangeIndication, filterLife < 10 ? 1 : 0
|
|
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
|
-
|
|
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
|
-
|
|
683
|
-
this.
|
|
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
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
|
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
|
-
//
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
|
816
|
+
// Convert percentage to mist level based on device type
|
|
888
817
|
let speed;
|
|
889
|
-
if (
|
|
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
|
-
//
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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) {
|