UNPKG

homebridge-rabbitair

Version:
269 lines (268 loc) 11.3 kB
import { RabbitAirClient, RabbitAirMode, RabbitAirQuality, RabbitAirSpeed } from './rabbitair-client.js'; import { AccessoryHandler } from 'hap-fluent'; /** * RabbitAir Platform Accessory * An instance of this class is created for each RabbitAir air purifier. */ export class RabbitAirAccessory extends AccessoryHandler { platform; accessory; client; updateInterval = null; initialUpdateTimeout = null; currentState = { active: false, currentAirPurifierState: 0, // Will be set in constructor targetAirPurifierState: 0, // Will be set in constructor rotationSpeed: 0, filterChangeIndication: 0, // Will be set in constructor filterLifeLevel: 100, airQuality: 0 // Will be set in constructor }; constructor(platform, accessory) { super(platform, accessory); this.platform = platform; this.accessory = accessory; // Initialize services // Initialize state values now that platform is available // Initialize the RabbitAir client this.client = new RabbitAirClient({ host: accessory.context.device.host, token: accessory.context.device.token, port: accessory.context.device.port }, this.platform.log); this.cleanup(); // Start periodic updates this.startPeriodicUpdates(); this.initialize({ accessoryInformation: {}, airPurifier: { active: this.currentState.active ? 1 /* Enums.Active.Active */ : 0 /* Enums.Active.Inactive */, currentAirPurifierState: this.currentState.currentAirPurifierState, targetAirPurifierState: this.currentState.targetAirPurifierState, rotationSpeed: this.currentState.rotationSpeed }, airQualitySensor: { airQuality: this.currentState.airQuality, vocDensity: 0, pm25Density: 0, pm10Density: 0 }, filterMaintenance: { filterChangeIndication: this.currentState.filterChangeIndication, filterLifeLevel: this.currentState.filterLifeLevel } }); // Defer service configuration to next tick to ensure services are initialized process.nextTick(() => { this.configureServices(); }); } /** * Configure service characteristics and event handlers */ configureServices() { // Type assertion to help TypeScript understand the services are initialized const services = this.services; if (services?.airQualitySensor?.characteristics?.AirQuality) { services.airQualitySensor.characteristics.AirQuality.setProps({ minValue: 0, maxValue: 6 }); } if (services?.airPurifier?.characteristics?.Active) { services.airPurifier.characteristics.Active.onSet(async (value) => { await this.client.setState({ power: value ? true : false }); }); } } /** * Start periodic updates of device state */ startPeriodicUpdates() { // Update state every 30 seconds this.updateInterval = setInterval(async () => { try { await this.updateDeviceState(); } catch (error) { this.platform.log.error('Error updating device state:', error); } }, 30000); // Initial state update this.initialUpdateTimeout = setTimeout(async () => { try { await this.updateDeviceState(); } catch (error) { this.platform.log.error('Error in initial state update:', error); } }, 5000); } /** * Stop periodic updates and cleanup resources */ stopPeriodicUpdates() { if (this.updateInterval) { clearInterval(this.updateInterval); this.updateInterval = null; } if (this.initialUpdateTimeout) { clearTimeout(this.initialUpdateTimeout); this.initialUpdateTimeout = null; } } /** * Cleanup resources */ async cleanup() { this.platform.log.debug('Cleaning up RabbitAir accessory'); this.stopPeriodicUpdates(); try { await this.client.shutdown(); } catch (error) { this.platform.log.error('Error during client shutdown:', error); } } /** * Update device state from RabbitAir device */ async updateDeviceState() { try { const state = await this.client.getState(); const services = this.services; services.airPurifier.active = state.power ? 1 /* Enums.Active.Active */ : 0 /* Enums.Active.Inactive */; // Update internal stat // Update current air purifier state based on power and speed if (!state.power) { services.airPurifier.currentAirPurifierState = 0 /* Enums.CurrentAirPurifierState.Inactive */; } else if (state.idle === RabbitAirSpeed.SuperSilent) { services.airPurifier.currentAirPurifierState = 1 /* Enums.CurrentAirPurifierState.Idle */; } else { services.airPurifier.currentAirPurifierState = 2 /* Enums.CurrentAirPurifierState.PurifyingAir */; } services.airPurifier.targetAirPurifierState = state.mode === RabbitAirMode.Auto ? 1 /* Enums.TargetAirPurifierState.Auto */ : 0 /* Enums.TargetAirPurifierState.Manual */; // Update target air purifier state based on mode // Update rotation speed services.airPurifier.rotationSpeed = state.speed || 0; // Update filter status if (state.filterReplacement) { services.filterMaintenance.filterChangeIndication = 1 /* Enums.FilterChangeIndication.ChangeFilter */; } else { services.filterMaintenance.filterChangeIndication = 0 /* Enums.FilterChangeIndication.FilterOk */; } // Update filter life level (convert from minutes to percentage) if (state.filterLife !== undefined) { services.filterMaintenance.filterLifeLevel = Math.max(0, Math.min(100, Math.round((state.filterLife / 525600) * 100))); } // Update air quality if (state.quality !== undefined) { switch (state.quality) { case RabbitAirQuality.Lowest: case RabbitAirQuality.Low: services.airQualitySensor.airQuality = 5 /* Enums.AirQuality.Poor */; break; case RabbitAirQuality.Medium: services.airQualitySensor.airQuality = 3 /* Enums.AirQuality.Fair */; break; case RabbitAirQuality.High: services.airQualitySensor.airQuality = 2 /* Enums.AirQuality.Good */; break; case RabbitAirQuality.Highest: services.airQualitySensor.airQuality = 1 /* Enums.AirQuality.Excellent */; break; default: services.airQualitySensor.airQuality = 0 /* Enums.AirQuality.Unknown */; } } const airQualityService = this.accessory.getService(this.platform.Service.AirQualitySensor); if (airQualityService) { airQualityService.updateCharacteristic(this.platform.Characteristic.AirQuality, this.currentState.airQuality); } } catch (error) { this.platform.log.error('Failed to update device state:', error); } } // Characteristic handlers async setActive(value) { const active = value; this.platform.log.debug('Set Active ->', active); try { await this.client.setState({ power: active }); } catch (error) { this.platform.log.error('Failed to set active state:', error); throw new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */); } } async getActive() { this.platform.log.debug('Get Active ->', this.currentState.active); return this.currentState.active; } async getCurrentAirPurifierState() { this.platform.log.debug('Get Current Air Purifier State ->', this.currentState.currentAirPurifierState); return this.currentState.currentAirPurifierState; } async setTargetAirPurifierState(value) { const targetState = value; this.platform.log.debug('Set Target Air Purifier State ->', targetState); try { if (targetState === this.platform.Characteristic.TargetAirPurifierState.AUTO) { await this.client.setState({ mode: RabbitAirMode.Auto }); } else { await this.client.setState({ mode: RabbitAirMode.Manual }); } this.currentState.targetAirPurifierState = targetState; } catch (error) { this.platform.log.error('Failed to set target air purifier state:', error); throw new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */); } } async getTargetAirPurifierState() { this.platform.log.debug('Get Target Air Purifier State ->', this.currentState.targetAirPurifierState); return this.currentState.targetAirPurifierState; } async setRotationSpeed(value) { const speed = value; this.platform.log.debug('Set Rotation Speed ->', speed); try { await this.client.setState({ speed: speed }); this.currentState.rotationSpeed = speed; } catch (error) { this.platform.log.error('Failed to set rotation speed:', error); throw new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */); } } async getRotationSpeed() { this.platform.log.debug('Get Rotation Speed ->', this.currentState.rotationSpeed); return this.currentState.rotationSpeed; } async getFilterChangeIndication() { this.platform.log.debug('Get Filter Change Indication ->', this.currentState.filterChangeIndication); return this.currentState.filterChangeIndication; } async getFilterLifeLevel() { this.platform.log.debug('Get Filter Life Level ->', this.currentState.filterLifeLevel); return this.currentState.filterLifeLevel; } async getAirQuality() { this.platform.log.debug('Get Air Quality ->', this.currentState.airQuality); return this.currentState.airQuality; } }