Skip to content
This repository has been archived by the owner on Jan 21, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1 from Djelibeybi/2.0
Browse files Browse the repository at this point in the history
Create homebridge-delayed-occupancy v2.0
  • Loading branch information
Djelibeybi committed Apr 18, 2021
2 parents baf7221 + 6a9044b commit 4c5f673
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 90 deletions.
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
# "Occupancy Delay" Plugin
# Delayed Occupancy Sensor Plugin for Homebridge


## How to install

```sudo npm install -g homebridge-occupancy-delay```

## Example config.json:

```
## Example `config.json`

```json
"accessories": [
{
"accessory": "OccupancyDelay",
"name": "OccupancyDelay",
"accessory": "delayed-occupancy-sensor",
"name": "Delayed Occupancy Sensor",
"delay": 5,
"slaveCount": 1
}
"switches": [
"First activation switch",
"Second activation switch"
]
}
]
```

Note, "delay" is in seconds.
Note, "delay" is in seconds. The `switches` list is optional. The plugin will
always create at least one activation switch.

## What problem will this solve?

Expand All @@ -28,7 +32,7 @@ In iOS11 you are now able to turn off an automation after a certain number of mi
- You have a motion sensor in your laundry room
- When motion is detected, power on the light, set the brightness to 50%
- When motion is not detected, power on the light, turn off after 2 minutes

You need to power "on" the lights in the last step so that homekit has a target to turn off. To create this automation requires you to set up everything in the home app, make tweaks in a 3rd party app such as Eve.

Lets run some scenarios with this setup.
Expand All @@ -55,7 +59,7 @@ Uh-oh, you didn't want the lights to power back on when you had specifically tur
- You briefly exit the room (or stand still for to long), the motion sensor becomes inactive, the lights power on and the "turn off" timer starts.
- You re-enter the room (or move), the lights power on and turn to 50%.
- You work in the room for a bit, 2 minutes later the lights turn off, the motion sensor is still active.

That's no good, now you're in the dark. Moving around doesn't help you here because the motion sensor is already still active.
What are you supposed to do? Leave the room long enough for the motion sensor to read inactive, then move around the room to activate it?

Expand Down Expand Up @@ -83,7 +87,7 @@ Sounds simple enough. What this plugin does is crates one or more "dummy" switch
- When motion is detected, turn "dummy" switch "on".
- When motion is not-detected, turn "dummy" switch "off".
- (If you want you can tie more sensors to more "dummy" switches and the occupancy sensor will wait for all the switches to be "off" before starting the "turn off" timer).

Now the light is tied to the smarter occupancy sensor, not an erratic motion sensor. The "dummy" switch(es) directly reflects the motion sensor(s). This allows the software "dummy" switch to do it's magic with the occupancy sensor.


Expand Down Expand Up @@ -113,7 +117,7 @@ In this situation the lights are turned off twice without ever being turned on b
- You work in the room for a bit, 2 minutes later, nothing happens because you're still in the room!
- You leave the room, the motion sensor becomes inactive, dummy switch turns "off", occupancy sensor still reads "occupied" but starts internal "turn off" timer.
- 2 minutes later the occupancy sensor's "turn off" timer expires and the occupancy sensor is switched to "unoccupied", lights are triggered to turn off.

While there are quite a few steps happening here the setup is quite resilient. Even manually controlling of the "dummy" switches they will automatically come back into sync once the motion sensor changes state again.


Expand All @@ -123,10 +127,10 @@ Multi sensor setup is easy with this plugin because you simply tell the plugin h

### Walkway Lights
Say you have a long walk way, you could put a motion sensor at the bottom of the walk way, one at the top of the walk way and any number in between (if it makes sense), then tie each sensor to one "dummy" switch, then tie the occupancy sensor to your walkway lights. Now when someone walks up as long as they are in range of any of the motion sensors the lights will stay on. Once they safely enter the house (or leave your property if they are walking away) the lights turn off on a delay.

### Stairway Lights
Using the same setup as above you could have a motion sensor at the bottom of the stairs, one at the top and a short delay.


## Advanced Uses
You should be able to do some pretty advance stuff by tying a single sensor (motion, contact, light bulb) to more than one "dummy" switch.
You should be able to do some pretty advance stuff by tying a single sensor (motion, contact, light bulb) to more than one "dummy" switch.
135 changes: 64 additions & 71 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use strict";

var inherits = require('util').inherits;
const inherits = require('util').inherits;
const inspect = require('util').inspect;

var Service, Characteristic;

module.exports = function(homebridge) {
Expand All @@ -12,10 +14,10 @@ module.exports = function(homebridge) {


/**
* Characteristic "Time Remaining"
* Characteristic "Delay Time Remaining"
*/
Characteristic.TimeRemaining = function() {
Characteristic.call(this, 'Time Remaining', '1000006D-0000-1000-8000-0026BB765291');
Characteristic.DelayTimeRemaining = function() {
Characteristic.call(this, 'Delay Time Remaining', '1000006D-0000-1000-8000-0026BB765291');
this.setProps({
format: Characteristic.Formats.UINT64,
unit: Characteristic.Units.SECONDS,
Expand All @@ -26,15 +28,15 @@ module.exports = function(homebridge) {
});
this.value = this.getDefaultValue();
};
inherits(Characteristic.TimeRemaining, Characteristic);
Characteristic.TimeRemaining.UUID = '1000006D-0000-1000-8000-0026BB765291';
inherits(Characteristic.DelayTimeRemaining, Characteristic);
Characteristic.DelayTimeRemaining.UUID = '1000006D-0000-1000-8000-0026BB765291';


/**
* Characteristic "Timeout Delay"
* Characteristic "Delay Time in Seconds"
*/
Characteristic.TimeoutDelay = function() {
Characteristic.call(this, 'Timeout Delay', '1100006D-0000-1000-8000-0026BB765291');
Characteristic.DelayTime = function() {
Characteristic.call(this, 'Delay Time', '1100006D-0000-1000-8000-0026BB765291');
this.setProps({
format: Characteristic.Formats.UINT64,
unit: Characteristic.Units.SECONDS,
Expand All @@ -45,45 +47,43 @@ module.exports = function(homebridge) {
});
this.value = this.getDefaultValue();
};
inherits(Characteristic.TimeoutDelay, Characteristic);
Characteristic.TimeoutDelay.UUID = '1100006D-0000-1000-8000-0026BB765291';

inherits(Characteristic.DelayTime, Characteristic);
Characteristic.DelayTime.UUID = '1100006D-0000-1000-8000-0026BB765291';


homebridge.registerAccessory("homebridge-occupancy-delay", "OccupancyDelay", OccupancyDelay);
homebridge.registerAccessory("@djelibeybi/homebridge-delayed-occupancy", "delayed-occupancy-sensor", DelayedOccupancy);
};



/**
* This accessory publishes an Occupancy Sensor as well as 1 or more slave
* Switches to control the status of the sensor. If any of the slaves are on
* This accessory publishes an Occupancy Sensor as well as 1 or more activation
* switches to control the status of the sensor. If any of the slaves are on
* then this sensor registers as "Occupancy Detected" ("Occupied). When all
* slaves are turned off this will remain "Occupied" for as long as the
* specified delay.
*
* Config:
*
* name: The name of this Occupancy Sensor and it's slave switches. If there are
* name: The name of this Occupancy Sensor and it's activation switches. If there are
* more than one slaves they will become "name 1", "name 2", etc.
* slaveCount (optional): Will create 1 slave Switch with the same name as the
* Occupancy Sensor by default. Change this if you need more than 1 Switch
* switchCount (optional): Will create 1 activation switch with the same name as the
* Occupancy Sensor by default. Change this if you need more than 1 switch
* to control the sensor.
* delay: If set to less than 1 there will be no delay when all Switches are
* delay: If set to less than 1 there will be no delay when all switches are
* turned to off. Specify a number in seconds and the sensor will wait
* that long after all switches have been turned off to become
* "Un-occupied". If any slave Switch is turned on the counter will clear
* and start over once all Switches are off again.
* "Un-occupied". If any activation switch is turned on the counter will clear
* and start over once all switches are off again.
*
*
* What can I do with this plugin?
* @todo: Addd use case and instructions here.
*/
class OccupancyDelay {
class DelayedOccupancy {
constructor(log, config) {
this.log = log;
this.name = config.name || "OccupancyDelay";
this.slaveCount = Math.max(1, (config.slaveCount || 1));
this.name = config.name || "Delayed Occupancy Sensor";
this.switches = config.switches || [];
this.delay = Math.min(3600, Math.max(0, parseInt(config.delay, 10) || 0));


Expand All @@ -97,29 +97,26 @@ class OccupancyDelay {
this.switchServices = [];
this.occupancyService = new Service.OccupancySensor(this.name);

this.occupancyService.addCharacteristic(Characteristic.TimeoutDelay);
this.occupancyService.setCharacteristic(Characteristic.TimeoutDelay, this.delay);
this.occupancyService.getCharacteristic(Characteristic.TimeoutDelay).on('change', (event) => {
this.occupancyService.addCharacteristic(Characteristic.DelayTime);
this.occupancyService.setCharacteristic(Characteristic.DelayTime, this.delay);
this.occupancyService.getCharacteristic(Characteristic.DelayTime).on('change', (event) => {
this.log('Setting delay to:', event.newValue);
this.delay = event.newValue;
});

this.occupancyService.addCharacteristic(Characteristic.TimeRemaining);
this.occupancyService.setCharacteristic(Characteristic.TimeRemaining, 0);

this.occupancyService.addCharacteristic(Characteristic.DelayTimeRemaining);
this.occupancyService.setCharacteristic(Characteristic.DelayTimeRemaining, 0);



/* Make the slave Switches */
if (1 === this.slaveCount) {
this.log('Making a single slave switch');
this.switchServices.push(this._createSwitch());
if (this.switches.length === 0) {
this.switchCount = 1;
this.switchServices.push(this._createSwitch('Occupancy Delay Activation Switch'));
} else {
this.log('Making ' + this.slaveCount + ' salve switches');
for (let i = 0, c = this.slaveCount; i < c; i += 1) {
this.switchServices.push(this._createSwitch(i + 1));
this.switchCount = this.switches.length;
for (const activationSwitch of this.switches) {
this.switchServices.push(this._createSwitch(activationSwitch));
}
}

}

/**
Expand All @@ -128,7 +125,6 @@ class OccupancyDelay {
start() {
this.stop();
this._timer_started = (new Date()).getTime();
this.log('Timer started:', this.delay);
if (this.delay) {
this._timer = setTimeout(this.setOccupancyNotDetected.bind(this), (this.delay * 1000));
this._timer_delay = this.delay;
Expand All @@ -137,7 +133,7 @@ class OccupancyDelay {
newValue = Math.round(this._timer_delay - elapsed);

if (newValue !== this._interval_last_value) {
this.occupancyService.setCharacteristic(Characteristic.TimeRemaining, newValue);
this.occupancyService.setCharacteristic(Characteristic.DelayTimeRemaining, newValue);
this._interval_last_value = newValue;
}
}, 250);
Expand All @@ -148,11 +144,10 @@ class OccupancyDelay {
};

/**
* Stops teh countdown timer
* Stops the delay countdown timer
*/
stop() {
if (this._timer) {
this.log('Timer stopped');
clearTimeout(this._timer);
clearInterval(this._interval);
this._timer = null;
Expand All @@ -165,29 +160,31 @@ class OccupancyDelay {

setOccupancyDetected() {
this._last_occupied_state = true;
this.log('Sensor is now detecting occupancy.');
this.occupancyService.setCharacteristic(Characteristic.OccupancyDetected, Characteristic.OccupancyDetected.OCCUPANCY_DETECTED);
if (this.delay) {
this.occupancyService.setCharacteristic(Characteristic.TimeRemaining, this.delay);
this.occupancyService.setCharacteristic(Characteristic.DelayTimeRemaining, this.delay);
}
}

setOccupancyNotDetected() {
this._last_occupied_state = false;
this.stop();
this.log('Sensor is no longer detecting occupancy.');
this.occupancyService.setCharacteristic(Characteristic.OccupancyDetected, Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED);
if (this.delay) {
this.occupancyService.setCharacteristic(Characteristic.TimeRemaining, 0);
this.occupancyService.setCharacteristic(Characteristic.DelayTimeRemaining, 0);
}
}

/**
* Checks all the slave Switches to see if any of them are on. If so this
* Checks all the activation switches to see if any of them are on. If so this
* Occupancy Sensor will remain "Occupied". This is used as a callback when
* the "On" state changes on any of the slave Switches.
* the "On" state changes on any of the activation switches.
*/
checkOccupancy() {
var occupied = 0,
remaining = this.slaveCount,
remaining = this.switchCount,

/* callback for when all the switches values have been returend */
return_occupancy = (occupied) => {
Expand All @@ -201,8 +198,6 @@ class OccupancyDelay {
this.start();
}

// @todo: Set a custom property for how many switches we're waiting for
this.log('checkOccupancy: ' + occupied);
},

/*
Expand All @@ -221,8 +216,8 @@ class OccupancyDelay {
};


/* look at all the slave switches "on" characteristic and return to callback */
for (let i = 0; i < this.slaveCount; i += 1) {
/* look at all the activation switches "on" characteristic and return to callback */
for (let i = 0; i < this.switchCount; i += 1) {
this.switchServices[i]
.getCharacteristic(Characteristic.On)
.getValue(function(err, value) {
Expand All @@ -241,38 +236,36 @@ class OccupancyDelay {
*/
getServices() {
var informationService = new Service.AccessoryInformation()
.setCharacteristic(Characteristic.Manufacturer, 'github.com/archanglmr')
.setCharacteristic(Characteristic.Model, '1.0.1')
.setCharacteristic(Characteristic.SerialNumber, '20171019');

.setCharacteristic(Characteristic.Manufacturer, 'Djelibeybi')
.setCharacteristic(Characteristic.Model, 'Delayed Occupancy Sensor')
.setCharacteristic(Characteristic.FirmwareRevision, '2.0');

return [this.occupancyService, informationService, ...this.switchServices]
}

/**
* Internal helper function to create a new "Switch" that is ties to the
* status of this Occupancy Snesor.
* Internal helper function to create a virtual switch that is tied to the
* status of this Occupancy Sensor.
*
* @param name
* @returns {Service.Switch|*}
* @private
*/
_createSwitch(name) {
var displayName = (name || '').toString(),
sw;
_createSwitch(displayName) {
var sw;

if (displayName.length) {
displayName = this.name + ' ' + displayName;
} else {
displayName = this.name;
}

this.log('Create Switch: ' + displayName);
sw = new Service.Switch(displayName, name);
sw = new Service.Switch(displayName, displayName);
sw.setCharacteristic(Characteristic.On, false);
sw.getCharacteristic(Characteristic.On).on('change', this.checkOccupancy.bind(this));

sw.getCharacteristic(Characteristic.On).onSet(async (value) => {
var state;
this.log('%s turned %s', displayName, state = value ? 'on' : 'off');
});

this.log('Created occupancy activation switch: ' + displayName);

return sw;
}
}

}
16 changes: 16 additions & 0 deletions package-lock.json

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

Loading

0 comments on commit 4c5f673

Please sign in to comment.