@homebridge-plugins/homebridge-air
Version:
The AirNow plugin allows you to monitor the current AirQuality for your Zip Code from HomeKit and Siri.
181 lines • 10.8 kB
JavaScript
import { interval } from 'rxjs';
import { skipWhile } from 'rxjs/operators';
import striptags from 'striptags';
import { request } from 'undici';
import { AirNowUrl, AqicnUrl, HomeKitAQI } from '../settings.js';
import { deviceBase } from './device.js';
/**
* Platform Accessory
* An instance of this class is created for each accessory your platform registers
* Each accessory may expose multiple services of different service types.
*/
export class AirQualitySensor extends deviceBase {
platform;
// Service
AirQualitySensor;
// Updates
SensorUpdateInProgress;
deviceStatus;
constructor(platform, accessory, device) {
super(platform, accessory, device);
this.platform = platform;
// AirQuality Sensor Service
this.debugLog('Configure AirQuality Sensor Service');
accessory.context.AirQualitySensor = accessory.context.AirQualitySensor ?? {};
this.AirQualitySensor = {
Name: this.accessory.displayName,
Service: this.accessory.getService(this.hap.Service.AirQualitySensor) ?? this.accessory.addService(this.hap.Service.AirQualitySensor),
AirQuality: accessory.context.AirQuality ?? this.hap.Characteristic.AirQuality.EXCELLENT,
StatusFault: accessory.context.StatusFault ?? this.hap.Characteristic.StatusFault.NO_FAULT,
OzoneDensity: accessory.context.OzoneDensity ?? 0,
NitrogenDioxideDensity: accessory.context.NitrogenDioxideDensity ?? 0,
SulphurDioxideDensity: accessory.context.SulphurDioxideDensity ?? 0,
PM2_5Density: accessory.context.PM2_5Density ?? 0,
PM10Density: accessory.context.PM10Density ?? 0,
CarbonMonoxideLevel: accessory.context.CarbonMonoxideLevel ?? 0,
};
accessory.context.AirQualitySensor = this.AirQualitySensor;
// Add AirQuality Sensor Service's Characteristics
this.AirQualitySensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.AirQualitySensor.Name);
// this is subject we use to track when we need to POST changes to the Air API
this.SensorUpdateInProgress = false;
// Retrieve initial values and updateHomekit
this.refreshStatus();
// Start an update interval
interval(this.deviceRefreshRate * 10000)
.pipe(skipWhile(() => this.SensorUpdateInProgress))
.subscribe(async () => {
await this.refreshStatus();
});
}
/**
* Parse the device status from the Air api
*/
async parseStatus() {
try {
const provider = this.device.provider;
const status = this.deviceStatus[0];
if (provider === 'airnow' && !status) {
this.errorLog('AirNow air quality Configuration Error - Invalid ZipCode for %s.', provider);
this.AirQualitySensor.StatusFault = this.hap.Characteristic.StatusFault.GENERAL_FAULT;
}
else if (provider === 'airnow' && typeof status.AQI === 'undefined') {
this.errorLog('AirNow air quality Observation Error - %s for %s.', striptags(JSON.stringify(this.deviceStatus)), provider);
this.AirQualitySensor.StatusFault = this.hap.Characteristic.StatusFault.GENERAL_FAULT;
}
else if (provider === 'airnow' || provider === 'aqicn') {
const pollutants = provider === 'airnow' ? ['O3', 'PM2.5', 'PM10'] : ['o3', 'no2', 'so2', 'pm25', 'pm10', 'co'];
pollutants.forEach((pollutant) => {
const param = provider === 'airnow' ? this.deviceStatus.find((p) => p.ParameterName === pollutant) : this.deviceStatus.iaqi[pollutant]?.v;
if (param !== undefined) {
const aqi = provider === 'airnow' ? Number.parseFloat(param.AQI.toString()) : Number.parseFloat(param);
if (!Number.isNaN(aqi)) {
switch (pollutant.toLowerCase()) {
case 'o3':
this.AirQualitySensor.OzoneDensity = aqi;
break;
case 'pm2.5':
this.AirQualitySensor.PM2_5Density = aqi;
break;
case 'pm10':
this.AirQualitySensor.PM10Density = aqi;
break;
case 'no2':
this.AirQualitySensor.NitrogenDioxideDensity = aqi;
break;
case 'so2':
this.AirQualitySensor.SulphurDioxideDensity = aqi;
break;
case 'co':
this.AirQualitySensor.CarbonMonoxideLevel = aqi;
break;
}
this.AirQualitySensor.AirQuality = HomeKitAQI(Math.max(0, aqi));
}
}
});
this.infoLog(`${provider} air quality AQI is: ${this.AirQualitySensor.AirQuality}`);
this.AirQualitySensor.StatusFault = this.hap.Characteristic.StatusFault.NO_FAULT;
}
else {
await this.errorLog('Unknown air quality provider: %s.', provider);
}
}
catch (e) {
await this.errorLog(`failed to parseStatus, Error Message: ${JSON.stringify(e.message ?? e)}`);
await this.apiError(e);
}
}
/**
* Asks the Air API for the latest device information
*/
async refreshStatus() {
try {
const AirNowCurrentObservationBy = this.device.latitude && this.device.longitude ? `latLong` : 'zipCode';
const AqicnCurrentObservationBy = this.device.latitude && this.device.longitude ? `geo:${this.device.latitude};${this.device.longitude}` : this.device.city;
const AirNowCurrentObservationByValue = this.device.latitude && this.device.longitude ? `latitude=${this.device.latitude}&longitude=${this.device.longitude}` : `zipCode=${this.device.zipCode}`;
const providerUrls = {
airnow: `${AirNowUrl}${AirNowCurrentObservationBy}/current/?format=application/json&${AirNowCurrentObservationByValue}&distance=${this.device.distance}&API_KEY=${this.device.apiKey}`,
aqicn: `${AqicnUrl}${AqicnCurrentObservationBy}/?token=${this.device.apiKey}`,
};
const url = providerUrls[this.device.provider];
await this.debugSuccessLog(`url: ${JSON.stringify(url)}`);
if (url) {
const { body, statusCode } = await request(url);
const response = await body.json();
await this.debugWarnLog(`statusCode: ${JSON.stringify(statusCode)}`);
await this.debugLog(`response: ${JSON.stringify(response)}`);
if (statusCode !== 200) {
this.errorLog(`${this.device.provider === 'airnow' ? 'AirNow' : 'World Air Quality Index'} air quality Network or Unknown Error from %s.`, this.device.provider);
this.AirQualitySensor.StatusFault = this.hap.Characteristic.StatusFault.GENERAL_FAULT;
await this.debugLog(`Error: ${JSON.stringify(response)}`);
await this.apiError(response);
}
else {
this.deviceStatus = this.device.provider === 'aqicn' ? response.data : response;
await this.parseStatus();
}
}
else {
await this.errorLog('Unknown air quality provider: %s.', this.device.provider);
}
await this.updateHomeKitCharacteristics();
}
catch (e) {
await this.errorLog(`failed to update status, Error Message: ${JSON.stringify(e.message ?? e)}`);
await this.apiError(e);
}
}
/**
* Updates the status for each of the HomeKit Characteristics
*/
async updateHomeKitCharacteristics() {
// AirQuality
await this.updateCharacteristic(this.AirQualitySensor.Service, this.hap.Characteristic.AirQuality, this.AirQualitySensor.AirQuality, 'AirQuality');
// OzoneDensity
await this.updateCharacteristic(this.AirQualitySensor.Service, this.hap.Characteristic.OzoneDensity, this.AirQualitySensor.OzoneDensity, 'OzoneDensity');
// NitrogenDioxideDensity
await this.updateCharacteristic(this.AirQualitySensor.Service, this.hap.Characteristic.NitrogenDioxideDensity, this.AirQualitySensor.NitrogenDioxideDensity, 'NitrogenDioxideDensity');
// SulphurDioxideDensity
await this.updateCharacteristic(this.AirQualitySensor.Service, this.hap.Characteristic.SulphurDioxideDensity, this.AirQualitySensor.SulphurDioxideDensity, 'SulphurDioxideDensity');
// PM2_5Density
await this.updateCharacteristic(this.AirQualitySensor.Service, this.hap.Characteristic.PM2_5Density, this.AirQualitySensor.PM2_5Density, 'PM2_5Density');
// PM10Density
await this.updateCharacteristic(this.AirQualitySensor.Service, this.hap.Characteristic.PM10Density, this.AirQualitySensor.PM10Density, 'PM10Density');
// CarbonMonoxideLevel
await this.updateCharacteristic(this.AirQualitySensor.Service, this.hap.Characteristic.CarbonMonoxideLevel, this.AirQualitySensor.CarbonMonoxideLevel, 'CarbonMonoxideLevel');
// StatusFault
await this.updateCharacteristic(this.AirQualitySensor.Service, this.hap.Characteristic.StatusFault, this.AirQualitySensor.StatusFault, 'StatusFault');
}
async apiError(e) {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.AirQuality, e);
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.OzoneDensity, e);
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.NitrogenDioxideDensity, e);
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.SulphurDioxideDensity, e);
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.PM2_5Density, e);
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.PM10Density, e);
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.CarbonMonoxideLevel, e);
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.StatusFault, this.hap.Characteristic.StatusFault.GENERAL_FAULT);
}
}
//# sourceMappingURL=airqualitysensor.js.map