homebridge-levoit-humidifiers
Version:
Homebridge plugin for Levoit Humidifiers
336 lines • 15.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const CurrentState_1 = __importDefault(require("./characteristics/CurrentState"));
const Humidity_1 = __importDefault(require("./characteristics/Humidity"));
const Active_1 = __importDefault(require("./characteristics/Active"));
const MistLevel_1 = __importDefault(require("./characteristics/MistLevel"));
const TargetState_1 = __importDefault(require("./characteristics/TargetState"));
const SleepState_1 = __importDefault(require("./characteristics/SleepState"));
const LightBrightness_1 = __importDefault(require("./characteristics/LightBrightness"));
const DisplayState_1 = __importDefault(require("./characteristics/DisplayState"));
const TargetHumidity_1 = __importDefault(require("./characteristics/TargetHumidity"));
const LightState_1 = __importDefault(require("./characteristics/LightState"));
const WarmMistLevel_1 = __importDefault(require("./characteristics/WarmMistLevel"));
const WarmActive_1 = __importDefault(require("./characteristics/WarmActive"));
const AutoProState_1 = __importDefault(require("./characteristics/AutoProState"));
const HumidifierName = 'Humidifier';
const HumiditySensorName = 'Humidity Sensor';
const MistName = 'Mist';
const CoolMistName = 'Cool Mist';
const WarmMistName = 'Warm Mist';
const NightLightName = 'Night Light';
const SleepModeName = 'Sleep Mode';
const DisplayName = 'Display';
const AutoProModeName = 'AutoPro Mode';
/**
* VeSyncAccessory represents a single Levoit humidifier device in HomeKit.
* It manages all services and characteristics for the device, including background
* polling to keep device state synchronized without blocking HomeKit read requests.
*/
class VeSyncAccessory {
get UUID() {
return this.device.uuid.toString();
}
get device() {
return this.accessory.context.device;
}
constructor(platform, accessory) {
this.platform = platform;
this.accessory = accessory;
/**
* Background polling interval to keep device state fresh.
* Polls every 30 seconds to balance freshness with API quota limits.
* The VeSync API has daily quotas (3200 + 1500 * device count), so we
* need to be conservative with polling frequency.
*/
this.pollingInterval = null;
this.POLLING_INTERVAL_MS = 30000; // 30 seconds
const config = platform.config;
const accessories = config.accessories ? config.accessories : {};
this.setupAccessoryInfo();
this.humidifierService = this.setupHumidifierService();
this.mistService = this.setupMistService(accessories);
this.displayService = this.setupDisplayService(accessories);
this.humiditySensorService = this.setupHumiditySensorService(accessories);
this.warmMistService = this.setupWarmMistService(accessories);
this.sleepService = this.setupSleepService(accessories);
this.lightService = this.setupLightService(accessories);
this.autoProService = this.setupAutoProService(accessories);
this.startPolling();
}
setupAccessoryInfo() {
const { manufacturer, model, mac } = this.device;
this.accessory
.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Manufacturer, manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, mac);
}
setupHumidifierService() {
const service = this.accessory.getService(HumidifierName) ||
this.accessory.addService(this.platform.Service.HumidifierDehumidifier, HumidifierName, HumidifierName);
service.setPrimaryService(true);
service
.getCharacteristic(this.platform.Characteristic.Active)
.onGet(Active_1.default.get.bind(this))
.onSet(Active_1.default.set.bind(this));
service
.getCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState)
.setProps({ validValues: [1] })
.onGet(TargetState_1.default.get.bind(this));
service
.getCharacteristic(this.platform.Characteristic.CurrentHumidifierDehumidifierState)
.setProps({ validValues: [1, 2] })
.onGet(CurrentState_1.default.get.bind(this));
service
.getCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold)
.setProps({ minStep: 1, minValue: 0, maxValue: 100 })
.onGet(TargetHumidity_1.default.get.bind(this))
.onSet(TargetHumidity_1.default.set.bind(this));
service
.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.onGet(Humidity_1.default.get.bind(this));
return service;
}
/**
* Gets or creates a service, updating its name.
* Returns undefined and removes the service if the accessory config disables it.
*/
getOrCreateService(serviceType, name, enabled, fallbackName) {
if (enabled) {
let service = this.accessory.getService(name);
service !== null && service !== void 0 ? service : (service = this.accessory.addService(serviceType, name, name));
service.updateCharacteristic(this.platform.Characteristic.Name, name);
return service;
}
const existing = this.accessory.getService(name) ||
(fallbackName ? this.accessory.getService(fallbackName) : undefined);
if (existing) {
this.platform.log.info(`Removing ${name} service.`);
this.accessory.removeService(existing);
}
return undefined;
}
setupMistService(accessories) {
const enabled = accessories.mist !== false && accessories.cool_mist !== false;
const service = this.getOrCreateService(this.platform.Service.Fan, MistName, enabled, CoolMistName);
if (!service) {
return undefined;
}
service
.getCharacteristic(this.platform.Characteristic.On)
.onGet(Active_1.default.get.bind(this))
.onSet(Active_1.default.set.bind(this));
service
.getCharacteristic(this.platform.Characteristic.RotationSpeed)
.setProps({ minStep: 1, minValue: 0, maxValue: 100 })
.onGet(MistLevel_1.default.get.bind(this))
.onSet(MistLevel_1.default.set.bind(this));
this.humidifierService.addLinkedService(service);
return service;
}
setupDisplayService(accessories) {
const service = this.getOrCreateService(this.platform.Service.Switch, DisplayName, accessories.display !== false);
if (!service) {
return undefined;
}
this.humidifierService.addLinkedService(service);
service
.getCharacteristic(this.platform.Characteristic.On)
.onGet(DisplayState_1.default.get.bind(this))
.onSet(DisplayState_1.default.set.bind(this));
return service;
}
setupHumiditySensorService(accessories) {
const service = this.getOrCreateService(this.platform.Service.HumiditySensor, HumiditySensorName, accessories.humidity_sensor !== false);
if (!service) {
return undefined;
}
this.humidifierService.addLinkedService(service);
service
.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.onGet(Humidity_1.default.get.bind(this));
return service;
}
setupWarmMistService(accessories) {
const enabled = this.device.deviceType.hasWarmMode && accessories.warm_mist !== false;
const service = this.getOrCreateService(this.platform.Service.Fan, WarmMistName, enabled);
if (!service) {
return undefined;
}
this.humidifierService.addLinkedService(service);
service
.getCharacteristic(this.platform.Characteristic.On)
.onGet(WarmActive_1.default.get.bind(this))
.onSet(WarmActive_1.default.set.bind(this));
service
.getCharacteristic(this.platform.Characteristic.RotationSpeed)
.setProps({ minStep: 1, minValue: 0, maxValue: 100 })
.onGet(WarmMistLevel_1.default.get.bind(this))
.onSet(WarmMistLevel_1.default.set.bind(this));
return service;
}
setupSleepService(accessories) {
const enabled = this.device.deviceType.hasSleepMode && accessories.sleep_mode !== false;
const service = this.getOrCreateService(this.platform.Service.Switch, SleepModeName, enabled);
if (!service) {
return undefined;
}
this.humidifierService.addLinkedService(service);
service
.getCharacteristic(this.platform.Characteristic.On)
.onGet(SleepState_1.default.get.bind(this))
.onSet(SleepState_1.default.set.bind(this));
return service;
}
setupLightService(accessories) {
const enabled = this.device.deviceType.hasLight && accessories.night_light !== false;
const service = this.getOrCreateService(this.platform.Service.Lightbulb, NightLightName, enabled);
if (!service) {
return undefined;
}
this.humidifierService.addLinkedService(service);
service
.getCharacteristic(this.platform.Characteristic.On)
.onGet(LightState_1.default.get.bind(this))
.onSet(LightState_1.default.set.bind(this));
const props = this.device.deviceType.hasColorMode
? { minValue: 39, maxValue: 100 }
: {
minStep: 25,
minValue: 0,
maxValue: 100,
validValues: [0, 25, 50, 75, 100],
};
service
.getCharacteristic(this.platform.Characteristic.Brightness)
.setProps(props)
.onGet(LightBrightness_1.default.get.bind(this))
.onSet(LightBrightness_1.default.set.bind(this));
return service;
}
setupAutoProService(accessories) {
const enabled = !!this.device.deviceType.hasAutoProMode && accessories.auto_pro !== false;
const service = this.getOrCreateService(this.platform.Service.Switch, AutoProModeName, enabled);
if (!service) {
return undefined;
}
this.humidifierService.addLinkedService(service);
service
.getCharacteristic(this.platform.Characteristic.On)
.onGet(AutoProState_1.default.get.bind(this))
.onSet(AutoProState_1.default.set.bind(this));
return service;
}
/**
* Starts background polling to periodically update device state.
* This ensures characteristics always have fresh data without blocking
* HomeKit read requests, which prevents "slow to respond" warnings.
*/
startPolling() {
this.device.updateInfo().catch((err) => {
this.platform.log.debug(`[${this.device.name}] Initial device update failed:`, err instanceof Error ? err.message : String(err));
});
this.pollingInterval = setInterval(() => {
this.device.updateInfo().catch((err) => {
this.platform.log.debug(`[${this.device.name}] Background polling update failed:`, err instanceof Error ? err.message : String(err));
});
}, this.POLLING_INTERVAL_MS);
}
/**
* Stops background polling.
* Should be called when the accessory is removed to clean up resources.
*/
stopPolling() {
if (this.pollingInterval) {
clearInterval(this.pollingInterval);
this.pollingInterval = null;
}
}
/**
* Updates all HomeKit characteristics to match current device state.
* Call this after any device command to immediately reflect changes in HomeKit.
*/
updateAllCharacteristics() {
const { device } = this;
this.updateHumidifierCharacteristics(device);
this.updateOptionalServiceCharacteristics(device);
}
updateHumidifierCharacteristics(device) {
this.humidifierService
.getCharacteristic(this.platform.Characteristic.Active)
.updateValue(device.isOn ? 1 : 0);
this.humidifierService
.getCharacteristic(this.platform.Characteristic.CurrentHumidifierDehumidifierState)
.updateValue(device.isOn
? this.platform.Characteristic.CurrentHumidifierDehumidifierState
.HUMIDIFYING
: this.platform.Characteristic.CurrentHumidifierDehumidifierState
.IDLE);
this.humidifierService
.getCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold)
.updateValue(device.isOn ? device.targetHumidity : 0);
this.humidifierService
.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.updateValue(device.humidityLevel);
}
updateOptionalServiceCharacteristics(device) {
var _a;
if (this.humiditySensorService) {
this.humiditySensorService
.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.updateValue(device.humidityLevel);
}
if (this.mistService) {
this.mistService
.getCharacteristic(this.platform.Characteristic.On)
.updateValue(device.isOn);
const mistPct = device.isOn
? Math.round((device.mistLevel / device.deviceType.mistLevels) * 100)
: 0;
this.mistService
.getCharacteristic(this.platform.Characteristic.RotationSpeed)
.updateValue(mistPct);
}
if (this.warmMistService) {
this.warmMistService
.getCharacteristic(this.platform.Characteristic.On)
.updateValue(device.warmEnabled);
const maxWarm = (_a = device.deviceType.warmMistLevels) !== null && _a !== void 0 ? _a : 0;
const warmPct = device.isOn && maxWarm > 0
? Math.round((device.warmLevel / maxWarm) * 100)
: 0;
this.warmMistService
.getCharacteristic(this.platform.Characteristic.RotationSpeed)
.updateValue(warmPct);
}
if (this.lightService) {
this.lightService
.getCharacteristic(this.platform.Characteristic.On)
.updateValue(device.lightOn === 'on');
this.lightService
.getCharacteristic(this.platform.Characteristic.Brightness)
.updateValue(device.brightnessLevel);
}
if (this.displayService) {
this.displayService
.getCharacteristic(this.platform.Characteristic.On)
.updateValue(device.isOn && device.displayOn);
}
if (this.sleepService) {
this.sleepService
.getCharacteristic(this.platform.Characteristic.On)
.updateValue(device.isOn && device.mode === 'sleep');
}
if (this.autoProService) {
this.autoProService
.getCharacteristic(this.platform.Characteristic.On)
.updateValue(device.isOn && device.mode === 'autoPro');
}
}
}
exports.default = VeSyncAccessory;
//# sourceMappingURL=VeSyncAccessory.js.map