Skip to content

Commit

Permalink
Merge pull request #5 from mfkrause/develop
Browse files Browse the repository at this point in the history
v0.9.1 - Fixed Mac start-up, sensor type config
  • Loading branch information
mfkrause authored Jan 22, 2021
2 parents c013911 + 27dff51 commit af28f6c
Show file tree
Hide file tree
Showing 14 changed files with 3,778 additions and 151 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {}
"rules": {
"max-classes-per-file": "off"
}
}
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# homebridge-people-pro

This is a plugin for [homebridge](https://github.com/nfarina/homebridge). It monitors who is at home, based on their smartphone being seen on the network recently.
If you use the Elgato Eve app you can also see the history of every person sensor (powered by [fakegato](https://github.com/simont77/fakegato-history])).
If you use the Elgato Eve app you can also see the history of every person sensor (powered by [fakegato](https://github.com/simont77/fakegato-history]) - only works if you keep the sensor type "motion" in the plugin configuration).

It can also optionally spin up a webserver and receive webhooks sent by location-aware mobile apps (such as [Locative](https://my.locative.io), which can use iBeacons and geofencing to provide faster and more accurate location information.

Expand All @@ -25,20 +25,20 @@ See `config-sample.json` for an example config. This plugin can also be configur
| `nooneSensorName` | optional, default: "No One" |
| `webhookEnabled` | optional, default: false, enable webhook functionality / webserver |
| `webhookPort` | optional, default: 51828 |
| `cacheDirectory` | optional, default: "./.node-persist/storage" |
| `people` | array of objects of the sensors / people to set-up, see below for configuration of every sensor |

## Sensors / People Configuration

| Parameter | Note |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target` | may be either a hostname or IP address |
| `name` | a human-readable name for your sensor |
| `threshold` | optional, in minutes, default: 15 |
| `pingInterval` | optional, in milliseconds, default: 10000, if set to -1 the ping/arp mechanism will not be used |
| `pingUseArp` | optional, default: false, use ARP lookup tables instead of ICMP ping |
| `ignoreWebhookReEnter` | optional, in seconds, default: 0, if set to 0 every webhook re-enter/exit will trigger state change; otherwise the state will only change if no re-enter/exit occurs in specified number of seconds |
| `excludeFromWebhook` | optional, default: false, if set to true, this sensor won't be able to be managed through webhooks / will ignore webhook requests |
| Parameter | Note |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target` | may be either a hostname or IP address |
| `name` | a human-readable name for your sensor |
| `type` | optional, default: "motion", can be one of "motion", "occupancy". **WARNING**: Choosing something else than "motion" will disable fakegato / Elgato Eve history functionality. |
| `threshold` | optional, in minutes, default: 15 |
| `pingInterval` | optional, in milliseconds, default: 10000, if set to -1 the ping/arp mechanism will not be used |
| `pingUseArp` | optional, default: false, use ARP lookup tables instead of ICMP ping |
| `ignoreWebhookReEnter` | optional, in seconds, default: 0, if set to 0 every webhook re-enter/exit will trigger state change; otherwise the state will only change if no re-enter/exit occurs in specified number of seconds |
| `excludeFromWebhook` | optional, default: false, if set to true, this sensor won't be able to be managed through webhooks / will ignore webhook requests |

# How it works

Expand Down
182 changes: 130 additions & 52 deletions base/accessory.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ const ping = require('ping');
const moment = require('moment');
const arp = require('node-arp');

const {
LastActivationCharacteristic,
SensitivityCharacteristic,
DurationCharacteristic,
} = require('./characteristics');

class PeopleProAccessory {
constructor(log, config, platform) {
this.log = log;
this.name = config.name;
this.type = 'motion';
if (typeof config.type !== 'undefined' && config.type !== null) {
if (typeof config.type !== 'string' || (config.type !== 'motion' && config.type !== 'occupancy')) {
log(`Type "${config.type}" for sensor ${config.name} is invalid. Defaulting to "motion".`);
} else {
this.type = config.type;
}
}
this.target = config.target;
this.excludedFromWebhook = config.excludedFromWebhook;
this.platform = platform;
Expand All @@ -20,44 +22,111 @@ class PeopleProAccessory {
this.stateCache = false;
this.pingUseArp = ((typeof (config.pingUseArp) !== 'undefined' && config.pingUseArp !== null) ? config.pingUseArp : false);

this.service = new Service.MotionSensor(this.name);
this.service
.getCharacteristic(Characteristic.MotionDetected)
.on('get', this.getState.bind(this));

this.service.addCharacteristic(LastActivationCharacteristic);
this.service
.getCharacteristic(LastActivationCharacteristic)
.on('get', this.getLastActivation.bind(this));

this.service.addCharacteristic(SensitivityCharacteristic);
this.service
.getCharacteristic(SensitivityCharacteristic)
.on('get', (callback) => {
callback(null, 4);
});
// Set services and characteristics based on configured sensor type
if (this.type === 'motion') {
this.service = new Service.MotionSensor(this.name);
this.service
.getCharacteristic(Characteristic.MotionDetected)
.on('get', this.getState.bind(this));

class LastActivationCharacteristic extends Characteristic {
constructor() {
super('LastActivation', 'E863F11A-079E-48FF-8F27-9C2605A29F52');
this.setProps({
format: Characteristic.Formats.UINT32,
unit: Characteristic.Units.SECONDS,
perms: [
Characteristic.Perms.READ,
Characteristic.Perms.NOTIFY,
],
});
}
}

this.service.addCharacteristic(DurationCharacteristic);
this.service
.getCharacteristic(DurationCharacteristic)
.on('get', (callback) => {
callback(null, 5);
class DurationCharacteristic extends Characteristic {
constructor() {
super('Duration', 'E863F12D-079E-48FF-8F27-9C2605A29F52');
this.setProps({
format: Characteristic.Formats.UINT16,
unit: Characteristic.Units.SECONDS,
minValue: 5,
maxValue: 15 * 3600,
validValues: [
5, 10, 20, 30,
1 * 60, 2 * 60, 3 * 60, 5 * 60, 10 * 60, 20 * 60, 30 * 60,
1 * 3600, 2 * 3600, 3 * 3600, 5 * 3600, 10 * 3600, 12 * 3600, 15 * 3600,
],
perms: [
Characteristic.Perms.READ,
Characteristic.Perms.NOTIFY,
Characteristic.Perms.WRITE,
],
});
}
}

class SensitivityCharacteristic extends Characteristic {
constructor() {
super('Sensitivity', 'E863F120-079E-48FF-8F27-9C2605A29F52');
this.setProps({
format: Characteristic.Formats.UINT8,
minValue: 0,
maxValue: 7,
validValues: [0, 4, 7],
perms: [
Characteristic.Perms.READ,
Characteristic.Perms.NOTIFY,
Characteristic.Perms.WRITE,
],
});
}
}

this.service.addCharacteristic(LastActivationCharacteristic);
this.service
.getCharacteristic(LastActivationCharacteristic)
.on('get', this.getLastActivation.bind(this));

this.service.addCharacteristic(SensitivityCharacteristic);
this.service
.getCharacteristic(SensitivityCharacteristic)
.on('get', (callback) => {
callback(null, 4);
});

this.service.addCharacteristic(DurationCharacteristic);
this.service
.getCharacteristic(DurationCharacteristic)
.on('get', (callback) => {
callback(null, 5);
});

this.accessoryService = new Service.AccessoryInformation();
this.accessoryService
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.SerialNumber, `hps-${this.name.toLowerCase()}`)
.setCharacteristic(Characteristic.Manufacturer, 'Elgato');

this.historyService = new FakeGatoHistoryService('motion', {
displayName: this.name,
log: this.log,
},
{
storage: 'fs',
disableTimer: true,
});
} else {
this.accessoryService = new Service.AccessoryInformation();
this.accessoryService
.setCharacteristic(Characteristic.Name, this.name);

this.accessoryService = new Service.AccessoryInformation();
this.accessoryService
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.SerialNumber, `hps-${this.name.toLowerCase()}`)
.setCharacteristic(Characteristic.Manufacturer, 'Elgato');

this.historyService = new FakeGatoHistoryService('motion', {
displayName: this.name,
log: this.log,
},
{
storage: 'fs',
disableTimer: true,
});
if (this.type === 'occupancy') {
this.service = new Service.OccupancySensor(this.name);
this.service
.getCharacteristic(Characteristic.OccupancyDetected)
.on('get', this.getState.bind(this));
}
}

this.initStateCache();

Expand All @@ -67,21 +136,28 @@ class PeopleProAccessory {
}

/**
* Encodes a given bool state and returns it back as a Characteristic
* Encodes a given bool state
* @param {bool} state The state as a bool
* @returns {object} The state as a Characteristic
* @returns {object} The state as a Characteristic or int
*/
static encodeState(state) {
if (state) return Characteristic.OccupancyDetected.OCCUPANCY_DETECTED;
return Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED;
encodeState(state) {
if (this.type === 'motion') {
if (state) return 1;
return 0;
}
if (this.type === 'occupancy') {
if (state) return Characteristic.OccupancyDetected.OCCUPANCY_DETECTED;
return Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED;
}
return null;
}

/**
* Gets the current state from the cache
* @param {function} callback The function to callback with the current state
*/
getState(callback) {
callback(null, PeopleProAccessory.encodeState(this.stateCache));
callback(null, this.encodeState(this.stateCache));
}

/**
Expand Down Expand Up @@ -211,7 +287,7 @@ class PeopleProAccessory {
if (oldState !== newState) {
this.stateCache = newState;
this.service.getCharacteristic(Characteristic.MotionDetected)
.updateValue(PeopleProAccessory.encodeState(newState));
.updateValue(this.encodeState(newState));

if (this.platform.peopleAnyOneAccessory) {
this.platform.peopleAnyOneAccessory.refreshState();
Expand All @@ -232,10 +308,12 @@ class PeopleProAccessory {
lastWebhookMoment = moment(lastWebhook).format();
}

this.historyService.addEntry({
time: moment().unix(),
status: (newState) ? 1 : 0,
});
if (this.type === 'motion') {
this.historyService.addEntry({
time: moment().unix(),
status: (newState) ? 1 : 0,
});
}
if (this.pingUseArp) {
this.log('Changed occupancy state for %s to %s. Last successful arp lookup %s , last webhook %s .', this.target, newState, lastSuccessfulPingMoment, lastWebhookMoment);
} else {
Expand Down
23 changes: 0 additions & 23 deletions base/characteristics/duration.js

This file was deleted.

9 changes: 0 additions & 9 deletions base/characteristics/index.js

This file was deleted.

15 changes: 0 additions & 15 deletions base/characteristics/last_activation.js

This file was deleted.

18 changes: 0 additions & 18 deletions base/characteristics/sensitivity.js

This file was deleted.

3 changes: 1 addition & 2 deletions base/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ class PeopleProPlatform {
this.nooneSensorName = config.nooneSensorName || 'No One';
this.webhookPort = config.webhookPort || 51828;
this.webhookEnabled = ((typeof (config.webhookEnabled) !== 'undefined' && config.webhookEnabled !== null) ? config.webhookEnabled : false);
this.cacheDirectory = config.cacheDirectory || homebridge.user.persistPath();
this.pingInterval = config.pingInterval || 10000;
this.ignoreWebhookReEnter = config.ignoreWebhookReEnter || 0;
this.people = config.people;
this.storage = storage;
this.storage.initSync({ dir: this.cacheDirectory });
this.storage.initSync({ dir: `${homebridge.user.storagePath()}/plugin-persist/homebridge-people-pro` });
this.webhookQueue = [];
}

Expand Down
4 changes: 2 additions & 2 deletions config-sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"anyoneSensorName": "Anyone",
"nooneSensorName": "No One",
"webhookPort": 51828,
"cacheDirectory": "./.node-persist/storage",
"webhookEnabled": true,
"people" : [
{
Expand All @@ -14,7 +13,8 @@
"threshold" : 15,
"pingInterval": 10000,
"ignoreWebhookReEnter": 0,
"pingUseArp": true
"pingUseArp": true,
"type": "switch"
},
{
"name" : "Someone Else",
Expand Down
Loading

0 comments on commit af28f6c

Please sign in to comment.