Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

homebridge-air

Package Overview
Dependencies
Maintainers
0
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

homebridge-air - npm Package Compare versions

Comparing version 1.0.0-beta.2 to 1.0.0-beta.3

49

config.schema.json

@@ -5,3 +5,3 @@ {

"singular": true,
"customUi": false,
"customUi": true,
"customUiPath": "./dist/homebridge-ui",

@@ -29,5 +29,2 @@ "headerDisplay": "<p align='center'><img width='250px' src='https://raw.githubusercontent.com/donavanbecker/homebridge-air/latest/branding/Homebridge_x_Air.svg'></p>\n\nThe **Homebridge Air** plugin allows you monitor the current AirQuality for your Zip Code from HomeKit and Siri.",

"required": true,
"x-schema-form": {
"type": "email"
},
"default": "airnow",

@@ -40,2 +37,8 @@ "oneOf": [

]
},
{
"title": "Aqicn",
"enum": [
"aqicn"
]
}

@@ -52,4 +55,4 @@ ]

},
"locationName": {
"title": "Location Name",
"city": {
"title": "City",
"type": "string",

@@ -66,3 +69,6 @@ "required": true

"type": "string",
"required": true
"required": false,
"condition": {
"functionBody": "return (model.devices && model.devices[arrayIndices].city && model.devices[arrayIndices].zipCode);"
}
},

@@ -72,3 +78,6 @@ "firmware": {

"type": "string",
"placeholder": "1.2.8"
"placeholder": "1.2.8",
"condition": {
"functionBody": "return (model.devices && model.devices[arrayIndices].city && model.devices[arrayIndices].zipCode);"
}
},

@@ -80,3 +89,6 @@ "refreshRate": {

"placeholder": 1800,
"description": "Indicates the number of seconds between polls of the AirNow service."
"description": "Indicates the number of seconds between polls of the AirNow service.",
"condition": {
"functionBody": "return (model.devices && model.devices[arrayIndices].city && model.devices[arrayIndices].zipCode);"
}
},

@@ -88,2 +100,5 @@ "logging": {

"default": "",
"condition": {
"functionBody": "return (model.devices && model.devices[arrayIndices].city && model.devices[arrayIndices].zipCode);"
},
"oneOf": [

@@ -117,6 +132,8 @@ {

"delete": {
"title": "Delete",
"type": "button",
"style": "danger",
"onClick": "delete"
"title": "Delete Device",
"type": "boolean",
"description": "Delete this device from the plugin cache.",
"condition": {
"functionBody": "return (model.devices && model.devices[arrayIndices].city && model.devices[arrayIndices].zipCode);"
}
}

@@ -172,3 +189,3 @@ }

"type": "tabarray",
"title": "{{ value.locationName || 'New Location (Zip Code)' }}",
"title": "{{ value.city || 'New City (Zip Code)' }}",
"expandable": true,

@@ -180,3 +197,3 @@ "expanded": false,

"devices[].apiKey",
"devices[].locationName",
"devices[].city",
"devices[].zipCode",

@@ -208,2 +225,2 @@ "devices[].distance",

]
}
}
import type { PlatformAccessory } from 'homebridge';
import type { AirPlatform } from '../platform.js';
import type { AirQualityDataArray, devicesConfig } from '../settings.js';
import type { devicesConfig } from '../settings.js';
import { deviceBase } from './device.js';

