Skip to content

Commit 5c1bf5d

Browse files
committed
Merge pull request #23 from SphtKr/feature-interlock
Feature interlock--see README.md changes.
2 parents 49c2850 + 50197ca commit 5c1bf5d

File tree

3 files changed

+54
-18
lines changed

3 files changed

+54
-18
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,17 @@ Like [`Homebridge.Service.Type`](#homebridgeservicetypevalue), this allows you t
169169

170170
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.)
171171

172+
#### Homebridge.Interlock
173+
174+
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.**
175+
172176
#### Homebridge.ContactSensorState.Invert
173177

174178
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).
175179

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

178-
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.
182+
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.
179183

180184
# Technical Detail
181185

index.js

+48-16
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,12 @@ ZWayServerAccessory.prototype = {
379379
url: this.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id + '/command/' + command,
380380
qs: (value === undefined ? undefined : value)
381381
});
382-
},
383-
382+
}
383+
,
384+
isInterlockOn: function(){
385+
return !!this.interlock.value;
386+
}
387+
,
384388
rgb2hsv: function(obj) {
385389
// RGB: 0-255; H: 0-360, S,V: 0-100
386390
var r = obj.r/255, g = obj.g/255, b = obj.b/255;
@@ -594,6 +598,16 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
594598
this.platform.cxVDevMap[vdev.id].push(cx);
595599
if(!this.platform.vDevStore[vdev.id]) this.platform.vDevStore[vdev.id] = vdev;
596600

601+
var interlock = function(fnDownstream){
602+
return function(newval, callback){
603+
if(this.isInterlockOn()){
604+
callback(new Error("Interlock is on! Changes locked out!"));
605+
} else {
606+
fnDownstream(newval, callback);
607+
}
608+
}.bind(accessory);
609+
};
610+
597611
if(cx instanceof Characteristic.Name){
598612
cx.zway_getValueFromVDev = function(vdev){
599613
return vdev.metrics.title;
@@ -632,11 +646,11 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
632646
callback(false, cx.zway_getValueFromVDev(result.data));
633647
});
634648
}.bind(this));
635-
cx.on('set', function(powerOn, callback){
649+
cx.on('set', interlock(function(powerOn, callback){
636650
this.command(vdev, powerOn ? "on" : "off").then(function(result){
637651
callback();
638652
});
639-
}.bind(this));
653+
}.bind(this)));
640654
cx.on('change', function(ev){
641655
debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue);
642656
});
@@ -688,11 +702,11 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
688702
callback(false, cx.zway_getValueFromVDev(result.data));
689703
});
690704
}.bind(this));
691-
cx.on('set', function(level, callback){
705+
cx.on('set', interlock(function(level, callback){
692706
this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){
693707
callback();
694708
});
695-
}.bind(this));
709+
}.bind(this)));
696710
return cx;
697711
}
698712

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

725739
return cx;
726740
}
@@ -738,7 +752,7 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
738752
callback(false, cx.zway_getValueFromVDev(result.data));
739753
});
740754
}.bind(this));
741-
cx.on('set', function(saturation, callback){
755+
cx.on('set', interlock(function(saturation, callback){
742756
var hcx = service.getCharacteristic(Characteristic.Hue);
743757
var vcx = service.getCharacteristic(Characteristic.Brightness);
744758
if(!hcx || !vcx){
@@ -749,7 +763,7 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
749763
this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){
750764
callback();
751765
});
752-
}.bind(this));
766+
}.bind(this)));
753767

754768
return cx;
755769
}
@@ -804,12 +818,12 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
804818
callback(false, cx.zway_getValueFromVDev(result.data));
805819
});
806820
}.bind(this));
807-
cx.on('set', function(level, callback){
821+
cx.on('set', interlock(function(level, callback){
808822
this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){
809823
//debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + ".");
810824
callback();
811825
});
812-
}.bind(this));
826+
}.bind(this)));
813827
cx.setProps({
814828
minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5,
815829
maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40
@@ -857,10 +871,10 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
857871
callback(false, Characteristic.TargetHeatingCoolingState.HEAT);
858872
});
859873
// 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.
860-
cx.on('set', function(newValue, callback){
874+
cx.on('set', interlock(function(newValue, callback){
861875
debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!")
862876
callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT);
863-
}.bind(this));
877+
}.bind(this)));
864878
return cx;
865879
}
866880

@@ -1068,12 +1082,12 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
10681082
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
10691083
callback(false, cx.zway_getValueFromVDev(vdev));
10701084
});
1071-
cx.on('set', function(level, callback){
1085+
cx.on('set', interlock(function(level, callback){
10721086
this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){
10731087
//debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + ".");
10741088
callback(false, cx.zway_getValueFromVDev(result.data));
10751089
});
1076-
}.bind(this));
1090+
}.bind(this)));
10771091
cx.setProps({
10781092
minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 0,
10791093
maxValue: vdev.metrics && (vdev.metrics.max !== undefined || vdev.metrics.max != 99) ? vdev.metrics.max : 100
@@ -1245,12 +1259,30 @@ if(!vdev) debug("ERROR: vdev passed to getVDevServices is undefined!");
12451259
.setCharacteristic(Characteristic.SerialNumber, accId);
12461260

12471261
var services = [informationService];
1262+
12481263
services = services.concat(this.getVDevServices(vdevPrimary));
12491264
if(services.length === 1){
12501265
debug("WARN: Only the InformationService was successfully configured for " + vdevPrimary.id + "! No device services available!");
12511266
return services;
12521267
}
12531268

1269+
// Interlock specified? Create an interlock control switch...
1270+
if(this.platform.getTagValue(vdevPrimary, "Interlock") && services.length > 1){
1271+
var ilsvc = new Service.Switch("Interlock", vdevPrimary.id + "_interlock");
1272+
ilsvc.setCharacteristic(Characteristic.Name, "Interlock");
1273+
1274+
var ilcx = ilsvc.getCharacteristic(Characteristic.On);
1275+
ilcx.value = false; // Going to set this true in a minute...
1276+
ilcx.on('change', function(ev){
1277+
debug("Interlock for device " + vdevPrimary.metrics.title + " changed from " + ev.oldValue + " to " + ev.newValue + "!");
1278+
}.bind(this));
1279+
1280+
this.interlock = ilcx;
1281+
services.push(ilsvc);
1282+
1283+
ilcx.setValue(true); // Initializes the interlock as on
1284+
}
1285+
12541286
// Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services...
12551287
if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){
12561288
var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]];

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "homebridge-zway",
3-
"version": "0.5.0-alpha0",
3+
"version": "0.5.0-alpha1",
44
"description": "homebridge-plugin for ZWay Server and RaZBerry",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)