diff --git a/README.md b/README.md index 056401f7..1aa50a45 100755 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ With iOS14, the icons and status have been refined in the iOS/iPadOS/macOS Home For those with multiple Awair devices, you can optionally list the macAddress of the device (found on the back or bottom of the device) which you want to exclude from HomeKit. -For Awair Omni, battery charge level, charging status, low battery, light level and motion detection based on ambient sound level are also provided using the Local Sensors capability which is configured in the Awair App (reference screenshot below). Battery Status does not appear as a separate tile in the HomeKit interface. Battery charge level and status will be found in the Status menu for each of the sensors. A low battery indication will be identified as an alert in the HomeKit status section (see third and fourth screenshots). +For Awair Omni, battery charge level, charging status, low battery, light level and occupancy detection based on ambient sound level [experimental] are also provided using the Local Sensors capability which is configured in the Awair App (reference screenshot below). Battery Status does not appear as a separate tile in the HomeKit interface. Battery charge level and status will be found in the Status menu for each of the sensors. A low battery indication will be identified as an alert in the HomeKit status section (see third and fourth screenshots). ![iOS14 Screenshots](screenshots/Image2.png) @@ -53,9 +53,8 @@ See [config-sample.json](https://github.com/DMBlakeley/homebridge-awair2/blob/ma "carbonDioxideThreshold": 1200, "carbonDioxideThresholdOff": 800, "vocMw": 72.66578273019740, - "autoOccupancy": false, - "occupancyDetectedLevel": 60, - "occupancyNotDetectedLevel": 55, + "occupancyDetection": false, + "occupancyOffset": 2, "logging": false, "verbose": false, "development": false, @@ -79,9 +78,8 @@ Parameter | Description `carbonDioxideThreshold` | (OPTIONAL, default = `0` [i.e. OFF], the level at which HomeKit will trigger an alert for the CO2 in ppm) `carbonDioxideThresholdOff` | (OPTIONAL, default = `0` [i.e. `carbonDioxideThreshold`], the level at which HomeKit will turn off the trigger alert for the CO2 in ppm, to ensure that it doesn't trigger on/off too frequently choose a number lower than `carbonDioxideThreshold`) `vocMw` | (OPTIONAL, default = `72.66578273019740`) The Molecular Weight (g/mol) of a reference gas or mixture that you use to convert from ppb to ug/m^3 -`autoOccupancy` | (OPTIONAL - Omni only, default = `false`) Whether to enable Omni auto Occupancy detection based on minimum sound level detected. If enabled, `occupancyDetectedLevel` and `occupancyDetectedNotLevel`are initial values for the detection algorithm. -`occupancyDetectedLevel` | (OPTIONAL - Omni only, default = `60`) The initial level at which HomeKit will indicate room Occupancy is detected based on room sound level in dBA) -`occupancyDetectedNotLevel` | (OPTIONAL - Omni only, default = `55`) The initial level at which HomeKit will indicate room Occupancy is not detected based on room sound level in dBA) +`occupancyDetection` | (OPTIONAL - Omni only, default = `false`) Enables Omni occupancy detection based on minimum environmental sound level detected. +`occupancyOffset` | (OPTIONAL - Omni only, default = `2`) Used when `occupancy detection` enabled. Offset value in dBA above background sound level to set `not occupied` level, `occupied` is 0.5dBA higher. `logging` | Whether to output logs to the Homebridge logs (OPTIONAL, default = `false`) `verbose` | Whether to log results from API data calls (OPTIONAL, default = `false`). Requires `logging` to be `true`. `development` | Enables Development mode to allow use of `test` Awair devices lacking `end user`/Awair OUI formatted Serial numbers. diff --git a/config-sample.json b/config-sample.json index 2a253741..8d22507e 100644 --- a/config-sample.json +++ b/config-sample.json @@ -19,9 +19,8 @@ "carbonDioxideThreshold": 1200, "carbonDioxideThresholdOff": 800, "vocMw": 72.66578273019740, - "autoOccupancy": false, - "occupancyDetectedLevel": 60, - "occupancyNotDetectedLevel": 55, + "occupancyDetection": false, + "occupancyOffset": 2, "logging": false, "verbose": false, "development": false, diff --git a/config.schema.json b/config.schema.json index ba48b566..aacee9cc 100644 --- a/config.schema.json +++ b/config.schema.json @@ -58,22 +58,17 @@ "placeholder": 72.66578273019740, "description": "The Molecular Weight (g/mol) of a reference gas or mixture that you use to convert from ppb to ug/m^3." }, - "autoOccupancy": { - "title": "Whether to enable auto Occupancy detection based on minimum sound level.", + "occupancyDetection": { + "title": "Omni - Whether to enable auto Occupancy detection based on minimum sound level.", "type": "boolean", - "default": false + "default": false, + "description": "Omni only - enables occupancy detection based on minimum sound level + occupancyOffset value." }, - "occupancyDetectedLevel": { - "title": "Omni - Occupancy Threshold", - "type": "number", - "placeholder": 60, - "description": "Omni only - initial sound pressure level that indicates occupancy." - }, - "occupancyNotDetectedLevel": { - "title": "Omni - Occupancy Threshold Off", + "occupancyOffset": { + "title": "Omni - used when occupancyDetection enabled.", "type": "number", - "placeholder": 55, - "description": "Omni only - initial sound pressure level that indicates unoccupied." + "placeholder": 2, + "description": "Omni only - Used when `occupancy detection` enabled. Offset value in dBA above background sound level to set `not occupied` level, `occupied` is 0.5dBA higher." }, "logging": { "title": "Whether to output logs to the Homebridge logs.", @@ -124,9 +119,8 @@ "carbonDioxideThreshold", "carbonDioxideThresholdOff", "vocMw", - "autoOccupancy", - "occupancyDetectedLevel", - "occupancyNotDetectedLevel", + "occupancyDetection", + "occupancyOffset", "logging", "verbose", "development" diff --git a/package-lock.json b/package-lock.json index 90d4c3fa..8e80f205 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "homebridge-awair2", - "version": "5.5.3", + "version": "5.5.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f1aadd72..776d13d6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": false, "displayName": "Homebridge Awair2", "name": "homebridge-awair2", - "version": "5.5.3", + "version": "5.5.4", "description": "HomeKit integration of Awair air purifier as Dynamic Platform.", "main": "dist/index.js", "scripts": { diff --git a/src/configTypes.ts b/src/configTypes.ts index 2054b0b8..7de59d23 100644 --- a/src/configTypes.ts +++ b/src/configTypes.ts @@ -9,7 +9,8 @@ export type AwairPlatformConfig = { carbonDioxideThreshold: number; carbonDioxideThresholdOff: number; vocMw: number; - autoOccupancy: boolean; + occupancyDetection: boolean; + occupancyOffset: number; occupancyDetectedLevel: number; occupancyNotDetectedLevel: number; logging: boolean; diff --git a/src/index.ts b/src/index.ts index 23818982..c52ecb2e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,8 +45,9 @@ class AwairPlatform implements DynamicPlatformPlugin { private endpoint = '15-min-avg'; private carbonDioxideThreshold = 0; private carbonDioxideThresholdOff = 0; - private occupancyDetectedLevel = 60; - private occupancyNotDetectedLevel = 55; // min level is 50dBA +/- 3dBA due to dust sensor fan noise in Omni + private occupancyOffset = 2.0; + private occDetectedNotLevel = 55; // min level is 50dBA +/- 3dBA due to dust sensor fan noise in Omni + private occDetectedLevel = 60; //default User Info Hobbyist samples per 24 hours reference UTC 00:00:00 private userTier = 'Hobbyist'; @@ -60,7 +61,7 @@ class AwairPlatform implements DynamicPlatformPlugin { private readonly accessories: PlatformAccessory[] = []; private devices: any[] = []; // array of Awair devices private ignoredDevices: string [] = []; // array of ignored Awair devices - private omniDetected = false; // flag that Awair account contains Omni device(s), used to allow occupancy detection loop + private omniPresent = false; // flag that Awair account contains Omni device(s), used to allow occupancy detection loop constructor(log: Logging, config: PlatformConfig, api: API) { this.log = log; @@ -111,12 +112,8 @@ class AwairPlatform implements DynamicPlatformPlugin { this.vocMw = this.config.vocMw; } - if (this.config.occupancyDetectedLevel) { - this.occupancyDetectedLevel = this.config.occupancyDetectedLevel; - } - - if (this.config.occupancyNotDetectedLevel) { - this.occupancyNotDetectedLevel = this.config.occupancyNotDetectedLevel; + if (this.config.occupancyOffset) { + this.occupancyOffset = this.config.occupancyOffset; } if (this.config.ignoredDevices) { @@ -157,7 +154,8 @@ class AwairPlatform implements DynamicPlatformPlugin { } else { if (this.config.logging) { // conditions above _should_ be satisfied, unless the MAC is missing (contact Awair), incorrect, or a testing device - this.log(`Error with Serial ${device.macAddress} on ignore list, does not match Awair OUI "70886B" or not Test device`); + this.log(`Error with Serial ${device.macAddress} on ignore list, does not match Awair OUI "70886B" or is ` + + 'test device (requires development mode enabled to use).'); } } serNums.push(device.macAddress); @@ -193,9 +191,11 @@ class AwairPlatform implements DynamicPlatformPlugin { } this.updateAirData(accessory); if (accessory.context.deviceType === 'awair-omni') { - this.getOmniOccupancyStatus(accessory); this.getOmniBatteryStatus(accessory); } + if ((accessory.context.deviceType === 'awair-omni') && this.config.occupancyDetection) { + this.getOmniOccupancyStatus(accessory); + } if (accessory.context.deviceType === 'awair-omni' || accessory.context.deviceType === 'awair-mint') { this.getOmniMintLightLevel(accessory); // fetch 'lux' and 'spl_a' } @@ -217,8 +217,8 @@ class AwairPlatform implements DynamicPlatformPlugin { }); }, this.polling_interval * 1000); - // if Omni device exists in account, start 30 second loop to test for Omni occupancy status - if(this.omniDetected) { + // if Omni device exists in account & detection enabled, start 30 second loop to test for Omni occupancy status + if(this.omniPresent && this.config.occupancyDetection) { setInterval(() => { this.accessories.forEach(accessory => { if (accessory.context.deviceType === 'awair-omni') { @@ -235,7 +235,7 @@ class AwairPlatform implements DynamicPlatformPlugin { */ configureAccessory(accessory: PlatformAccessory): void { this.log('Loading accessory from cache:', accessory.displayName); - + // add the restored accessory to the accessories cache so we can track if it has already been registered this.accessories.push(accessory); } @@ -400,8 +400,10 @@ class AwairPlatform implements DynamicPlatformPlugin { if (data.deviceType === 'awair-omni') { accessory.addService(hap.Service.BatteryService, data.name + ' Battery'); accessory.addService(hap.Service.OccupancySensor, data.name + ' Occupancy'); - this.omniDetected = true; // set flag for Occupancy detected loop - accessory.context.minsoundlevel = this.occupancyNotDetectedLevel; // context value to track minimum sound level detected + this.omniPresent = true; // set flag for Occupancy detected loop + accessory.context.occDetectedLevel = this.occDetectedLevel; // use context to track occupancy for each Omni device + accessory.context.occDetectedNotLevel = this.occDetectedNotLevel; + accessory.context.minSoundLevel = this.occDetectedNotLevel; } this.addServices(accessory); @@ -411,15 +413,12 @@ class AwairPlatform implements DynamicPlatformPlugin { this.accessories.push(accessory); } else { // acessory exists, use data from cache - accessory.context.name = data.name; - accessory.context.serial = data.macAddress; - accessory.context.deviceType = data.deviceType; - if (data.deviceType === 'awair-omni') { - this.omniDetected = true; // set flag for Occupancy detected loop - accessory.context.minsoundlevel = this.occupancyNotDetectedLevel; // context value to track minimum sound level detected + if (this.config.logging) { + this.log(accessory.context.name + ' exists, using data from cache'); + } + if (accessory.context.deviceType === 'awair-omni') { + this.omniPresent = true; // set flag for Occupancy detected loop } - accessory.context.deviceUUID = data.deviceUUID; - accessory.context.deviceId = data.deviceId; } return; } @@ -797,13 +796,14 @@ class AwairPlatform implements DynamicPlatformPlugin { this.log('[' + accessory.context.serial + '] spl_a: ' + omniSpl_a); } - if((omniSpl_a < accessory.context.minsoundlevel) && this.config.autoOccupancy) { - accessory.context.minsoundlevel = omniSpl_a; - this.occupancyDetectedLevel = accessory.context.minsoundlevel + 1; - this.occupancyNotDetectedLevel = accessory.context.minsoundlevel + 0.5; + if(omniSpl_a < accessory.context.minSoundLevel) { // use context to track occupancy status for each Omni device + accessory.context.minSoundLevel = omniSpl_a; + accessory.context.occDetectedLevel = accessory.context.minSoundLevel + this.occupancyOffset + 0.5; // dBA + accessory.context.occDetectedNotLevel = accessory.context.minSoundLevel + this.occupancyOffset; // dBA if(this.config.logging) { - this.log('[' + accessory.context.serial + '] notDetectedLevel: ' + this.occupancyNotDetectedLevel - + 'dBA, DetectedLevel: ' + this.occupancyDetectedLevel + 'dBA'); + this.log('[' + accessory.context.serial + '] min spl_a: ' + omniSpl_a + 'dBA -> notDetectedLevel: ' + + accessory.context.occDetectedNotLevel + 'dBA, DetectedLevel: ' + + accessory.context.occDetectedLevel + 'dBA'); } } @@ -813,24 +813,27 @@ class AwairPlatform implements DynamicPlatformPlugin { // get current Occupancy state let occupancyStatus: any = occupancyService.getCharacteristic(hap.Characteristic.OccupancyDetected).value; - if (omniSpl_a >= this.occupancyDetectedLevel) { + if (omniSpl_a >= accessory.context.occDetectedLevel) { // occupancy detected occupancyStatus = 1; if(this.config.logging){ - this.log('[' + accessory.context.serial + '] Occupied: ' + omniSpl_a + 'dBA > ' + this.occupancyDetectedLevel + 'dBA'); + this.log('[' + accessory.context.serial + '] Occupied: ' + omniSpl_a + 'dBA > ' + + accessory.context.occDetectedLevel + 'dBA'); } - } else if (omniSpl_a <= this.occupancyNotDetectedLevel) { + } else if (omniSpl_a <= accessory.context.occDetectedNotLevel) { // unoccupied occupancyStatus = 0; if(this.config.logging){ // eslint-disable-next-line max-len - this.log('[' + accessory.context.serial + '] Not Occupied: ' + omniSpl_a + 'dBA < ' + this.occupancyNotDetectedLevel + 'dBA'); + this.log('[' + accessory.context.serial + '] Not Occupied: ' + omniSpl_a + 'dBA < ' + + accessory.context.occDetectedNotLevel + 'dBA'); } - } else if ((omniSpl_a > this.occupancyNotDetectedLevel) && (omniSpl_a < this.occupancyDetectedLevel)) { + } else if ((omniSpl_a > accessory.context.occDetectedNotLevel) && (omniSpl_a < accessory.context.occDetectedLevel)) { // inbetween ... no change, use current state if(this.config.logging){ - this.log('[' + accessory.context.serial + '] Occupancy Inbetween: ' + this.occupancyNotDetectedLevel + 'dBA < ' - + omniSpl_a + ' < ' + this.occupancyDetectedLevel + 'dBA'); + this.log('[' + accessory.context.serial + '] Occupancy Inbetween: ' + + accessory.context.occDetectedNotLevel + 'dBA < ' + omniSpl_a + ' < ' + + accessory.context.occDetectedLevel + 'dBA'); } } occupancyService