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.
- package/dist/accessories/air-purifier.accessory.d.ts +38 -5
- package/dist/accessories/air-purifier.accessory.d.ts.map +1 -1
- package/dist/accessories/air-purifier.accessory.js +421 -132
- package/dist/accessories/air-purifier.accessory.js.map +1 -1
- package/dist/platform.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/accessories/air-purifier.accessory.ts +505 -118
- package/src/platform.ts +1 -1
|
@@ -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
|
|
13
|
+
// Detect device class based on model number patterns
|
|
12
14
|
const extendedDevice = this.device;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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.
|
|
29
|
-
//
|
|
30
|
-
|
|
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:
|
|
41
|
-
hasChildLock:
|
|
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 (
|
|
62
|
-
//
|
|
63
|
-
let targetState =
|
|
64
|
-
if (
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
if (this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
//
|
|
125
|
-
|
|
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 ===
|
|
421
|
+
if (targetState === 1 && typeof extendedDevice.autoMode === 'function') {
|
|
130
422
|
success = await extendedDevice.autoMode();
|
|
131
423
|
}
|
|
132
|
-
else if (targetState ===
|
|
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
|
-
*
|
|
447
|
+
* Get rotation speed
|
|
156
448
|
*/
|
|
157
|
-
async
|
|
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
|
-
|
|
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
|
-
//
|
|
172
|
-
if (this.
|
|
173
|
-
this.platform.log.
|
|
174
|
-
//
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|