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 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,iBAAiB,CAAC,CAAU;IACpC,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;IAmD3B;;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;IAqJ9B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAsD9B;;OAEG;YACW,aAAa;IAW3B;;OAEG;YACW,cAAc;IAU5B;;OAEG;YACW,cAAc;IAS5B;;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;IAoJvE;;OAEG;YACW,cAAc;IAY5B;;OAEG;YACW,cAAc;IA2C5B;;OAEG;YACW,gBAAgB;IAqB9B;;OAEG;YACW,gBAAgB;YAmHhB,SAAS;YAIT,SAAS;CAsCxB"}
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.info(`Device Classification for ${this.device.deviceName}:`);
29
- this.platform.log.info(` - Device Type: "${deviceType}"`);
30
- this.platform.log.info(` - Is AirBypass: ${this.isAirBypassDevice}`);
31
- this.platform.log.info(` - Is AirBaseV2: ${this.isAirBaseV2Device}`);
32
- this.platform.log.info(` - Is Air131: ${this.isAir131Device}`);
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
- // Early check for Core200S and similar devices without air quality
46
- // This ensures we catch cached services even before setupService() is called
47
- if (!this.hasFeature('air_quality')) {
48
- const cachedAirQualityService = this.accessory.getService(this.platform.Service.AirQualitySensor);
49
- if (cachedAirQualityService) {
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.info(`${this.device.deviceName}: FEATURE CHECK - hasFeature('${feature}') for device type: "${this.device.deviceType}"`);
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.info(`${this.device.deviceName} (${this.device.deviceType}): Native hasFeature('${feature}') returned: ${result} (using tsvesync library configuration)`);
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.info(`${this.device.deviceName} (${this.device.deviceType}): Air quality feature decision based on tsvesync library config: ${result}`);
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.info(`${this.device.deviceName} (${this.device.deviceType}): Core200S variant detected - should NOT have air quality! hasFeature returned: ${result}`);
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.info(`${this.device.deviceName} (${deviceType}): Native hasFeature('filter_life') returned: ${result}`);
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.info(`${this.device.deviceName}: Core300S detected - SHOULD support filter_life! Native result: ${result}`);
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.info(`${this.device.deviceName} (${deviceType}): Native hasFeature('auto_mode') returned: ${result}`);
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.info(`${this.device.deviceName}: Core300S detected - SHOULD support auto_mode! Native result: ${result}`);
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
- // CRITICAL: Mark the AirPurifier service as primary
354
- // This ensures HomeKit shows the controls instead of the info page
355
- // when there are multiple services (e.g., with AirQualitySensor)
356
- this.service.setPrimaryService(true);
357
- this.platform.log.info(`${this.device.deviceName}: Marked AirPurifier service as PRIMARY service`);
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.info(`${this.device.deviceName}: Setting up filter characteristics for all air purifiers`);
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.info(`${this.device.deviceName}: Filter characteristics registered (always present for air purifiers)`);
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.info(`${this.device.deviceName} (${this.device.deviceType}): Auto mode support check - hasFeature('auto_mode') = ${hasAutoMode}`);
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.info(`${this.device.deviceName} (Core200S): Configured for MANUAL mode only - no auto mode support`);
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.info(`${this.device.deviceName} (Core300S): โœ… Configured for BOTH AUTO and MANUAL modes - auto mode ENABLED`);
396
- this.platform.log.info(`${this.device.deviceName} (Core300S): Target state characteristic should now show mode switcher in HomeKit`);
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.info(`${this.device.deviceName} (${this.device.deviceType}): Configured for MANUAL mode only - no auto mode feature`);
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
- // Set up air quality sensor service if supported
430
- this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): Checking air_quality feature...`);
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
- // Check if device supports air quality
434
- const hasAirQuality = this.hasFeature('air_quality');
435
- this.platform.log.debug(`${this.device.deviceName} (${this.device.deviceType}): hasFeature('air_quality') returned ${hasAirQuality}`);
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.info(`${this.device.deviceName}: Removing old FilterMaintenance service (migrating to AirPurifier service)`);
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.info(`${this.device.deviceName} (${this.device.deviceType}): Features detected:`);
461
- this.platform.log.info(` - auto_mode: ${autoModeSupported} (controls mode switch)`);
462
- this.platform.log.info(` - filter_life: ${this.hasFeature('filter_life')} (controls filter display)`);
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: this.hasFeature('air_quality'),
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, _c, _d, _e, _f, _g;
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
- // Update air quality characteristics if service exists AND device supports air quality
657
- if (this.hasFeature('air_quality')) {
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
- (_f = this.service) === null || _f === void 0 ? void 0 : _f.updateCharacteristic(this.platform.Characteristic.FilterChangeIndication, filterLife < 10 ? 1 : 0);
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
- (_g = this.service) === null || _g === void 0 ? void 0 : _g.updateCharacteristic(this.platform.Characteristic.FilterLifeLevel, Math.min(100, Math.max(0, filterLife)));
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 && this.isAir131Device) {
893
- // For Air131 devices, immediately update rotation speed characteristic
894
- // to show the restored speed instead of 0
895
- await this.updateAir131RotationSpeedAfterTurnOn();
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
- // Update device state and characteristics
898
- await this.updateDeviceSpecificStates(this.device);
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);