homebridge-purpleair-sensor
Version:
Monitor air quality using PurpleAir.
306 lines • 14.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.PurpleAirPlatformAccessory = void 0;
const axios_1 = __importStar(require("axios"));
const settings_1 = require("./settings");
const SensorReading_1 = require("./SensorReading");
/**
* 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.
*/
class PurpleAirPlatformAccessory {
constructor(platform, accessory) {
this.platform = platform;
this.accessory = accessory;
// set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'PurpleAir')
.setCharacteristic(this.platform.Characteristic.Model, 'PurpleAir')
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.context.sensor.sensorID || 'n/a');
this.platform.log.debug(this.accessory.displayName + ': ' + 'Configuring air quality service');
this.service = this.accessory.getService(this.platform.Service.AirQualitySensor)
|| this.accessory.addService(this.platform.Service.AirQualitySensor);
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
this.service.getCharacteristic(this.platform.Characteristic.StatusActive)
.onGet(this.getStatusActive.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.AirQuality)
.onGet(this.getAirQuality.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.PM2_5Density)
.onGet(this.getPM2_5Density.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.VOCDensity)
.onGet(this.getVOCDensity.bind(this));
if (this.accessory.context.sensor.humidity) {
this.service.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.onGet(this.getCurrentRelativeHumidity.bind(this));
}
else {
const humidityCharacteristic = this.service.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity);
if (humidityCharacteristic) {
this.service.removeCharacteristic(humidityCharacteristic);
}
}
const humidityService = this.accessory.getService(this.platform.Service.HumiditySensor);
if (this.accessory.context.sensor.humidity) {
this.debugLog('Configuring humidity service');
this.humidity = humidityService || this.accessory.addService(this.platform.Service.HumiditySensor);
this.humidity.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
this.humidity.getCharacteristic(this.platform.Characteristic.StatusActive).
onGet(this.getStatusActive.bind(this));
this.humidity.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.onGet(this.getCurrentRelativeHumidity.bind(this));
}
else {
this.debugLog('Skipping configuring humidity service');
if (humidityService) {
this.platform.log.debug('Removing humidity service');
this.accessory.removeService(humidityService);
this.humidity = undefined;
}
}
const temperatureService = this.accessory.getService(this.platform.Service.TemperatureSensor);
if (this.accessory.context.sensor.temperature) {
this.debugLog('Configuring temperature service');
this.temperature = temperatureService || this.accessory.addService(this.platform.Service.TemperatureSensor);
this.temperature.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
this.temperature.getCharacteristic(this.platform.Characteristic.StatusActive).
onGet(this.getStatusActive.bind(this));
this.temperature.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
.onGet(this.getCurrentTemperature.bind(this));
}
else {
this.debugLog('Skipping configuring temperature service');
if (temperatureService) {
this.debugLog('Removing temperature service');
this.accessory.removeService(temperatureService);
this.temperature = undefined;
}
}
const interval = settings_1.DEFAULT_UPDATE_INTERVAL_SECS * 1000;
this.infoLog('Scheduling updates every ' + interval / 1000 / 60 + ' minutes');
setInterval(() => {
this.update();
}, interval);
this.update();
}
debugLog(message) {
this.platform.log.debug(this.accessory.displayName + ': ' + message);
}
infoLog(message) {
this.platform.log.info(this.accessory.displayName + ': ' + message);
}
errorLog(message) {
this.platform.log.error(this.accessory.displayName + ': ' + message);
}
getAirQuality() {
this.debugLog('getAirQuality');
const lastReading = this.accessory.context.lastReading;
if (lastReading !== undefined && lastReading.airQualityHomekitReading !== undefined) {
return lastReading.airQualityHomekitReading;
}
return this.platform.Characteristic.AirQuality.UNKNOWN;
}
getPM2_5Density() {
this.debugLog('getPM2_5Density');
const lastReading = this.accessory.context.lastReading;
if (lastReading !== undefined) {
if (this.platform.config.aqiInsteadOfDensity) {
return lastReading.aqi || 0;
}
else {
return lastReading.pm25 || 0;
}
}
return 0;
}
getVOCDensity() {
this.debugLog('getVOCDensity');
const lastReading = this.accessory.context.lastReading;
if (lastReading !== undefined) {
return lastReading.voc || 0;
}
return 0;
}
getCurrentRelativeHumidity() {
this.debugLog('getCurrentRelativeHumidity');
const lastReading = this.accessory.context.lastReading;
if (lastReading !== undefined) {
return lastReading.humidity || 0;
}
return 0;
}
getCurrentTemperature() {
this.debugLog('getCurrentTemperature');
const lastReading = this.accessory.context.lastReading;
if (lastReading !== undefined) {
return lastReading.temperature || 0;
}
return 0;
}
async update() {
const lastReading = this.accessory.context.lastReading;
if (lastReading !== undefined) {
if (lastReading.updateTimeMs > Date.now() - settings_1.MIN_UPDATE_INTERVAL_SECS * 1000) {
this.infoLog('Skipping a fetch because the last update was ' +
(Date.now() - lastReading.updateTimeMs) / 1000 + ' seconds ago');
return;
}
else {
this.infoLog('Refreshing sensor readings. Last update was ' +
(Date.now() - lastReading.updateTimeMs) / 1000 + ' seconds ago');
}
}
else {
this.infoLog('Refreshing sensor readings. No previous update.');
}
const sensorConfig = this.accessory.context.sensor;
const platformConfig = this.platform.config;
let url = settings_1.PURPLEAIR_API_BASE_URL;
let usesLocalSensor = false;
const axiosInstance = axios_1.default.create();
if (sensorConfig.localIPAddress !== undefined) {
url = 'http://' + sensorConfig.localIPAddress + '/json';
usesLocalSensor = true;
}
else {
if (sensorConfig.sensor) {
url += '/' + sensorConfig.sensor;
}
else {
this.errorLog('No sensor ID or local IP address configured. Check your configuration.');
return;
}
if (platformConfig.apiReadKey) {
axiosInstance.defaults.headers.common['X-API-Key'] = platformConfig.apiReadKey;
}
else {
this.errorLog('No API Read Key or local IP address configured. Check your configuration.');
return;
}
}
axiosInstance.interceptors.request.use((request) => {
this.debugLog(`Fetching url ${request.url} with params ${JSON.stringify(request.params)}`);
return request;
});
try {
let fields = 'voc,pm2.5,pm2.5_cf_1,pm2.5_10minute,pm2.5_30minute,pm2.5_60minute';
if (sensorConfig.humidity) {
fields += ',humidity';
}
if (sensorConfig.temperature) {
fields += ',temperature';
}
if (platformConfig.conversion === 'ALT-CF3') {
fields += ',pm2.5_alt';
}
const request_config = {
params: {
read_key: sensorConfig.key,
fields: fields,
},
};
const resp = await axiosInstance.get(url, request_config);
if (!usesLocalSensor && resp.data.sensor === undefined) {
throw new Error('No sensor found with ID ${this.sensor}');
}
this.accessory.context.lastReading = (0, SensorReading_1.parsePurpleAirJson)(resp.data, platformConfig.averages, platformConfig.conversion, usesLocalSensor);
this.infoLog(`Received new sensor reading ${this.accessory.context.lastReading}`);
this.updateHomeKit(platformConfig.aqiInsteadOfDensity);
}
catch (err) {
if (err instanceof axios_1.AxiosError && err.response) {
this.errorLog(`Error fetching ${url}: ${JSON.stringify(err.response.data)}`);
}
else {
this.errorLog(`Error fetching ${url}: ${err}`);
}
}
}
/**
* Handle the "GET" requests from HomeKit
* These are sent when HomeKit wants to know the current state of the accessory, for example, checking if a Light bulb is on.
*/
getStatusActive() {
let activeResult = false;
if (this.accessory.context.lastReading) {
const lastUpdateDeltaMs = Date.now() - this.accessory.context.lastReading.updateTimeMs;
const updatesHappening = lastUpdateDeltaMs <= settings_1.DEFAULT_UPDATE_INTERVAL_SECS * 1000;
if (updatesHappening) {
activeResult = true;
}
else {
const serviceError = lastUpdateDeltaMs > settings_1.SENSOR_FAILURE_TIMEOUT_SECS * 1000;
if (serviceError) {
this.errorLog(`getStatusActive throwing SERVICE_COMMUNICATION_FAILURE because last update was ${lastUpdateDeltaMs / 1000}s ago`);
throw new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
}
}
}
this.debugLog(`getStatusActive reporting ${activeResult}`);
return activeResult;
}
updateHomeKit(aqiInsteadOfDensity) {
const lastReading = this.accessory.context.lastReading;
this.debugLog(`Updating HomeKit with new sensor reading: ${lastReading}`);
if (lastReading !== undefined) {
this.service.updateCharacteristic(this.platform.Characteristic.StatusActive, true);
this.service.updateCharacteristic(this.platform.Characteristic.AirQuality, lastReading.airQualityHomekitReading);
if (aqiInsteadOfDensity) {
this.service.updateCharacteristic(this.platform.Characteristic.PM2_5Density, lastReading.aqi);
}
else {
this.service.updateCharacteristic(this.platform.Characteristic.PM2_5Density, lastReading.pm25);
}
if (lastReading.voc) {
this.service.updateCharacteristic(this.platform.Characteristic.VOCDensity, lastReading.voc);
}
if (lastReading.humidity) {
if (this.humidity) {
this.humidity.updateCharacteristic(this.platform.Characteristic.StatusActive, true);
this.humidity.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, lastReading.humidity);
}
// This not listed as optional characteristic for air quality sensor, but HomeApp does show it on the accessory's page
this.service.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, lastReading.humidity);
}
if (this.temperature && lastReading.temperature) {
this.temperature.updateCharacteristic(this.platform.Characteristic.StatusActive, true);
this.temperature.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, lastReading.temperature);
}
}
}
}
exports.PurpleAirPlatformAccessory = PurpleAirPlatformAccessory;
//# sourceMappingURL=platformAccessory.js.map