@@ -14,3 +14,3 @@ /**

SensorUpdateInProgress: boolean;
deviceStatus: AirQualityDataArray;
deviceStatus: any;
constructor(platform: AirPlatform, accessory: PlatformAccessory, device: devicesConfig);

@@ -17,0 +17,0 @@ /**

@@ -5,3 +5,3 @@ import { interval } from 'rxjs';

import { request } from 'undici';
import { AirNowUrl } from '../settings.js';
import { AirNowUrl, AqicnUrl, HomeKitAQI } from '../settings.js';
import { deviceBase } from './device.js';

@@ -57,29 +57,47 @@ /**

async parseStatus() {
let aqi = 0;
if (typeof this.deviceStatus[0] === 'undefined') {
this.errorLog('AirNow air quality Configuration Error - Invalid ZipCode for %s.', this.device.provider);
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 (typeof this.deviceStatus[0].AQI === 'undefined') {
this.errorLog('AirNow air quality Observation Error - %s for %s.', striptags(JSON.stringify(this.deviceStatus)), this.device.provider);
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 {
for (const key in this.deviceStatus) {
switch (this.deviceStatus[key].ParameterName) {
case 'O3':
this.AirQualitySensor.OzoneDensity = Number.parseFloat(this.deviceStatus[key].AQI.toString());
break;
case 'PM2.5':
this.AirQualitySensor.PM2_5Density = Number.parseFloat(this.deviceStatus[key].AQI.toString());
break;
case 'PM10':
this.AirQualitySensor.PM10Density = Number.parseFloat(this.deviceStatus[key].AQI.toString());
break;
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;
const aqi = provider === 'airnow' ? Number.parseFloat(param.AQI.toString()) : Number.parseFloat(param);
if (aqi !== undefined) {
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));
}
aqi = Math.max(0, Number.parseFloat(this.deviceStatus[key].AQI.toString())); // AirNow.gov defaults to MAX returned observation.
}
this.infoLog(`AirNow air quality AQI is: ${aqi.toString()}`);
});
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);
}
}

@@ -91,15 +109,25 @@ /**

try {
const { body, statusCode } = await request(`${AirNowUrl}&zipCode=${this.device.zipCode}&distance=${this.device.distance}&API_KEY=${this.device.apiKey}`);
const response = await body.json();
await this.debugWarnLog(`statusCode: ${JSON.stringify(statusCode)}`);
await this.debugLog(`respsonse: ${JSON.stringify(response)}`);
if (statusCode !== 200) {
this.errorLog('AirNow 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);
const providerUrls = {
airnow: `${AirNowUrl}&zipCode=${this.device.zipCode}&distance=${this.device.distance}&API_KEY=${this.device.apiKey}`,
aqicn: `${AqicnUrl}${this.device.city}/?token=${this.device.apiKey}`,
};
const url = providerUrls[this.device.provider];
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 {
this.deviceStatus = response;
await this.parseStatus();
await this.errorLog('Unknown air quality provider: %s.', this.device.provider);
}

@@ -118,58 +146,18 @@ await this.updateHomeKitCharacteristics();

async updateHomeKitCharacteristics() {
if (this.AirQualitySensor.AirQuality === undefined) {
await this.debugLog(`AirQuality: ${this.AirQualitySensor.AirQuality}`);
}
else {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.AirQuality, this.AirQualitySensor.AirQuality);
await this.debugLog(`updateCharacteristic AirQuality: ${this.AirQualitySensor.AirQuality}`);
}
if (this.AirQualitySensor.OzoneDensity === undefined) {
await this.debugLog(`OzoneDensity: ${this.AirQualitySensor.OzoneDensity}`);
}
else {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.OzoneDensity, this.AirQualitySensor.OzoneDensity);
await this.debugLog(`updateCharacteristic OzoneDensity: ${this.AirQualitySensor.OzoneDensity}`);
}
if (this.AirQualitySensor.NitrogenDioxideDensity === undefined) {
await this.debugLog(`NitrogenDioxideDensity: ${this.AirQualitySensor.NitrogenDioxideDensity}`);
}
else {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.NitrogenDioxideDensity, this.AirQualitySensor.NitrogenDioxideDensity);
await this.debugLog(`updateCharacteristic NitrogenDioxideDensity: ${this.AirQualitySensor.NitrogenDioxideDensity}`);
}
if (this.AirQualitySensor.SulphurDioxideDensity === undefined) {
await this.debugLog(`SulphurDioxideDensity: ${this.AirQualitySensor.SulphurDioxideDensity}`);
}
else {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.SulphurDioxideDensity, this.AirQualitySensor.SulphurDioxideDensity);
await this.debugLog(`updateCharacteristic SulphurDioxideDensity: ${this.AirQualitySensor.SulphurDioxideDensity}`);
}
if (this.AirQualitySensor.PM2_5Density === undefined) {
await this.debugLog(`PM2_5Density: ${this.AirQualitySensor.PM2_5Density}`);
}
else {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.PM2_5Density, this.AirQualitySensor.PM2_5Density);
await this.debugLog(`updateCharacteristic PM2_5Density: ${this.AirQualitySensor.PM2_5Density}`);
}
if (this.AirQualitySensor.PM10Density === undefined) {
await this.debugLog(`PM10Density: ${this.AirQualitySensor.PM10Density}`);
}
else {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.PM10Density, this.AirQualitySensor.PM10Density);
await this.debugLog(`updateCharacteristic PM10Density: ${this.AirQualitySensor.PM10Density}`);
}
if (this.AirQualitySensor.CarbonMonoxideLevel === undefined) {
await this.debugLog(`CarbonMonoxideLevel: ${this.AirQualitySensor.CarbonMonoxideLevel}`);
}
else {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.CarbonMonoxideLevel, this.AirQualitySensor.CarbonMonoxideLevel);
await this.debugLog(`updateCharacteristic CarbonMonoxideLevel: ${this.AirQualitySensor.CarbonMonoxideLevel}`);
}
if (this.AirQualitySensor.StatusFault === undefined) {
await this.debugLog(`StatusFault: ${this.AirQualitySensor.StatusFault}`);
}
else {
this.AirQualitySensor.Service.updateCharacteristic(this.hap.Characteristic.StatusFault, this.AirQualitySensor.StatusFault);
await this.debugLog(`updateCharacteristic StatusFault: ${this.AirQualitySensor.StatusFault}`);
}
// 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');
}

@@ -176,0 +164,0 @@ async apiError(e) {

@@ -1,2 +0,2 @@

import type { API, HAP, Logging, PlatformAccessory } from 'homebridge';
import type { API, CharacteristicValue, HAP, Logging, PlatformAccessory, Service } from 'homebridge';
import type { AirPlatform } from '../platform.js';

@@ -20,2 +20,13 @@ import type { AirPlatformConfig, devicesConfig } from '../settings.js';

/**
* Update the characteristic value and log the change.
*
* @param Service Service
* @param Characteristic Characteristic
* @param CharacteristicValue CharacteristicValue | undefined
* @param CharacteristicName string
* @return: void
*
*/
updateCharacteristic(Service: Service, Characteristic: any, CharacteristicValue: CharacteristicValue | undefined, CharacteristicName: string): Promise<void>;
/**
* Logging for Device

@@ -22,0 +33,0 @@ */

