Skip to content

Commit

Permalink
Omni Occupancy Detection Improvements
Browse files Browse the repository at this point in the history
Omni occupancy is now determined by monitoring minimum sound level and setting a lower and upper limit based on offset value.
  • Loading branch information
DMBlakeley committed Nov 19, 2020
1 parent 923e9f8 commit d9829a1
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 66 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions config-sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 10 additions & 16 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -124,9 +119,8 @@
"carbonDioxideThreshold",
"carbonDioxideThresholdOff",
"vocMw",
"autoOccupancy",
"occupancyDetectedLevel",
"occupancyNotDetectedLevel",
"occupancyDetection",
"occupancyOffset",
"logging",
"verbose",
"development"
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
3 changes: 2 additions & 1 deletion src/configTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
77 changes: 40 additions & 37 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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'
}
Expand All @@ -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') {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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');
}
}

Expand All @@ -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
Expand Down

0 comments on commit d9829a1

Please sign in to comment.