homebridge-rabbitair
Version:
Homebridge plugin for RabbitAir air purifiers
269 lines (268 loc) • 11.3 kB
JavaScript
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;
}
}