@@ -91,2 +91,24 @@ export class deviceBase {

/**
* Update the characteristic value and log the change.
*
* @param Service Service
* @param Characteristic Characteristic
* @param CharacteristicValue CharacteristicValue | undefined
* @param CharacteristicName string
* @return: void
*
*/
async updateCharacteristic(Service, Characteristic, CharacteristicValue, CharacteristicName) {
if (CharacteristicValue === undefined) {
this.debugLog(`${CharacteristicName}: ${CharacteristicValue}`);
}
else {
Service.updateCharacteristic(Characteristic, CharacteristicValue);
this.debugLog(`updateCharacteristic ${CharacteristicName}: ${CharacteristicValue}`);
this.debugWarnLog(`${CharacteristicName} context before: ${this.accessory.context[CharacteristicName]}`);
this.accessory.context[CharacteristicName] = CharacteristicValue;
this.debugWarnLog(`${CharacteristicName} context after: ${this.accessory.context[CharacteristicName]}`);
}
}
/**
* Logging for Device

@@ -93,0 +115,0 @@ */

@@ -114,3 +114,3 @@ import { readFileSync } from 'node:fs';

for (const device of this.config.devices) {
await this.infoLog(`Discovered ${device.locationName}`);
await this.infoLog(`Discovered ${device.city}`);
this.createAirQualitySensor(device);

@@ -124,3 +124,3 @@ }

async createAirQualitySensor(device) {
const uuid = this.api.hap.uuid.generate(device.locationName + device.apiKey + device.zipCode);
const uuid = this.api.hap.uuid.generate(device.city + device.apiKey + device.zipCode);
// see if an accessory with the same uuid has already been registered and restored from

@@ -134,5 +134,5 @@ // the cached devices we stored in the `configureAccessory` method above

existingAccessory.context.device = device;
existingAccessory.displayName = await this.validateAndCleanDisplayName(device.locationName, 'locationName', device.locationName);
existingAccessory.displayName = await this.validateAndCleanDisplayName(device.city, 'city', device.city);
existingAccessory.context.serialNumber = device.zipCode;
existingAccessory.context.model = `Current Observation by Zip Code`;
existingAccessory.context.model = device.provider === 'airnow' ? 'AirNow' : device.provider === 'aqicn' ? 'Aqicn' : 'Unknown';
existingAccessory.context.FirmwareRevision = device.firmware ?? await this.getVersion();

@@ -145,3 +145,3 @@ this.api.updatePlatformAccessories([existingAccessory]);

new AirQualitySensor(this, existingAccessory, device);
await this.debugLog(`${device.locationName} uuid: ${device.locationName + device.apiKey + device.zipCode}`);
await this.debugLog(`${device.city} uuid: ${device.city + device.apiKey + device.zipCode}`);
}

@@ -154,16 +154,16 @@ else {

// create a new accessory
const accessory = new this.api.platformAccessory(device.locationName, uuid);
const accessory = new this.api.platformAccessory(device.city, uuid);
// store a copy of the device object in the `accessory.context`
// the `context` property can be used to store any data about the accessory you may need
accessory.context.device = device;
accessory.displayName = await this.validateAndCleanDisplayName(device.locationName, 'locationName', device.locationName);
accessory.displayName = await this.validateAndCleanDisplayName(device.city, 'city', device.city);
accessory.context.serialNumber = device.zipCode;
accessory.context.model = `Current Observation by Zip Code`;
accessory.context.model = device.provider === 'airnow' ? 'AirNow' : device.provider === 'aqicn' ? 'Aqicn' : 'Unknown';
accessory.context.FirmwareRevision = device.firmware ?? await this.getVersion();
// the accessory does not yet exist, so we need to create it
await this.infoLog(`Adding new accessory: ${device.locationName}`);
await this.infoLog(`Adding new accessory: ${device.city}`);
// create the accessory handler for the newly create accessory
// this is imported from `platformAccessory.ts`
new AirQualitySensor(this, accessory, device);
await this.debugLog(`${device.locationName} uuid: ${device.locationName + device.apiKey + device.zipCode}`);
await this.debugLog(`${device.city} uuid: ${device.city + device.apiKey + device.zipCode}`);
// link the accessory to your platform

@@ -174,3 +174,3 @@ this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);

else {
this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.locationName)}`);
this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.city)}`);
}

