homebridge-tsvesync
Version:
Homebridge plugin for VeSync devices including Levoit air purifiers, humidifiers, and Etekcity smart outlets
277 lines • 11.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AirQualitySensorAccessory = void 0;
const base_accessory_1 = require("./base.accessory");
/**
* Air Quality Sensor Accessory
* This is a separate accessory that only provides air quality monitoring
* Used for Core300S, Core400S, Core600S and other devices with AQ sensors
*/
class AirQualitySensorAccessory extends base_accessory_1.BaseAccessory {
constructor(platform, accessory, device) {
super(platform, accessory, device);
this.parentDevice = device;
// Set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'VeSync')
.setCharacteristic(this.platform.Characteristic.Model, `${device.deviceType || 'Unknown'} AQ Sensor`)
.setCharacteristic(this.platform.Characteristic.SerialNumber, `${device.cid || device.uuid}-AQ`);
// Get or create the Air Quality Sensor service
this.service = this.accessory.getService(this.platform.Service.AirQualitySensor) ||
this.accessory.addService(this.platform.Service.AirQualitySensor);
// Set the service name
this.service.setCharacteristic(this.platform.Characteristic.Name, `${device.deviceName} Air Quality`);
// Configure the Air Quality characteristic
this.service.getCharacteristic(this.platform.Characteristic.AirQuality)
.setProps({
validValues: [
this.platform.Characteristic.AirQuality.UNKNOWN,
this.platform.Characteristic.AirQuality.EXCELLENT,
this.platform.Characteristic.AirQuality.GOOD,
this.platform.Characteristic.AirQuality.FAIR,
this.platform.Characteristic.AirQuality.INFERIOR,
],
})
.onGet(this.getAirQuality.bind(this));
// Add PM2.5 Density characteristic if device supports it
if (this.deviceSupportsPM25()) {
this.service.getCharacteristic(this.platform.Characteristic.PM2_5Density)
.onGet(this.getPM25Density.bind(this));
}
// Add PM10 Density characteristic if device supports it
if (this.deviceSupportsPM10()) {
this.service.getCharacteristic(this.platform.Characteristic.PM10Density)
.onGet(this.getPM10Density.bind(this));
}
// Polling is handled by BaseAccessory
}
/**
* Check if device supports PM2.5 readings
*/
deviceSupportsPM25() {
// Core300S, Core400S, Core600S all support PM2.5
const deviceType = this.parentDevice.deviceType || '';
return deviceType.includes('Core300S') ||
deviceType.includes('Core400S') ||
deviceType.includes('Core600S') ||
deviceType.includes('LAP-') ||
deviceType.includes('LV-PUR');
}
/**
* Check if device supports PM10 readings
*/
deviceSupportsPM10() {
// Some newer devices support PM10
const deviceType = this.parentDevice.deviceType || '';
return deviceType.includes('Core600S') ||
deviceType.includes('LAP-V');
}
/**
* Get the current air quality level
*/
async getAirQuality() {
try {
const level = this.readNormalizedAirQualityLevel();
if (level >= 1) {
const hkValue = this.mapToHomeKitAirQuality(level);
this.platform.log.debug(`${this.device.deviceName} AQ: Normalized air quality level = ${level}`);
return hkValue;
}
const pm25 = this.getDevicePm25();
this.platform.log.debug(`${this.device.deviceName} AQ: Falling back to PM2.5 heuristic (${pm25})`);
return this.convertPM25ToAirQuality(pm25);
}
catch (error) {
this.platform.log.error(`${this.device.deviceName} AQ: Failed to get air quality:`, error);
return this.platform.Characteristic.AirQuality.UNKNOWN;
}
}
/**
* Get the current PM2.5 density
*/
async getPM25Density() {
try {
const pm25 = this.getDevicePm25();
// HomeKit expects PM2.5 in μg/m³, range 0-1000
return Math.min(1000, Math.max(0, pm25));
}
catch (error) {
this.platform.log.error(`${this.device.deviceName} AQ: Failed to get PM2.5:`, error);
return 0;
}
}
/**
* Get the current PM10 density
*/
async getPM10Density() {
var _a;
try {
const extendedDevice = this.parentDevice;
const pm10 = ((_a = extendedDevice.details) === null || _a === void 0 ? void 0 : _a.pm10) || 0;
// HomeKit expects PM10 in μg/m³, range 0-1000
return Math.min(1000, Math.max(0, pm10));
}
catch (error) {
this.platform.log.error(`${this.device.deviceName} AQ: Failed to get PM10:`, error);
return 0;
}
}
/**
* Convert PM2.5 value to HomeKit AirQuality level
* Based on EPA Air Quality Index (AQI) standards
*/
convertPM25ToAirQuality(pm25) {
if (pm25 <= 12) {
return this.platform.Characteristic.AirQuality.EXCELLENT; // Good (0-12 μg/m³)
}
else if (pm25 <= 35) {
return this.platform.Characteristic.AirQuality.GOOD; // Moderate (12.1-35.4 μg/m³)
}
else if (pm25 <= 55) {
return this.platform.Characteristic.AirQuality.FAIR; // Unhealthy for Sensitive (35.5-55.4 μg/m³)
}
return this.platform.Characteristic.AirQuality.INFERIOR;
}
mapToHomeKitAirQuality(level) {
switch (level) {
case 1:
return this.platform.Characteristic.AirQuality.EXCELLENT;
case 2:
return this.platform.Characteristic.AirQuality.GOOD;
case 3:
return this.platform.Characteristic.AirQuality.FAIR;
case 4:
return this.platform.Characteristic.AirQuality.INFERIOR;
default:
return this.platform.Characteristic.AirQuality.UNKNOWN;
}
}
readNormalizedAirQualityLevel() {
var _a, _b, _c, _d, _e;
const extendedDevice = this.parentDevice;
if (typeof extendedDevice.getNormalizedAirQuality === 'function') {
try {
const normalized = extendedDevice.getNormalizedAirQuality();
if (normalized && typeof normalized.level === 'number' && normalized.level >= 1) {
return normalized.level;
}
}
catch (error) {
this.platform.log.debug(`${this.device.deviceName} AQ: getNormalizedAirQuality failed`, error);
}
}
const directLevel = (_a = extendedDevice.airQualityLevel) !== null && _a !== void 0 ? _a : (_b = extendedDevice.details) === null || _b === void 0 ? void 0 : _b.air_quality_level;
if (typeof directLevel === 'number' && directLevel >= 1) {
return directLevel;
}
const normalized = this.normalizeAirQuality((_e = (_c = extendedDevice.airQuality) !== null && _c !== void 0 ? _c : (_d = extendedDevice.details) === null || _d === void 0 ? void 0 : _d.air_quality) !== null && _e !== void 0 ? _e : extendedDevice.airQualityLabel);
return normalized.level;
}
getDevicePm25() {
var _a, _b, _c, _d;
const extendedDevice = this.parentDevice;
const pm25 = (_d = (_c = (_a = extendedDevice.airQualityValue) !== null && _a !== void 0 ? _a : (_b = extendedDevice.details) === null || _b === void 0 ? void 0 : _b.air_quality_value) !== null && _c !== void 0 ? _c : extendedDevice.pm25) !== null && _d !== void 0 ? _d : 0;
return Number.isFinite(pm25) ? pm25 : 0;
}
normalizeAirQuality(value) {
const stringMap = {
'excellent': 1,
'very good': 1,
'good': 2,
'moderate': 3,
'fair': 3,
'inferior': 4,
'poor': 4,
'bad': 4,
};
if (typeof value === 'number') {
const level = Number.isFinite(value) ? Math.trunc(value) : -1;
if (level >= 1 && level <= 4) {
return {
level,
label: level === 1 ? 'excellent' : level === 2 ? 'good' : level === 3 ? 'moderate' : 'poor',
};
}
}
else if (typeof value === 'string') {
const normalized = value.trim().toLowerCase();
const level = stringMap[normalized];
if (level) {
return {
level,
label: level === 1 ? 'excellent' : level === 2 ? 'good' : level === 3 ? 'moderate' : 'poor',
};
}
}
return { level: -1, label: 'unknown' };
}
/**
* Update characteristics from device state
*/
async updateCharacteristics() {
try {
// Make sure device data is current
const extendedDevice = this.parentDevice;
if (this.shouldSkipDeviceDetailsRefresh(extendedDevice)) {
this.platform.log.debug(`${this.device.deviceName} AQ: Skipping refresh because parent device is offline`);
}
else if (!this.hasFreshDeviceDetails() && typeof extendedDevice.getDetails === 'function') {
await extendedDevice.getDetails();
this.cacheDeviceDetails(extendedDevice);
}
// Update air quality
const airQuality = await this.getAirQuality();
this.service.updateCharacteristic(this.platform.Characteristic.AirQuality, airQuality);
// Update PM2.5 if supported
if (this.deviceSupportsPM25()) {
const pm25 = await this.getPM25Density();
this.service.updateCharacteristic(this.platform.Characteristic.PM2_5Density, pm25);
}
// Update PM10 if supported
if (this.deviceSupportsPM10()) {
const pm10 = await this.getPM10Density();
this.service.updateCharacteristic(this.platform.Characteristic.PM10Density, pm10);
}
this.platform.log.debug(`${this.device.deviceName} AQ: Updated air quality characteristics`);
}
catch (error) {
this.platform.log.error(`${this.device.deviceName} AQ: Failed to update characteristics:`, error);
}
}
/**
* Get device capabilities (required by BaseAccessory)
*/
getDeviceCapabilities() {
return {
hasSpeed: false,
hasAirQuality: true,
hasChildLock: false,
hasBrightness: false,
hasHumidity: false,
hasWaterLevel: false,
hasSwingMode: false,
hasColorTemp: false,
hasColor: false,
};
}
/**
* Setup the service (required by BaseAccessory)
*/
setupService() {
// Service is already set up in constructor
}
/**
* Update device specific states (required by BaseAccessory)
*/
async updateDeviceSpecificStates() {
await this.updateCharacteristics();
}
/**
* Identify this accessory
*/
identify() {
this.platform.log.info(`${this.device.deviceName} AQ Sensor: Identify!`);
}
}
exports.AirQualitySensorAccessory = AirQualitySensorAccessory;
//# sourceMappingURL=air-quality-sensor.accessory.js.map