From 443351dd8a190bf7ed76e1e77379d2441f821860 Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Sat, 12 Oct 2019 15:11:13 +0200 Subject: [PATCH] feat: replace co2 sensor with air quality sensor --- README.md | 11 ++++++-- config.sample.json | 2 +- config.schema.json | 43 +++++++++++++++++++++++----- src/index.ts | 70 ++++++++++++++++++++++++++++++---------------- 4 files changed, 91 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 7d67b59..9ee0718 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # homebridge-withings-air-quality -This plugin retrieves carbon dioxide and temperature levels from a Withings Smart Body Analyzer smart scale and exposes them as Homekit sensors with [Homebridge](https://github.com/nfarina/homebridge). +This plugin retrieves carbon dioxide and temperature levels from a Withings Smart Body Analyzer smart scale and exposes them as HomeKit sensors with [Homebridge](https://github.com/nfarina/homebridge). ## Installation @@ -29,10 +29,15 @@ Here is a sample config (you can view [config.sample.json](./config.sample.json) "email": "my.withings@email.com", "password": "myWithingsPassword", "mac": "0ab2c3d4e5f6", // Do not add separators - "coThreshold": 1000 // Optional + "levels": [ + 350, // Excellent + 1000, // Good + 2500, // Fair + 5000 // Inferior + ] } ] } ``` -The `coThreshold` parameter is used to trigger an "abnormal level" alert on the carbon dioxide sensor in Homekit. The value is measured in ppm (parts per million). For more information, [check out this page](https://support.withings.com/hc/en-us/articles/201489797-Smart-Body-Analyzer-WS-50-Frequently-asked-questions-about-air-quality-measurements). +The `levels` parameter is used to control the information displayed on the air quality sensor. The values are measured in ppm (parts per million). Carbon dioxide level higher than the value for "Inferior" will trigger a warning in HomeKit. For more information, [check out this page](https://support.withings.com/hc/en-us/articles/201489797-Smart-Body-Analyzer-WS-50-Frequently-asked-questions-about-air-quality-measurements). diff --git a/config.sample.json b/config.sample.json index 343cb76..97d031b 100644 --- a/config.sample.json +++ b/config.sample.json @@ -13,7 +13,7 @@ "email": "my.withings@email.com", "password": "myWithingsPassword", "mac": "0ab2c3d4e5f6", - "coThreshold": 1000 + "levels": [350, 1000, 2500, 5000] } ] } diff --git a/config.schema.json b/config.schema.json index c7f106a..be67bee 100644 --- a/config.schema.json +++ b/config.schema.json @@ -31,13 +31,42 @@ "maxLength": 12, "required": true }, - "coThreshold": { - "title": "Carbon dioxide alert threshold", - "description": "Will trigger the abnormal level alert if your carbon dioxide level is higher than this value.", - "type": "number", - "default": 1000, - "minimum": 300, - "maximum": 100000 + "levels": { + "title": "Air quality levels", + "type": "array", + "minItems": 4, + "uniqueItems": true, + "items": [ + { + "title": "Excellent", + "type": "number", + "default": 350, + "minimum": 0, + "maximum": 1000000 + }, + { + "title": "Good", + "type": "number", + "default": 1000, + "minimum": 0, + "maximum": 1000000 + }, + { + "title": "Fair", + "type": "number", + "default": 2500, + "minimum": 0, + "maximum": 1000000 + }, + { + "title": "Inferior", + "description": "Carbon dioxide levels higher than this value will trigger a warning in HomeKit", + "type": "number", + "default": 5000, + "minimum": 0, + "maximum": 1000000 + } + ] } } } diff --git a/src/index.ts b/src/index.ts index f51d403..811489e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,8 @@ import pkg from '../package.json'; import { WithingsApi } from './lib/api'; import { DataType } from './lib/api.types'; +type AirQualityLevel = { level: number, threshold: number, label: string }; +type Config = { name: string, email: string, password: string, mac: string, levels?: number[] }; type Homebridge = { hap: typeof Hap; registerAccessory(pluginName: string, accessoryName: string, constructor: any, configurationRequestHandler?: any): void; @@ -13,7 +15,13 @@ let Service: typeof Hap.Service; let Characteristic: typeof Hap.Characteristic; const DEFAULT_BATTERY_LOW_LEVEL = 10; -const DEFAULT_CO_THRESHOLD = 1000; +const DEFAULT_AIR_QUALITY_LEVELS: AirQualityLevel[] = [ + { level: Hap.Characteristic.AirQuality.EXCELLENT, threshold: 350, label: 'Excellent' }, + { level: Hap.Characteristic.AirQuality.GOOD, threshold: 1000, label: 'Good' }, + { level: Hap.Characteristic.AirQuality.FAIR, threshold: 2500, label: 'Fair' }, + { level: Hap.Characteristic.AirQuality.INFERIOR, threshold: 5000, label: 'Inferior' }, + { level: Hap.Characteristic.AirQuality.POOR, threshold: Number.MAX_SAFE_INTEGER, label: 'Poor' }, +]; export default function (homebridge: Homebridge) { Service = homebridge.hap.Service; @@ -25,20 +33,28 @@ export default function (homebridge: Homebridge) { class WithingsScale { private readonly api: WithingsApi; private readonly name = this.config.name; - private readonly coThreshold = this.config.coThreshold && parseInt(this.config.coThreshold, 10) || DEFAULT_CO_THRESHOLD; + private readonly levels: AirQualityLevel[]; readonly informationService = new Service.AccessoryInformation('', ''); readonly batteryService = new Service.BatteryService('', ''); - readonly carbonDioxideService = new Service.CarbonDioxideSensor('', ''); + readonly airQualityService = new Service.AirQualitySensor('', ''); readonly temperatureService = new Service.TemperatureSensor('', ''); constructor( private readonly log: any, - private readonly config: Record, + private readonly config: Config, ) { this.api = new WithingsApi(config.email, config.password, config.mac); this.api.on('error', ({ message, error }) => this.log.warn(message, '/', error.message || error)); + if (!config.levels || config.levels.length !== DEFAULT_AIR_QUALITY_LEVELS.length - 1) { + this.levels = DEFAULT_AIR_QUALITY_LEVELS; + } else { + this.levels = DEFAULT_AIR_QUALITY_LEVELS.map((level, index) => { + return { ...level, threshold: config.levels[index] || level.threshold }; + }); + } + this.init(); } @@ -49,24 +65,21 @@ class WithingsScale { this.initServiceEvents( 'battery', this.batteryService.getCharacteristic(Characteristic.BatteryLevel), - () => this.api.getBatteryLevel(), - (value) => this.setBatteryLevel(value), + () => this.updateBatteryLevel(), ); - this.carbonDioxideService.setCharacteristic(Characteristic.Name, 'CO² Level'); + this.airQualityService.setCharacteristic(Characteristic.Name, 'Air Quality'); this.initServiceEvents( 'carbondioxide', - this.carbonDioxideService.getCharacteristic(Characteristic.CarbonDioxideLevel), - () => this.api.getCarbonDioxide(), - (value) => this.setCarbonDioxideLevel(value), + this.airQualityService.getCharacteristic(Characteristic.AirQuality), + () => this.updateAirQuality(), ); this.temperatureService.setCharacteristic(Characteristic.Name, 'Temperature'); this.initServiceEvents( 'temperature', this.temperatureService.getCharacteristic(Characteristic.CurrentTemperature), - () => this.api.getTemperature(), - (value) => this.setTemperature(value), + () => this.updateTemperature(), ); } @@ -83,7 +96,7 @@ class WithingsScale { this.informationService, this.batteryService, this.temperatureService, - this.carbonDioxideService, + this.airQualityService, ]; } @@ -95,37 +108,46 @@ class WithingsScale { private async initServiceEvents( type: DataType, characteristic: Hap.Characteristic, - queryFn: () => number, - updateFn: (value: number) => void, + updateFn: () => Hap.CharacteristicValue, ) { - this.api.on(type, (value) => updateFn(value)); + this.api.on(type, () => updateFn()); characteristic.on('get' as Hap.CharacteristicEventTypes, async (cb: Hap.NodeCallback) => { - const value = queryFn(); - updateFn(value); + const value = updateFn(); cb(null, value); }); } - private setBatteryLevel(value: number) { + private updateBatteryLevel() { + const value = this.api.getBatteryLevel(); this.log.debug(`[Battery Level] ${value}%`); this.batteryService .setCharacteristic(Characteristic.BatteryLevel, value) .setCharacteristic(Characteristic.StatusLowBattery, value < DEFAULT_BATTERY_LOW_LEVEL ? 1 : 0) ; + + return value; } - private setCarbonDioxideLevel(value: number) { - this.log.debug(`[Carbon Dioxide Level] ${value}ppm`); - this.carbonDioxideService - .setCharacteristic(Characteristic.CarbonDioxideDetected, value >= this.coThreshold ? 1 : 0) + private updateAirQuality() { + const value = this.api.getCarbonDioxide(); + const { label, level } = this.levels.find(({ threshold }) => value <= threshold); + + this.log.debug(`[Air Quality] ${label} (${value}ppm)`); + this.airQualityService + .setCharacteristic(Characteristic.AirQuality, level) .setCharacteristic(Characteristic.CarbonDioxideLevel, value) ; + + return level; } - private setTemperature(value: number) { + private updateTemperature() { + const value = this.api.getTemperature(); this.log.debug(`[Temperature] ${value}°C`); this.temperatureService .setCharacteristic(Characteristic.CurrentTemperature, value) ; + + return value; } }