@@ -177,0 +177,0 @@ }

@@ -1,2 +0,2 @@

import type { PlatformConfig } from 'homebridge';
import { type PlatformConfig } from 'homebridge';
/**

@@ -14,2 +14,3 @@ * This is the name of the platform that users will use to register the plugin in the Homebridge config.json

export declare const AirNowUrl = "https://www.airnowapi.org/aq/observation/zipCode/current/?format=application/json";
export declare const AqicnUrl = "http://api.waqi.info/feed/";
export interface AirPlatformConfig extends PlatformConfig {

@@ -25,3 +26,3 @@ name?: string;

distance?: string;
locationName?: string;
city?: string;
zipCode?: string;

@@ -37,3 +38,3 @@ firmware: string;

}
interface AirQualityData {
interface AirNowAirQualityData {
DateObserved: string;

@@ -50,4 +51,46 @@ HourObserved: number;

}
export type AirQualityDataArray = AirQualityData[];
export type AirNowAirQualityDataArray = AirNowAirQualityData[];
export interface AqicnData {
status: string;
data: {
idx: number;
aqi: number;
time: {
s: string;
tz: string;
};
city: {
name: string;
geo: [number, number];
url: string;
};
attributions: {
name: string;
url: string;
}[];
iaqi: {
pm25: {
v: number;
};
};
forecast: {
daily: {
pm25: {
v: number;
}[];
pm10: {
v: number;
}[];
o3: {
v: number;
}[];
uvi: {
v: number;
}[];
};
};
};
}
export declare function HomeKitAQI(aqi: number): number;
export {};
//# sourceMappingURL=settings.d.ts.map

@@ -13,2 +13,26 @@ /**

export const AirNowUrl = 'https://www.airnowapi.org/aq/observation/zipCode/current/?format=application/json';
export const AqicnUrl = 'http://api.waqi.info/feed/';
export function HomeKitAQI(aqi) {
if (!aqi) {
return (0); // Error or unknown response (this.hap.Characteristic.AirQuality.UNKNOWN)
}
else if (aqi <= 50) {
return (1); // Return EXCELLENT (this.hap.Characteristic.AirQuality.EXCELLENT)
}
else if (aqi >= 51 && aqi <= 100) {
return (2); // Return GOOD (this.hap.Characteristic.AirQuality.GOOD)
}
else if (aqi >= 101 && aqi <= 150) {
return (3); // Return FAIR (this.hap.Characteristic.AirQuality.FAIR)
}
else if (aqi >= 151 && aqi <= 200) {
return (4); // Return INFERIOR (this.hap.Characteristic.AirQuality.INFERIOR)
}
else if (aqi >= 201) {
return (5); // Return POOR (Homekit only goes to cat 5, so combined the last two AQI cats of Very Unhealty and Hazardous. (this.hap.Characteristic.AirQuality.POOR)
}
else {
return (0); // Error or unknown response.
}
}
//# sourceMappingURL=settings.js.map

@@ -5,3 +5,3 @@ {

"type": "module",
"version": "1.0.0-beta.2",
"version": "1.0.0-beta.3",
"description": "The AirNow plugin allows you to monitor the current AirQuality for your Zip Code from HomeKit and Siri.",

@@ -33,3 +33,9 @@ "author": {

"air",
"Air Quality"
"Air Quality",
"homebridge",
"airnow",
"aqicn",
"air quality",
"waqi",
"pollution"
],

@@ -62,16 +68,16 @@ "main": "dist/index.js",

"devDependencies": {
"@antfu/eslint-config": "^3.3.2",
"@antfu/eslint-config": "^3.6.2",
"@types/aes-js": "^3.1.4",
"@types/debug": "^4.1.12",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.12",
"@types/jest": "^29.5.13",
"@types/mdast": "^4.0.4",
"@types/node": "^22.5.4",
"@types/node": "^22.5.5",
"@types/semver": "^7.5.8",
"@types/source-map-support": "^0.5.10",
"@vitest/coverage-v8": "^2.0.5",
"@vitest/coverage-v8": "^2.1.1",
"eslint": "^9.10.0",
"eslint-plugin-format": "^0.1.2",
"homebridge": "^1.8.4",
"homebridge-config-ui-x": "4.57.1",
"homebridge-config-ui-x": "4.58.0",
"jest": "^29.7.0",

@@ -82,7 +88,7 @@ "nodemon": "^3.1.4",

"ts-node": "^10.9.2",
"typedoc": "^0.26.6",
"typescript": "^5.5.4",
"typedoc": "^0.26.7",
"typescript": "^5.6.2",
"typescript-axios-wb": "^1.0.3",
"vitest": "^2.0.5"
"vitest": "^2.1.1"
}
}

@@ -1,2 +0,1 @@

<span align="center">

@@ -13,3 +12,3 @@

<p>The Homebridge <a href="https://airnow.gove">Air</a>
<p>The Homebridge <a href="https://airnow.gove">Air</a>
plugin allows you monitor the current AirQuality for your Zip Code from HomeKit and Siri.

@@ -39,4 +38,17 @@ </p>

## Supported Air Quality Providers
Currently supports AQI Services:
- [AirNow](https://www.airnow.gov/) which is limited to the USA. A valid ZipCode is required.
- [Aqicn](https://www.aqicn.org/) which has international support, provided by the [World Air Quality Index Project](http://waqi.info/).
Depending on where exactly you would like to monitor AQI, one service may be more appropriate than the other.
## Supported Air Quality Features
This plugin will create an AirQualitySensor element. The Home app works well, but the Eve app seems to show more measurements. Measurements retrieved are PM2.5, PM10, & O3 for AirNow. Aqicn adds NO2, SO2, CO...
This plugin will create an AirQualitySensor element. The Home app works well, but the Eve app seems to show more measurements. Measurements retrieved are PM2.5, PM10, & O3 for AirNow. Aqicn adds NO2, SO2, CO...
## Thanks
Thank you to [ToddGreenfield](https://github.com/ToddGreenfield) for the the work done on the accesorry based plugin [homebridge-airnow](https://github.com/ToddGreenfield/homebridge-airnow/blob/master/README.md).

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc