Skip to content

Commit

Permalink
Merge pull request #23 from SphtKr/feature-interlock
Browse files Browse the repository at this point in the history
Feature interlock--see README.md changes.
  • Loading branch information
SphtKr committed Apr 6, 2016
2 parents 49c2850 + 50197ca commit 5c1bf5d
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 18 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,17 @@ Like [`Homebridge.Service.Type`](#homebridgeservicetypevalue), this allows you t

This tag is particularly useful for scenarios where the physical device is reported ambiguously by Z-Way. For instance, the Vision ZP 3012 motion sensor is presented by Z-Way merely as two `sensorBinary` devices (plus a temperature sensor), one of which is the actual motion sensor and the other is a tampering detector. The `sensorBinary` designation (with no accompanying `probeTitle`) is too ambiguous for the bridge to work with, so it will be ignored. To make this device work, you can tag the motion sensor device in Z-Way with `Homebridge.Characteristic.Type:MotionDetected` and (optionally) the tamper detector with `Homebridge.Characteristic.Type:StatusTampered`. (Note that for this device you will also need to tag the motion sensor with `Homebridge.Service.Type:MotionSensor` and `Homebridge.IsPrimary`, otherwise the more recognizable temperature sensor will take precedence.)

#### Homebridge.Interlock

Adding the tag `Homebridge.Interlock` to the primary device will add an additional `Switch` service named "Interlock", defaulted to "on". When this switch is engaged, you will not be able to set the characteristics of any other devices in the accessory! You will be required to turn off the Interlock switch before changing/setting other values. This is a kind of a "safety" switch, so that you (or Siri) does not turn something on or off that you did not intend. A use case might be if you had your cable modem or router plugged into a power outlet switch so that you could power cycle it remotely: you would not want to turn this off accidentally, so add an Interlock switch. **Do NOT rely on this capability for health or life safety purposes--it is a convenience and is not designed or intended to be a robust safety feature.**

#### Homebridge.ContactSensorState.Invert

If you have a `ContactSensor`, this will invert the state reported to HomeKit. This is useful if you are using the `ContactSensor` Service type for a `Door/Window` sensor, and you want it to show "Yes" when open and "No" when closed, which may be more intuitive. The default for a `ContactSensor` is to show "Yes" when there is contact (in the case of a door, when it's closed) and "No" when there is no contact (which for a door is when it's open).

#### Homebridge.OutletInUse.Level:*value*

This can be used in conjunction with the `Homebridge.Service.Type:Outlet` tag and lets you change the threshold value that changes the `OutletInUse` value to true for a particular device. The main use case is if you have a USB charger or transformer that always consumes a given amount of power, but you want events to trigger when the consumption rises above that level (e.g. when a device is plugged into the USB charger and draws more power). You could also adjust this to trigger only when the higher settings on a 3-way lamp are used, when a fan is turned to high speed, or other creative purposes.
This can be used in conjunction with the `Homebridge.Service.Type:Outlet` tag and lets you change the threshold value that changes the `OutletInUse` value to true for a particular device. The main use case is if you have a USB charger or transformer that always consumes a given amount of power, but you want events to trigger when the consumption rises above that level (e.g. when a device is plugged into the USB charger and draws more power). You could also adjust this to trigger only when the higher settings on a 3-way lamp are used, when a fan is turned to high speed, or other creative purposes.

# Technical Detail

Expand Down
64 changes: 48 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,12 @@ ZWayServerAccessory.prototype = {
url: this.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id + '/command/' + command,
qs: (value === undefined ? undefined : value)
});
},

}
,
isInterlockOn: function(){
return !!this.interlock.value;
}
,
rgb2hsv: function(obj) {
// RGB: 0-255; H: 0-360, S,V: 0-100
var r = obj.r/255, g = obj.g/255, b = obj.b/255;
Expand Down Expand Up @@ -594,6 +598,16 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
this.platform.cxVDevMap[vdev.id].push(cx);
if(!this.platform.vDevStore[vdev.id]) this.platform.vDevStore[vdev.id] = vdev;

var interlock = function(fnDownstream){
return function(newval, callback){
if(this.isInterlockOn()){
callback(new Error("Interlock is on! Changes locked out!"));
} else {
fnDownstream(newval, callback);
}
}.bind(accessory);
};

if(cx instanceof Characteristic.Name){
cx.zway_getValueFromVDev = function(vdev){
return vdev.metrics.title;
Expand Down Expand Up @@ -632,11 +646,11 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('set', function(powerOn, callback){
cx.on('set', interlock(function(powerOn, callback){
this.command(vdev, powerOn ? "on" : "off").then(function(result){
callback();
});
}.bind(this));
}.bind(this)));
cx.on('change', function(ev){
debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue);
});
Expand Down Expand Up @@ -688,11 +702,11 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('set', function(level, callback){
cx.on('set', interlock(function(level, callback){
this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){
callback();
});
}.bind(this));
}.bind(this)));
return cx;
}

Expand All @@ -709,7 +723,7 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('set', function(hue, callback){
cx.on('set', interlock(function(hue, callback){
var scx = service.getCharacteristic(Characteristic.Saturation);
var vcx = service.getCharacteristic(Characteristic.Brightness);
if(!scx || !vcx){
Expand All @@ -720,7 +734,7 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){
callback();
});
}.bind(this));
}.bind(this)));

return cx;
}
Expand All @@ -738,7 +752,7 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('set', function(saturation, callback){
cx.on('set', interlock(function(saturation, callback){
var hcx = service.getCharacteristic(Characteristic.Hue);
var vcx = service.getCharacteristic(Characteristic.Brightness);
if(!hcx || !vcx){
Expand All @@ -749,7 +763,7 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){
callback();
});
}.bind(this));
}.bind(this)));

return cx;
}
Expand Down Expand Up @@ -804,12 +818,12 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('set', function(level, callback){
cx.on('set', interlock(function(level, callback){
this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){
//debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + ".");
callback();
});
}.bind(this));
}.bind(this)));
cx.setProps({
minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5,
maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40
Expand Down Expand Up @@ -857,10 +871,10 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
callback(false, Characteristic.TargetHeatingCoolingState.HEAT);
});
// Hmm... apparently if this is not setable, we can't add a thermostat change to a scene. So, make it writable but a no-op.
cx.on('set', function(newValue, callback){
cx.on('set', interlock(function(newValue, callback){
debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!")
callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT);
}.bind(this));
}.bind(this)));
return cx;
}

Expand Down Expand Up @@ -1068,12 +1082,12 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, cx.zway_getValueFromVDev(vdev));
});
cx.on('set', function(level, callback){
cx.on('set', interlock(function(level, callback){
this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){
//debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + ".");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
}.bind(this)));
cx.setProps({
minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 0,
maxValue: vdev.metrics && (vdev.metrics.max !== undefined || vdev.metrics.max != 99) ? vdev.metrics.max : 100
Expand Down Expand Up @@ -1245,12 +1259,30 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
.setCharacteristic(Characteristic.SerialNumber, accId);

var services = [informationService];

services = services.concat(this.getVDevServices(vdevPrimary));
if(services.length === 1){
debug("WARN: Only the InformationService was successfully configured for " + vdevPrimary.id + "! No device services available!");
return services;
}

// Interlock specified? Create an interlock control switch...
if(this.platform.getTagValue(vdevPrimary, "Interlock") && services.length > 1){
var ilsvc = new Service.Switch("Interlock", vdevPrimary.id + "_interlock");
ilsvc.setCharacteristic(Characteristic.Name, "Interlock");

var ilcx = ilsvc.getCharacteristic(Characteristic.On);
ilcx.value = false; // Going to set this true in a minute...
ilcx.on('change', function(ev){
debug("Interlock for device " + vdevPrimary.metrics.title + " changed from " + ev.oldValue + " to " + ev.newValue + "!");
}.bind(this));

this.interlock = ilcx;
services.push(ilsvc);

ilcx.setValue(true); // Initializes the interlock as on
}

// Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services...
if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){
var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]];
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "homebridge-zway",
"version": "0.5.0-alpha0",
"version": "0.5.0-alpha1",
"description": "homebridge-plugin for ZWay Server and RaZBerry",
"main": "index.js",
"scripts": {
Expand Down

0 comments on commit 5c1bf5d

Please sign in to comment.