homebridge-tsvesync 1.0.123 โ 1.1.1
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/CHANGELOG.md +85 -0
- package/dist/accessories/air-purifier.accessory.d.ts +0 -17
- package/dist/accessories/air-purifier.accessory.d.ts.map +1 -1
- package/dist/accessories/air-purifier.accessory.js +74 -158
- package/dist/accessories/air-purifier.accessory.js.map +1 -1
- package/dist/accessories/air-quality-sensor.accessory.d.ts +70 -0
- package/dist/accessories/air-quality-sensor.accessory.d.ts.map +1 -0
- package/dist/accessories/air-quality-sensor.accessory.js +204 -0
- package/dist/accessories/air-quality-sensor.accessory.js.map +1 -0
- package/dist/platform.d.ts +8 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +67 -2
- package/dist/platform.js.map +1 -1
- package/dist/utils/device-factory.d.ts +2 -1
- package/dist/utils/device-factory.d.ts.map +1 -1
- package/dist/utils/device-factory.js +25 -0
- package/dist/utils/device-factory.js.map +1 -1
- package/package.json +2 -2
- package/src/accessories/air-purifier.accessory.ts +79 -202
- package/src/accessories/air-quality-sensor.accessory.ts +237 -0
- package/src/platform.ts +81 -2
- package/src/utils/device-factory.ts +35 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,90 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.1 (2025-08-30)
|
|
4
|
+
|
|
5
|
+
### Improved
|
|
6
|
+
- **โก Enhanced On/Off Responsiveness**: Significantly improved HomeKit control responsiveness
|
|
7
|
+
- **๐ฏ Instant UI Feedback**: HomeKit characteristics now update immediately when on/off commands are issued
|
|
8
|
+
- **โณ Background Processing**: API calls are processed in the background while users see immediate state changes
|
|
9
|
+
- **๐ก๏ธ Error Recovery**: Failed API calls now properly revert HomeKit state to maintain consistency
|
|
10
|
+
- **๐ฑ Better UX**: Users experience near-instantaneous response when toggling devices on/off in the Home app
|
|
11
|
+
|
|
12
|
+
- **๐ง Enhanced Speed Restoration**: Improved device speed handling when turning devices back on
|
|
13
|
+
- **๐ช๏ธ Air131 Support**: Air131 devices now properly restore their previous speed setting when turned on
|
|
14
|
+
- **โก All Device Types**: Enhanced speed restoration logic for all air purifier models
|
|
15
|
+
- **๐๏ธ Speed Preservation**: Device speed settings are maintained and restored correctly after power cycling
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **๐งช Test Environment Compatibility**: Fixed setPrimaryService() calls in testing environments
|
|
19
|
+
- **โ
Test Safety**: Added proper function existence check before calling setPrimaryService()
|
|
20
|
+
- **๐ง Development**: Ensures compatibility across different testing and development environments
|
|
21
|
+
- **๐ก๏ธ Defensive Coding**: Prevents errors when service methods are not available
|
|
22
|
+
|
|
23
|
+
### Technical Details
|
|
24
|
+
- **๐ฆ Immediate State Updates**: Uses updateCharacteristic() for instant HomeKit feedback
|
|
25
|
+
- **๐ Error Handling**: Comprehensive error recovery with state reversion on API failures
|
|
26
|
+
- **โก Performance**: Optimized to reduce perceived latency while maintaining API rate limits
|
|
27
|
+
- **๐ฏ Device State**: Enhanced device state persistence for consistent behavior across restarts
|
|
28
|
+
|
|
29
|
+
### Affected Devices
|
|
30
|
+
- **All Air Purifiers**: Improved on/off responsiveness and speed restoration
|
|
31
|
+
- **Air131 Models**: Enhanced speed restoration logic with immediate characteristic updates
|
|
32
|
+
- **Core Series**: Better HomeKit interaction consistency with instant feedback
|
|
33
|
+
|
|
34
|
+
## 1.1.0 (2025-08-29)
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- **๐ Separated Air Quality Sensors**: Air quality sensors now appear as independent HomeKit accessories
|
|
38
|
+
- **๐ฆ New AirQualitySensorAccessory Class**: Dedicated accessory for air quality monitoring with PM2.5 and PM10 support
|
|
39
|
+
- **๐ฏ Core300S, Core400S, Core600S**: Air quality sensors appear as separate tiles in HomeKit for better organization
|
|
40
|
+
- **๐ EPA AQI Standards**: Air quality levels calculated using official EPA Air Quality Index thresholds
|
|
41
|
+
- **๐ง Smart Detection**: Automatic PM2.5 and PM10 characteristic setup based on device capabilities
|
|
42
|
+
- **๐ Better UX**: Air purifier controls and air quality readings are now clearly separated in HomeKit
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **๐ฏ Core Series Filter Life Detection**: Enhanced filter life detection for all Core series air purifiers
|
|
46
|
+
- **โ
Core300S Filter Support**: Core300S devices now properly display filter life percentage in HomeKit
|
|
47
|
+
- **๐ง Pattern Matching**: Improved device type detection to catch variants like "300S", "200S", "400S", "600S"
|
|
48
|
+
- **๐ก๏ธ Defensive Logic**: Robust filter detection ensures all Core series devices get filter characteristics
|
|
49
|
+
- **๐ฑ HomeKit Integration**: Filter maintenance notifications now work correctly for all Core series models
|
|
50
|
+
|
|
51
|
+
- **๐จ Critical Air Quality Bug Fix**: Resolved phantom air quality services on devices without sensors
|
|
52
|
+
- **โก hasFeature() Fix**: Fixed API proxy wrapping causing hasFeature() to return Promise instead of boolean
|
|
53
|
+
- **๐ฏ Core200S Specific**: Core200S and LAP-C201S/C202S no longer show phantom air quality services
|
|
54
|
+
- **๐ง Bypass Logic**: Moved bypass check before async wrapper to prevent Promise wrapping of sync methods
|
|
55
|
+
- **โ
Accurate Detection**: Only devices with actual air quality sensors now show AQ characteristics
|
|
56
|
+
|
|
57
|
+
### Improved
|
|
58
|
+
- **๐ Reduced Log Verbosity**: Changed excessive info logging to debug level for cleaner operation
|
|
59
|
+
- **๐ Debug Level**: Feature detection, device type analysis, and configuration details moved to debug
|
|
60
|
+
- **โก Performance**: Reduced log noise during normal operation while maintaining troubleshooting capability
|
|
61
|
+
- **๐ฏ Focused Logging**: Important warnings and errors remain visible, routine operations moved to debug
|
|
62
|
+
|
|
63
|
+
- **๐๏ธ Enhanced HomeKit Service Architecture**: Proper service hierarchy for better HomeKit display
|
|
64
|
+
- **๐ฏ Primary Service**: AirPurifier service marked as primary to show controls instead of info page
|
|
65
|
+
- **๐ Linked Services**: Air quality sensors properly linked to maintain service relationships
|
|
66
|
+
- **๐ฑ HomeKit UX**: All air purifiers now show control interface when tapped in Home app
|
|
67
|
+
|
|
68
|
+
### Technical Details
|
|
69
|
+
- **๐ง API Proxy Enhancement**: Improved rate limiting with proper bypass handling for sync methods
|
|
70
|
+
- **๐ฆ Device Factory**: Enhanced device classification and feature detection logic
|
|
71
|
+
- **๐๏ธ Service Management**: Comprehensive service setup with primary/secondary hierarchy
|
|
72
|
+
- **๐ Enhanced Logging**: Detailed debugging information available when needed
|
|
73
|
+
|
|
74
|
+
### Affected Devices
|
|
75
|
+
- **Core Series**: Core200S, Core300S, Core400S, Core600S - improved filter detection and service setup
|
|
76
|
+
- **LAP Series**: LAP-C201S, LAP-C202S, LAP-C301S, LAP-C401S, LAP-C601S - fixed phantom AQ services
|
|
77
|
+
- **All Air Purifiers**: Better HomeKit service hierarchy and consistent behavior across models
|
|
78
|
+
|
|
79
|
+
### Migration Notes
|
|
80
|
+
- **๐ Automatic Upgrade**: Changes take effect after Homebridge restart, no configuration needed
|
|
81
|
+
- **๐ฑ HomeKit Changes**: Air quality sensors will appear as separate accessories for supported devices
|
|
82
|
+
- **๐ Backward Compatible**: All existing functionality preserved with enhanced capabilities
|
|
83
|
+
- **โ
Verification**: Check that Core series devices show filter life and separated AQ sensors work correctly
|
|
84
|
+
|
|
85
|
+
### Dependencies
|
|
86
|
+
- **๐ฆ tsvesync**: Updated to 1.1.0 for synchronized release versioning
|
|
87
|
+
|
|
3
88
|
## 1.0.123 (2025-08-29)
|
|
4
89
|
|
|
5
90
|
### Fixed
|
|
@@ -8,7 +8,6 @@ export declare class AirPurifierAccessory extends BaseAccessory {
|
|
|
8
8
|
private isAirBypassDevice;
|
|
9
9
|
private isAirBaseV2Device;
|
|
10
10
|
private isAir131Device;
|
|
11
|
-
private airQualityService?;
|
|
12
11
|
private lastSetSpeed;
|
|
13
12
|
private lastSetPercentage;
|
|
14
13
|
private skipNextUpdate;
|
|
@@ -55,22 +54,6 @@ export declare class AirPurifierAccessory extends BaseAccessory {
|
|
|
55
54
|
* Set up the air purifier service
|
|
56
55
|
*/
|
|
57
56
|
protected setupService(): void;
|
|
58
|
-
/**
|
|
59
|
-
* Set up the air quality sensor service
|
|
60
|
-
*/
|
|
61
|
-
private setupAirQualityService;
|
|
62
|
-
/**
|
|
63
|
-
* Get air quality level for HomeKit
|
|
64
|
-
*/
|
|
65
|
-
private getAirQuality;
|
|
66
|
-
/**
|
|
67
|
-
* Get PM2.5 density
|
|
68
|
-
*/
|
|
69
|
-
private getPM25Density;
|
|
70
|
-
/**
|
|
71
|
-
* Get PM10 density
|
|
72
|
-
*/
|
|
73
|
-
private getPM10Density;
|
|
74
57
|
/**
|
|
75
58
|
* Extract filter life from device data in a centralized way
|
|
76
59
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"air-purifier.accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/air-purifier.accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,iBAAiB,EAAW,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAkC9E,qBAAa,oBAAqB,SAAQ,aAAa;IACrD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAC7C,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"air-purifier.accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/air-purifier.accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,iBAAiB,EAAW,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAkC9E,qBAAa,oBAAqB,SAAQ,aAAa;IACrD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAC7C,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,cAAc,CAAkB;gBAGtC,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,iBAAiB,EAC5B,MAAM,EAAE,iBAAiB;IAiD3B;;OAEG;IACH,OAAO,CAAC,UAAU;IAqIlB;;OAEG;IACH,OAAO,CAAC,+BAA+B;IAmBvC;;OAEG;IACH,OAAO,CAAC,cAAc;IAiCtB;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAclC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgCzB;;;;;;;;;;;;OAYG;YACW,oCAAoC;IAoClD;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAsCzB;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,IAAI;IAqI9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0BzB;;OAEG;YACW,yBAAyB;IAOvC;;OAEG;YACW,kBAAkB;IAOhC;;OAEG;YACW,eAAe;IAI7B;;OAEG;IACH,SAAS,CAAC,qBAAqB,IAAI,kBAAkB;IAcrD;;OAEG;cACa,0BAA0B,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IA8GvE;;OAEG;YACW,cAAc;IAY5B;;OAEG;YACW,cAAc;IA2C5B;;OAEG;YACW,gBAAgB;IAqB9B;;OAEG;YACW,gBAAgB;YAmHhB,SAAS;YAIT,SAAS;CAoExB"}
|
|
@@ -25,11 +25,11 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
25
25
|
// Air131 Devices: LV-PUR131S, LV-RH131S
|
|
26
26
|
this.isAir131Device = deviceType.startsWith('LV-');
|
|
27
27
|
// Log device class detection with more detail
|
|
28
|
-
this.platform.log.
|
|
29
|
-
this.platform.log.
|
|
30
|
-
this.platform.log.
|
|
31
|
-
this.platform.log.
|
|
32
|
-
this.platform.log.
|
|
28
|
+
this.platform.log.debug(`Device Classification for ${this.device.deviceName}:`);
|
|
29
|
+
this.platform.log.debug(` - Device Type: "${deviceType}"`);
|
|
30
|
+
this.platform.log.debug(` - Is AirBypass: ${this.isAirBypassDevice}`);
|
|
31
|
+
this.platform.log.debug(` - Is AirBaseV2: ${this.isAirBaseV2Device}`);
|
|
32
|
+
this.platform.log.debug(` - Is Air131: ${this.isAir131Device}`);
|
|
33
33
|
if (this.isAirBypassDevice) {
|
|
34
34
|
this.platform.log.debug(`Detected AirBypass device: ${this.device.deviceName} (${deviceType})`);
|
|
35
35
|
}
|
|
@@ -42,13 +42,11 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
42
42
|
else {
|
|
43
43
|
this.platform.log.warn(`Unknown device class: ${this.device.deviceName} (${deviceType}) - device may not function properly`);
|
|
44
44
|
}
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.platform.log.debug(`${this.device.deviceName} (${deviceType}): Found cached air quality service on device without AQ sensor - will be removed during setup`);
|
|
51
|
-
}
|
|
45
|
+
// Remove any cached air quality service - these are now separate accessories
|
|
46
|
+
const cachedAirQualityService = this.accessory.getService(this.platform.Service.AirQualitySensor);
|
|
47
|
+
if (cachedAirQualityService) {
|
|
48
|
+
this.platform.log.debug(`${this.device.deviceName} (${deviceType}): Removing cached embedded AQ service - AQ sensors are now separate accessories`);
|
|
49
|
+
this.accessory.removeService(cachedAirQualityService);
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
/**
|
|
@@ -58,28 +56,28 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
58
56
|
var _a;
|
|
59
57
|
const extendedDevice = this.device;
|
|
60
58
|
// **ENHANCED DEBUGGING**: Log detailed device type information
|
|
61
|
-
this.platform.log.
|
|
59
|
+
this.platform.log.debug(`${this.device.deviceName}: FEATURE CHECK - hasFeature('${feature}') for device type: "${this.device.deviceType}"`);
|
|
62
60
|
// Use device's native hasFeature method if available
|
|
63
61
|
if (typeof extendedDevice.hasFeature === 'function') {
|
|
64
62
|
const result = extendedDevice.hasFeature(feature);
|
|
65
|
-
this.platform.log.
|
|
63
|
+
this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Native hasFeature('${feature}') returned: ${result} (using tsvesync library configuration)`);
|
|
66
64
|
// For air_quality, trust the library's configuration completely
|
|
67
65
|
// The library was updated in v1.0.107 to correctly exclude air_quality for devices without sensors
|
|
68
66
|
if (feature === 'air_quality') {
|
|
69
|
-
this.platform.log.
|
|
67
|
+
this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Air quality feature decision based on tsvesync library config: ${result}`);
|
|
70
68
|
// Add extra debugging for Core200S devices
|
|
71
69
|
if (this.device.deviceType.includes('Core200S') || this.device.deviceType.includes('LAP-C20')) {
|
|
72
|
-
this.platform.log.
|
|
70
|
+
this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Core200S variant detected - should NOT have air quality! hasFeature returned: ${result}`);
|
|
73
71
|
}
|
|
74
72
|
return result;
|
|
75
73
|
}
|
|
76
74
|
// **CRITICAL FIX**: Enhanced filter_life detection with explicit Core300S support
|
|
77
75
|
if (feature === 'filter_life') {
|
|
78
76
|
const deviceType = this.device.deviceType;
|
|
79
|
-
this.platform.log.
|
|
77
|
+
this.platform.log.debug(`${this.device.deviceName} (${deviceType}): Native hasFeature('filter_life') returned: ${result}`);
|
|
80
78
|
// **EXPLICIT Core300S CHECK**: Make sure Core300S is always recognized
|
|
81
79
|
if (deviceType === 'Core300S' || deviceType.includes('Core300S')) {
|
|
82
|
-
this.platform.log.
|
|
80
|
+
this.platform.log.debug(`${this.device.deviceName}: Core300S detected - SHOULD support filter_life! Native result: ${result}`);
|
|
83
81
|
if (!result) {
|
|
84
82
|
this.platform.log.warn(`${this.device.deviceName}: Core300S should support filter_life but native hasFeature returned false. OVERRIDING to true.`);
|
|
85
83
|
return true;
|
|
@@ -103,9 +101,9 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
103
101
|
// **EXPLICIT Core300S auto_mode CHECK**: Make sure Core300S auto mode is recognized
|
|
104
102
|
if (feature === 'auto_mode') {
|
|
105
103
|
const deviceType = this.device.deviceType;
|
|
106
|
-
this.platform.log.
|
|
104
|
+
this.platform.log.debug(`${this.device.deviceName} (${deviceType}): Native hasFeature('auto_mode') returned: ${result}`);
|
|
107
105
|
if (deviceType === 'Core300S' || deviceType.includes('Core300S')) {
|
|
108
|
-
this.platform.log.
|
|
106
|
+
this.platform.log.debug(`${this.device.deviceName}: Core300S detected - SHOULD support auto_mode! Native result: ${result}`);
|
|
109
107
|
if (!result) {
|
|
110
108
|
this.platform.log.warn(`${this.device.deviceName}: Core300S should support auto_mode but native hasFeature returned false. OVERRIDING to true.`);
|
|
111
109
|
return true;
|
|
@@ -350,18 +348,19 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
350
348
|
// Get or create the air purifier service
|
|
351
349
|
this.service = this.accessory.getService(this.platform.Service.AirPurifier) ||
|
|
352
350
|
this.accessory.addService(this.platform.Service.AirPurifier);
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
351
|
+
// Mark the AirPurifier service as primary
|
|
352
|
+
// setPrimaryService may not be available in test environment
|
|
353
|
+
if (typeof this.service.setPrimaryService === 'function') {
|
|
354
|
+
this.service.setPrimaryService(true);
|
|
355
|
+
this.platform.log.debug(`${this.device.deviceName}: Marked AirPurifier service as primary`);
|
|
356
|
+
}
|
|
358
357
|
// **CRITICAL FIX**: Add optional characteristics FIRST before setting up handlers
|
|
359
358
|
// This ensures they persist through service configuration
|
|
360
359
|
this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Pre-configuring optional characteristics`);
|
|
361
360
|
// Always set up filter characteristics (like reference plugin does)
|
|
362
361
|
// The reference implementation ALWAYS registers these for ALL air purifiers
|
|
363
362
|
// This ensures they appear in HomeKit even if the device doesn't report having them initially
|
|
364
|
-
this.platform.log.
|
|
363
|
+
this.platform.log.debug(`${this.device.deviceName}: Setting up filter characteristics for all air purifiers`);
|
|
365
364
|
// Use getCharacteristic which auto-adds if missing (like reference plugin)
|
|
366
365
|
this.service.getCharacteristic(this.platform.Characteristic.FilterChangeIndication)
|
|
367
366
|
.onGet(this.getFilterChangeIndication.bind(this));
|
|
@@ -372,28 +371,28 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
372
371
|
maxValue: 100,
|
|
373
372
|
minStep: 1
|
|
374
373
|
});
|
|
375
|
-
this.platform.log.
|
|
374
|
+
this.platform.log.debug(`${this.device.deviceName}: Filter characteristics registered (always present for air purifiers)`);
|
|
376
375
|
// Set up required characteristics
|
|
377
376
|
this.setupCharacteristic(this.platform.Characteristic.Active, this.getActive.bind(this), this.setActive.bind(this));
|
|
378
377
|
// Set up target state characteristic for mode mapping
|
|
379
378
|
const targetStateChar = this.service.getCharacteristic(this.platform.Characteristic.TargetAirPurifierState);
|
|
380
379
|
// Check if device supports auto mode
|
|
381
380
|
const hasAutoMode = this.hasFeature('auto_mode');
|
|
382
|
-
this.platform.log.
|
|
381
|
+
this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Auto mode support check - hasFeature('auto_mode') = ${hasAutoMode}`);
|
|
383
382
|
// For Core200S, only allow manual mode
|
|
384
383
|
if (this.device.deviceType.includes('Core200S')) {
|
|
385
384
|
targetStateChar.setProps({
|
|
386
385
|
validValues: [0] // MANUAL only
|
|
387
386
|
});
|
|
388
|
-
this.platform.log.
|
|
387
|
+
this.platform.log.debug(`${this.device.deviceName} (Core200S): Configured for MANUAL mode only - no auto mode support`);
|
|
389
388
|
}
|
|
390
389
|
else if (this.device.deviceType.includes('Core300S')) {
|
|
391
390
|
// Core300S explicitly supports both modes
|
|
392
391
|
targetStateChar.setProps({
|
|
393
392
|
validValues: [0, 1] // MANUAL and AUTO
|
|
394
393
|
});
|
|
395
|
-
this.platform.log.
|
|
396
|
-
this.platform.log.
|
|
394
|
+
this.platform.log.debug(`${this.device.deviceName} (Core300S): โ
Configured for BOTH AUTO and MANUAL modes - auto mode ENABLED`);
|
|
395
|
+
this.platform.log.debug(`${this.device.deviceName} (Core300S): Target state characteristic should now show mode switcher in HomeKit`);
|
|
397
396
|
}
|
|
398
397
|
else if (hasAutoMode) {
|
|
399
398
|
// Other devices with auto_mode feature
|
|
@@ -407,7 +406,7 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
407
406
|
targetStateChar.setProps({
|
|
408
407
|
validValues: [0] // MANUAL only
|
|
409
408
|
});
|
|
410
|
-
this.platform.log.
|
|
409
|
+
this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Configured for MANUAL mode only - no auto mode feature`);
|
|
411
410
|
}
|
|
412
411
|
// Set up the characteristic handlers for all devices
|
|
413
412
|
this.setupCharacteristic(this.platform.Characteristic.TargetAirPurifierState, this.getTargetState.bind(this), this.setTargetState.bind(this));
|
|
@@ -426,104 +425,24 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
426
425
|
this.platform.log.debug(`${this.device.deviceName}: Configured RotationSpeed with minStep: ${minStep}`);
|
|
427
426
|
// Add Name characteristic
|
|
428
427
|
this.setupCharacteristic(this.platform.Characteristic.Name, async () => this.device.deviceName);
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
// Always check for existing air quality service first
|
|
428
|
+
// Air quality sensors are now separate accessories
|
|
429
|
+
// Remove any legacy embedded AQ service
|
|
432
430
|
const existingAirQualityService = this.accessory.getService(this.platform.Service.AirQualitySensor);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (hasAirQuality) {
|
|
437
|
-
this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Device has air quality sensor - setting up air quality service`);
|
|
438
|
-
this.setupAirQualityService();
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
this.platform.log.debug(`${this.device.deviceName}: Device does not have air quality sensor - removing any air quality service`);
|
|
442
|
-
// Remove any existing air quality service and clear the reference
|
|
443
|
-
if (existingAirQualityService) {
|
|
444
|
-
this.platform.log.debug(`${this.device.deviceName}: Found existing air quality service on device without AQ sensor - removing it`);
|
|
445
|
-
this.accessory.removeService(existingAirQualityService);
|
|
446
|
-
// Clear the reference to ensure it's not used elsewhere
|
|
447
|
-
this.airQualityService = undefined;
|
|
448
|
-
}
|
|
449
|
-
// Ensure the reference is cleared even if no service was found
|
|
450
|
-
this.airQualityService = undefined;
|
|
431
|
+
if (existingAirQualityService) {
|
|
432
|
+
this.platform.log.debug(`${this.device.deviceName}: Removing legacy embedded air quality service`);
|
|
433
|
+
this.accessory.removeService(existingAirQualityService);
|
|
451
434
|
}
|
|
452
435
|
// Clean up any existing FilterMaintenance service (from old implementation)
|
|
453
436
|
const existingFilterService = this.accessory.getService(this.platform.Service.FilterMaintenance);
|
|
454
437
|
if (existingFilterService) {
|
|
455
|
-
this.platform.log.
|
|
438
|
+
this.platform.log.debug(`${this.device.deviceName}: Removing old FilterMaintenance service (migrating to AirPurifier service)`);
|
|
456
439
|
this.accessory.removeService(existingFilterService);
|
|
457
440
|
}
|
|
458
441
|
// Check and log important features for debugging
|
|
459
442
|
const autoModeSupported = this.hasFeature('auto_mode');
|
|
460
|
-
this.platform.log.
|
|
461
|
-
this.platform.log.
|
|
462
|
-
this.platform.log.
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Set up the air quality sensor service
|
|
466
|
-
*/
|
|
467
|
-
setupAirQualityService() {
|
|
468
|
-
var _a;
|
|
469
|
-
this.platform.log.debug(`Setting up air quality service for ${this.device.deviceName}`);
|
|
470
|
-
// Get or create the air quality sensor service
|
|
471
|
-
this.airQualityService = this.accessory.getService(this.platform.Service.AirQualitySensor) ||
|
|
472
|
-
this.accessory.addService(this.platform.Service.AirQualitySensor);
|
|
473
|
-
// Mark this as a linked service (not primary)
|
|
474
|
-
// The AirPurifier service is the primary service
|
|
475
|
-
if (this.service) {
|
|
476
|
-
this.airQualityService.addLinkedService(this.service);
|
|
477
|
-
this.platform.log.info(`${this.device.deviceName}: Linked AirQualitySensor service to PRIMARY AirPurifier service`);
|
|
478
|
-
}
|
|
479
|
-
// Set up required air quality characteristic
|
|
480
|
-
this.setupCharacteristic(this.platform.Characteristic.AirQuality, this.getAirQuality.bind(this), undefined, {}, this.airQualityService);
|
|
481
|
-
// Set up optional PM2.5 density characteristic
|
|
482
|
-
this.setupCharacteristic(this.platform.Characteristic.PM2_5Density, this.getPM25Density.bind(this), undefined, {
|
|
483
|
-
minValue: 0,
|
|
484
|
-
maxValue: 1000,
|
|
485
|
-
minStep: 1
|
|
486
|
-
}, this.airQualityService);
|
|
487
|
-
// Set up optional PM10 density characteristic (if available)
|
|
488
|
-
const extendedDevice = this.device;
|
|
489
|
-
if (((_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.pm10) !== undefined) {
|
|
490
|
-
this.setupCharacteristic(this.platform.Characteristic.PM10Density, this.getPM10Density.bind(this), undefined, {
|
|
491
|
-
minValue: 0,
|
|
492
|
-
maxValue: 1000,
|
|
493
|
-
minStep: 1
|
|
494
|
-
}, this.airQualityService);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Get air quality level for HomeKit
|
|
499
|
-
*/
|
|
500
|
-
async getAirQuality() {
|
|
501
|
-
var _a, _b, _c, _d;
|
|
502
|
-
const extendedDevice = this.device;
|
|
503
|
-
// Get PM2.5 value from device
|
|
504
|
-
const pm25 = (_d = (_b = (_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.air_quality_value) !== null && _b !== void 0 ? _b : (_c = extendedDevice.details) === null || _c === void 0 ? void 0 : _c.pm25) !== null && _d !== void 0 ? _d : 0;
|
|
505
|
-
// Convert to HomeKit air quality scale using existing method
|
|
506
|
-
return this.convertAirQualityToHomeKit(pm25);
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* Get PM2.5 density
|
|
510
|
-
*/
|
|
511
|
-
async getPM25Density() {
|
|
512
|
-
var _a, _b, _c, _d;
|
|
513
|
-
const extendedDevice = this.device;
|
|
514
|
-
const pm25 = (_d = (_b = (_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.air_quality_value) !== null && _b !== void 0 ? _b : (_c = extendedDevice.details) === null || _c === void 0 ? void 0 : _c.pm25) !== null && _d !== void 0 ? _d : 0;
|
|
515
|
-
// Ensure value is within HomeKit limits (0-1000)
|
|
516
|
-
return Math.min(1000, Math.max(0, pm25));
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Get PM10 density
|
|
520
|
-
*/
|
|
521
|
-
async getPM10Density() {
|
|
522
|
-
var _a, _b;
|
|
523
|
-
const extendedDevice = this.device;
|
|
524
|
-
const pm10 = (_b = (_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.pm10) !== null && _b !== void 0 ? _b : 0;
|
|
525
|
-
// Ensure value is within HomeKit limits (0-1000)
|
|
526
|
-
return Math.min(1000, Math.max(0, pm10));
|
|
443
|
+
this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Features detected:`);
|
|
444
|
+
this.platform.log.debug(` - auto_mode: ${autoModeSupported} (controls mode switch)`);
|
|
445
|
+
this.platform.log.debug(` - filter_life: ${this.hasFeature('filter_life')} (controls filter display)`);
|
|
527
446
|
}
|
|
528
447
|
/**
|
|
529
448
|
* Extract filter life from device data in a centralized way
|
|
@@ -577,7 +496,7 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
577
496
|
getDeviceCapabilities() {
|
|
578
497
|
return {
|
|
579
498
|
hasSpeed: true,
|
|
580
|
-
hasAirQuality:
|
|
499
|
+
hasAirQuality: false,
|
|
581
500
|
hasChildLock: false,
|
|
582
501
|
hasBrightness: false,
|
|
583
502
|
hasColorTemp: false,
|
|
@@ -591,7 +510,7 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
591
510
|
* Update device states based on the latest details
|
|
592
511
|
*/
|
|
593
512
|
async updateDeviceSpecificStates(details) {
|
|
594
|
-
var _a, _b
|
|
513
|
+
var _a, _b;
|
|
595
514
|
// Update power state
|
|
596
515
|
const isOn = details.enabled || details.deviceStatus === 'on';
|
|
597
516
|
this.service.updateCharacteristic(this.platform.Characteristic.Active, isOn ? 1 : 0);
|
|
@@ -653,40 +572,15 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
653
572
|
this.lastSetSpeed = 0;
|
|
654
573
|
this.lastSetPercentage = 0;
|
|
655
574
|
}
|
|
656
|
-
//
|
|
657
|
-
|
|
658
|
-
// Device supports air quality - update characteristics if service exists
|
|
659
|
-
if (this.airQualityService) {
|
|
660
|
-
const extendedDevice = this.device;
|
|
661
|
-
// Update air quality level
|
|
662
|
-
const pm25 = (_d = (_b = (_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.air_quality_value) !== null && _b !== void 0 ? _b : (_c = extendedDevice.details) === null || _c === void 0 ? void 0 : _c.pm25) !== null && _d !== void 0 ? _d : 0;
|
|
663
|
-
const airQualityLevel = this.convertAirQualityToHomeKit(pm25);
|
|
664
|
-
this.airQualityService.updateCharacteristic(this.platform.Characteristic.AirQuality, airQualityLevel);
|
|
665
|
-
// Update PM2.5 density
|
|
666
|
-
this.airQualityService.updateCharacteristic(this.platform.Characteristic.PM2_5Density, Math.min(1000, Math.max(0, pm25)));
|
|
667
|
-
// Update PM10 density if available
|
|
668
|
-
if (((_e = extendedDevice.details) === null || _e === void 0 ? void 0 : _e.pm10) !== undefined) {
|
|
669
|
-
this.airQualityService.updateCharacteristic(this.platform.Characteristic.PM10Density, Math.min(1000, Math.max(0, extendedDevice.details.pm10)));
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
else {
|
|
674
|
-
// Device does not support air quality - remove any existing service
|
|
675
|
-
// Check both our reference and the actual accessory for any air quality service
|
|
676
|
-
const existingService = this.airQualityService || this.accessory.getService(this.platform.Service.AirQualitySensor);
|
|
677
|
-
if (existingService) {
|
|
678
|
-
this.platform.log.debug(`${this.device.deviceName}: Device does not support air quality but service exists - removing it now`);
|
|
679
|
-
this.accessory.removeService(existingService);
|
|
680
|
-
this.airQualityService = undefined;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
575
|
+
// Air quality is now handled by separate accessories
|
|
576
|
+
// No need to update AQ characteristics here
|
|
683
577
|
// Update filter characteristics on main service if supported
|
|
684
578
|
if (this.hasFeature('filter_life')) {
|
|
685
579
|
const filterLife = this.extractFilterLife();
|
|
686
580
|
// Update filter change indication on main service
|
|
687
|
-
(
|
|
581
|
+
(_a = this.service) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.platform.Characteristic.FilterChangeIndication, filterLife < 10 ? 1 : 0);
|
|
688
582
|
// Update filter life level on main service
|
|
689
|
-
(
|
|
583
|
+
(_b = this.service) === null || _b === void 0 ? void 0 : _b.updateCharacteristic(this.platform.Characteristic.FilterLifeLevel, Math.min(100, Math.max(0, filterLife)));
|
|
690
584
|
}
|
|
691
585
|
}
|
|
692
586
|
/**
|
|
@@ -870,6 +764,11 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
870
764
|
try {
|
|
871
765
|
const isOn = value === 1;
|
|
872
766
|
this.platform.log.debug(`Setting device ${this.device.deviceName} to ${isOn ? 'on' : 'off'}`);
|
|
767
|
+
// Immediately update HomeKit characteristics for better responsiveness
|
|
768
|
+
// This provides instant feedback to the user while the API call is made
|
|
769
|
+
this.service.updateCharacteristic(this.platform.Characteristic.Active, isOn ? 1 : 0);
|
|
770
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentAirPurifierState, isOn ? 2 : 0 // 2 = PURIFYING_AIR, 0 = INACTIVE
|
|
771
|
+
);
|
|
873
772
|
const success = isOn ? await this.device.turnOn() : await this.device.turnOff();
|
|
874
773
|
if (!success) {
|
|
875
774
|
// For LV-PUR131S, success=false might mean device is already in desired state
|
|
@@ -882,21 +781,38 @@ class AirPurifierAccessory extends base_accessory_1.BaseAccessory {
|
|
|
882
781
|
return; // Don't throw error if already in desired state
|
|
883
782
|
}
|
|
884
783
|
}
|
|
784
|
+
// If the API call failed, revert the HomeKit state
|
|
785
|
+
this.platform.log.warn(`Failed to turn ${isOn ? 'on' : 'off'} device, reverting HomeKit state`);
|
|
786
|
+
this.service.updateCharacteristic(this.platform.Characteristic.Active, isOn ? 0 : 1);
|
|
787
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentAirPurifierState, isOn ? 0 : 2);
|
|
885
788
|
throw new Error(`Failed to turn ${isOn ? 'on' : 'off'} device`);
|
|
886
789
|
}
|
|
887
790
|
// Reset tracking variables when turning off
|
|
888
791
|
if (!isOn) {
|
|
889
792
|
this.lastSetSpeed = 0;
|
|
890
793
|
this.lastSetPercentage = 0;
|
|
794
|
+
// Also immediately update rotation speed to 0
|
|
795
|
+
this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, 0);
|
|
891
796
|
}
|
|
892
|
-
else if (isOn
|
|
893
|
-
// For
|
|
894
|
-
|
|
895
|
-
|
|
797
|
+
else if (isOn) {
|
|
798
|
+
// For all air purifiers when turning on, restore the rotation speed
|
|
799
|
+
if (this.isAir131Device) {
|
|
800
|
+
// For Air131 devices, immediately update rotation speed characteristic
|
|
801
|
+
// to show the restored speed instead of 0
|
|
802
|
+
await this.updateAir131RotationSpeedAfterTurnOn();
|
|
803
|
+
}
|
|
804
|
+
else if (this.device.speed && this.device.speed > 0) {
|
|
805
|
+
// For other devices, also update the speed immediately
|
|
806
|
+
const percentage = this.speedToPercentage(this.device.speed);
|
|
807
|
+
this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, percentage);
|
|
808
|
+
}
|
|
896
809
|
}
|
|
897
|
-
//
|
|
898
|
-
|
|
810
|
+
// Don't call updateDeviceSpecificStates to avoid polling the API
|
|
811
|
+
// The device state is already reflected in HomeKit through our immediate updates
|
|
812
|
+
// await this.updateDeviceSpecificStates(this.device);
|
|
813
|
+
// Still persist the state for recovery after restarts
|
|
899
814
|
await this.persistDeviceState('deviceStatus', isOn ? 'on' : 'off');
|
|
815
|
+
this.platform.log.debug(`${this.device.deviceName}: Successfully turned ${isOn ? 'on' : 'off'} with immediate HomeKit feedback`);
|
|
900
816
|
}
|
|
901
817
|
catch (error) {
|
|
902
818
|
this.handleDeviceError('set active state', error);
|