mirror of
https://github.com/mtan93/homebridge.git
synced 2026-03-08 05:31:55 +00:00
Refactored for new API.
Mostly working, but door sensors need further work, battery service still isn't right, and I'm losing the bridge periodically...merging from master to see if I need some bugfixes.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
var Service = require("HAP-NodeJS").Service;
|
||||
var Characteristic = require("HAP-NodeJS").Characteristic;
|
||||
var types = require("HAP-NodeJS/accessories/types.js");
|
||||
var request = require("request");
|
||||
var tough = require('tough-cookie');
|
||||
@@ -79,6 +81,7 @@ ZWayServerPlatform.getVDevServiceTypes = function(vdev){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
ZWayServerPlatform.getVDevCharacteristicsTypes = function(vdev){
|
||||
var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev);
|
||||
switch (typeKey) {
|
||||
@@ -96,6 +99,7 @@ ZWayServerPlatform.getVDevCharacteristicsTypes = function(vdev){
|
||||
return [types.BATTERY_LEVEL_CTYPE, types.STATUS_LOW_BATTERY_CTYPE];
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
ZWayServerPlatform.prototype = {
|
||||
|
||||
@@ -108,7 +112,7 @@ ZWayServerPlatform.prototype = {
|
||||
opts.headers = {
|
||||
"Cookie": "ZWAYSession=" + this.sessionId
|
||||
};
|
||||
//opts.proxy = 'http://localhost:8888';
|
||||
opts.proxy = 'http://localhost:8888';
|
||||
|
||||
request(opts, function(error, response, body){
|
||||
if(response.statusCode == 401){
|
||||
@@ -116,7 +120,7 @@ ZWayServerPlatform.prototype = {
|
||||
request({
|
||||
method: "POST",
|
||||
url: that.url + 'ZAutomation/api/v1/login',
|
||||
//proxy: 'http://localhost:8888',
|
||||
proxy: 'http://localhost:8888',
|
||||
body: { //JSON.stringify({
|
||||
"form": true,
|
||||
"login": that.login,
|
||||
@@ -235,6 +239,7 @@ ZWayServerAccessory.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
informationCharacteristics: function() {
|
||||
return [
|
||||
{
|
||||
@@ -272,7 +277,7 @@ ZWayServerAccessory.prototype = {
|
||||
onUpdate: null,
|
||||
perms: ["pr"],
|
||||
format: "string",
|
||||
initialValue: "VDev-" + this.devDesc.devices[this.devDesc.primary].h, //TODO: Is this valid?
|
||||
initialValue: "",
|
||||
supportEvents: false,
|
||||
supportBonjour: false,
|
||||
manfDescription: "SN",
|
||||
@@ -409,14 +414,14 @@ ZWayServerAccessory.prototype = {
|
||||
cTypes.push({
|
||||
cType: types.CURRENTHEATINGCOOLING_CTYPE,
|
||||
//TODO: Support multifunction thermostats...only heating supported now.
|
||||
/*
|
||||
/ *
|
||||
onUpdate: null,
|
||||
onRead: function(callback) {
|
||||
that.getCurrentHeatingCooling(function(currentHeatingCooling){
|
||||
callback(currentHeatingCooling);
|
||||
});
|
||||
},
|
||||
*/
|
||||
* /
|
||||
perms: ["pr"],
|
||||
format: "int",
|
||||
initialValue: 1,
|
||||
@@ -434,7 +439,7 @@ ZWayServerAccessory.prototype = {
|
||||
cTypes.push({
|
||||
cType: types.TARGETHEATINGCOOLING_CTYPE,
|
||||
//TODO: Support multifunction thermostats...only heating supported now.
|
||||
/*
|
||||
/ *
|
||||
onUpdate: function(value) {
|
||||
that.setTargetHeatingCooling(value);
|
||||
},
|
||||
@@ -443,7 +448,7 @@ ZWayServerAccessory.prototype = {
|
||||
callback(targetHeatingCooling);
|
||||
});
|
||||
},
|
||||
*/
|
||||
* /
|
||||
perms: ["pr"],
|
||||
format: "int",
|
||||
initialValue: 0,
|
||||
@@ -550,7 +555,7 @@ ZWayServerAccessory.prototype = {
|
||||
});
|
||||
},
|
||||
perms: ["pr","ev"],
|
||||
format: "int",
|
||||
format: "uint8",
|
||||
initialValue: 100,
|
||||
supportEvents: true,
|
||||
supportBonjour: false,
|
||||
@@ -575,7 +580,7 @@ ZWayServerAccessory.prototype = {
|
||||
});
|
||||
},
|
||||
perms: ["pr","ev"],
|
||||
format: "bool",
|
||||
format: "uint8",
|
||||
initialValue: 0,
|
||||
supportEvents: false,
|
||||
supportBonjour: false,
|
||||
@@ -586,75 +591,317 @@ ZWayServerAccessory.prototype = {
|
||||
|
||||
return cTypes;
|
||||
},
|
||||
*/
|
||||
|
||||
getVDevServices: function(vdev){
|
||||
var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev);
|
||||
var services = [], service;
|
||||
switch (typeKey) {
|
||||
case "switchBinary":
|
||||
services.push(new Service.Switch(vdev.metrics.title));
|
||||
break;
|
||||
case "switchMultilevel":
|
||||
services.push(new Service.Lightbulb(vdev.metrics.title));
|
||||
break;
|
||||
case "thermostat":
|
||||
services.push(new Service.Thermostat(vdev.metrics.title));
|
||||
break;
|
||||
case "sensorMultilevel.Temperature":
|
||||
services.push(new Service.TemperatureSensor(vdev.metrics.title));
|
||||
break;
|
||||
case "sensorBinary.Door/Window":
|
||||
services.push(new Service.Door(vdev.metrics.title));
|
||||
break;
|
||||
case "battery.Battery":
|
||||
services.push(new Service.BatteryService(vdev.metrics.title));
|
||||
break;
|
||||
}
|
||||
|
||||
var validServices =[];
|
||||
for(var i = 0; i < services.length; i++){
|
||||
if(this.configureService(services[i], vdev))
|
||||
validServices.push(services[i]);
|
||||
}
|
||||
|
||||
return validServices;
|
||||
}
|
||||
,
|
||||
uuidToTypeKeyMap: null
|
||||
,
|
||||
getVDevForCharacteristic: function(cx, vdevPreferred){
|
||||
var map = this.uuidToTypeKeyMap;
|
||||
if(!map){
|
||||
this.uuidToTypeKeyMap = map = {};
|
||||
map[(new Characteristic.On).UUID] = ["switchBinary","switchMultilevel"];
|
||||
map[(new Characteristic.Brightness).UUID] = ["switchMultilevel"];
|
||||
map[(new Characteristic.CurrentTemperature).UUID] = ["sensorMultilevel.Temperature","thermostat"];
|
||||
map[(new Characteristic.TargetTemperature).UUID] = ["thermostat"];
|
||||
map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result
|
||||
map[(new Characteristic.CurrentHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result
|
||||
map[(new Characteristic.TargetHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result
|
||||
map[(new Characteristic.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"];
|
||||
map[(new Characteristic.TargetDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result
|
||||
map[(new Characteristic.ObstructionDetected).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result
|
||||
map[(new Characteristic.BatteryLevel).UUID] = ["battery.Battery"];
|
||||
map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"];
|
||||
map[(new Characteristic.ChargingState).UUID] = ["battery.Battery"]; //TODO: Always a fixed result
|
||||
}
|
||||
|
||||
var typekeys = map[cx.UUID];
|
||||
if(typekeys === undefined) return null;
|
||||
|
||||
if(vdevPreferred && typekeys.indexOf(ZWayServerPlatform.getVDevTypeKey(vdevPreferred)) >= 0){
|
||||
return vdevPreferred;
|
||||
}
|
||||
|
||||
var candidates = this.devDesc.devices;
|
||||
for(var i = 0; i < typekeys.length; i++){
|
||||
for(var j = 0; j < candidates.length; j++){
|
||||
if(ZWayServerPlatform.getVDevTypeKey(candidates[j]) === typekeys[i]) return candidates[j];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
,
|
||||
configureCharacteristic: function(cx, vdev){
|
||||
var that = this;
|
||||
|
||||
var gdv = function(){
|
||||
that.log("Default value for " + vdev.metrics.title + " is " + vdev.metrics.level);
|
||||
return vdev.metrics.level;
|
||||
};
|
||||
|
||||
if(cx instanceof Characteristic.On){
|
||||
cx.getDefaultValue = gdv;
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
that.platform.zwayRequest({
|
||||
method: "GET",
|
||||
url: that.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id
|
||||
}).then(function(result){
|
||||
that.log("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + ".");
|
||||
var val;
|
||||
if(result.data.metrics.level === "off"){
|
||||
val = false;
|
||||
} else if(val <= 5) {
|
||||
val = false;
|
||||
} else if (val > 5) {
|
||||
val = true;
|
||||
}
|
||||
callback(false, val);
|
||||
});
|
||||
}.bind(this));
|
||||
cx.on('set', function(powerOn, callback){
|
||||
this.command(vdev, powerOn ? "on" : "off").then(function(result){
|
||||
callback();
|
||||
});
|
||||
}.bind(this));
|
||||
return cx;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.Brightness){
|
||||
cx.getDefaultValue = gdv;
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
that.platform.zwayRequest({
|
||||
method: "GET",
|
||||
url: that.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id
|
||||
}).then(function(result){
|
||||
that.log("Got value " + result.data.metrics.level + " for " + vdev.metrics.title + ".");
|
||||
callback(false, result.data.metrics.level);
|
||||
});
|
||||
}.bind(this));
|
||||
cx.on('set', function(level, callback){
|
||||
this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){
|
||||
callback();
|
||||
});
|
||||
}.bind(this));
|
||||
return cx;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.CurrentTemperature){
|
||||
cx.getDefaultValue = gdv;
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
that.platform.zwayRequest({
|
||||
method: "GET",
|
||||
url: that.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id
|
||||
}).then(function(result){
|
||||
callback(false, result.data.metrics.level);
|
||||
});
|
||||
}.bind(this));
|
||||
cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40;
|
||||
cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999;
|
||||
return cx;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.TargetTemperature){
|
||||
cx.getDefaultValue = gdv;
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
this.platform.zwayRequest({
|
||||
method: "GET",
|
||||
url: that.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id
|
||||
}).then(function(result){
|
||||
callback(false, result.data.metrics.level);
|
||||
});
|
||||
}.bind(this));
|
||||
cx.on('set', function(level, callback){
|
||||
this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){
|
||||
callback();
|
||||
});
|
||||
}.bind(this));
|
||||
cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5;
|
||||
cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40;
|
||||
return cx;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.TemperatureDisplayUnits){
|
||||
//TODO: Always in °C for now.
|
||||
cx.getDefaultValue = function(){ return Characteristic.TemperatureDisplayUnits.CELCIUS; };
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
callback(false, Characteristic.TemperatureDisplayUnits.CELCIUS);
|
||||
});
|
||||
cx.writable = false;
|
||||
return cx;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.CurrentHeatingCoolingState){
|
||||
//TODO: Always HEAT for now, we don't have an example to work with that supports another function.
|
||||
cx.getDefaultValue = function(){ return Characteristic.CurrentHeatingCoolingState.HEAT; };
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
callback(false, Characteristic.CurrentHeatingCoolingState.HEAT);
|
||||
});
|
||||
return cx;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.TargetHeatingCoolingState){
|
||||
//TODO: Always HEAT for now, we don't have an example to work with that supports another function.
|
||||
cx.getDefaultValue = function(){ return Characteristic.TargetHeatingCoolingState.HEAT; };
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
callback(false, Characteristic.TargetHeatingCoolingState.HEAT);
|
||||
});
|
||||
cx.writable = false;
|
||||
return cx;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.CurrentDoorState){
|
||||
cx.getDefaultValue = function(){
|
||||
return vdev.metrics.level == "off" ? Characteristic.CurrentDoorState.CLOSED : Characteristic.CurrentDoorState.OPEN;
|
||||
};
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
this.platform.zwayRequest({
|
||||
method: "GET",
|
||||
url: that.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id
|
||||
}).then(function(result){
|
||||
callback(false, result.data.metrics.level == "off" ? Characteristic.CurrentDoorState.CLOSED : Characteristic.CurrentDoorState.OPEN);
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.TargetDoorState){
|
||||
//TODO: We only support this for Door sensors now, so it's a fixed value.
|
||||
cx.getDefaultValue = function(){ return Characteristic.TargetDoorState.CLOSED; };
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
callback(false, Characteristic.TargetDoorState.CLOSED);
|
||||
});
|
||||
//cx.readable = false;
|
||||
cx.writable = false;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.ObstructionDetected){
|
||||
//TODO: We only support this for Door sensors now, so it's a fixed value.
|
||||
cx.getDefaultValue = function(){ return false; };
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
callback(false, false);
|
||||
});
|
||||
//cx.readable = false;
|
||||
cx.writable = false;
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.BatteryLevel){
|
||||
cx.getDefaultValue = gdv;
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
that.platform.zwayRequest({
|
||||
method: "GET",
|
||||
url: that.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id
|
||||
}).then(function(result){
|
||||
callback(false, result.data.metrics.level);
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.StatusLowBattery){
|
||||
cx.getDefaultValue = function(){ return Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; };
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
that.platform.zwayRequest({
|
||||
method: "GET",
|
||||
url: that.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id
|
||||
}).then(function(result){
|
||||
callback(false, result.data.metrics.level <= that.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
if(cx instanceof Characteristic.ChargingState){
|
||||
//TODO: No known chargeable devices(?), so always return false.
|
||||
cx.getDefaultValue = function(){ return Characteristic.ChargingState.NOT_CHARGING; };
|
||||
cx.on('get', function(callback, context){
|
||||
that.log("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
|
||||
callback(false, Characteristic.ChargingState.NOT_CHARGING);
|
||||
});
|
||||
//cx.readable = false;
|
||||
cx.writable = false;
|
||||
}
|
||||
|
||||
}
|
||||
,
|
||||
configureService: function(service, vdev){
|
||||
var success = true;
|
||||
for(var i = 0; i < service.characteristics.length; i++){
|
||||
var cx = service.characteristics[i];
|
||||
var vdev = this.getVDevForCharacteristic(cx, vdev);
|
||||
if(!vdev){
|
||||
success = false;
|
||||
this.log("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!");
|
||||
}
|
||||
cx = this.configureCharacteristic(cx, vdev);
|
||||
}
|
||||
for(var i = 0; i < service.optionalCharacteristics.length; i++){
|
||||
var cx = service.optionalCharacteristics[i];
|
||||
var vdev = this.getVDevForCharacteristic(cx);
|
||||
if(!vdev) continue;
|
||||
cx = this.configureCharacteristic(cx, vdev);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
,
|
||||
getServices: function() {
|
||||
var that = this;
|
||||
var services = [{
|
||||
sType: types.ACCESSORY_INFORMATION_STYPE,
|
||||
characteristics: this.informationCharacteristics(),
|
||||
}];
|
||||
|
||||
var informationService = new Service.AccessoryInformation();
|
||||
|
||||
// rearrange the array so the primary is first
|
||||
var vdevs = this.devDesc.devices.concat();
|
||||
var p = vdevs.splice(this.devDesc.primary, 1)[0];
|
||||
vdevs.unshift(p);
|
||||
/*
|
||||
for(var i = 0; i < vdevs.length; i++){
|
||||
var sTypes = ZWayServerPlatform.getVDevServiceTypes(vdevs[i]);
|
||||
if(!sTypes) continue;
|
||||
for(var j = 0; j < sTypes.length; j++){
|
||||
services.push({
|
||||
sType: sTypes[j],
|
||||
characteristics: this.controlCharacteristics(vdevs[i])
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
var sTypes = ZWayServerPlatform.getVDevServiceTypes(vdevs[0]);
|
||||
var cTypes = [{
|
||||
cType: types.NAME_CTYPE,
|
||||
onUpdate: null,
|
||||
perms: ["pr"],
|
||||
format: "string",
|
||||
initialValue: this.name,
|
||||
supportEvents: true,
|
||||
supportBonjour: false,
|
||||
manfDescription: "Name of service",
|
||||
designedMaxLength: 255
|
||||
}];
|
||||
if(sTypes) for(var i = 0; i < vdevs.length; i++){
|
||||
cTypes = cTypes.concat(this.controlCharacteristics(vdevs[i]));
|
||||
}
|
||||
|
||||
// Scrub/eliminate duplicate cTypes? This is a lot of guesswork ATM...
|
||||
var hits = {};
|
||||
for (var i = 0; i < cTypes.length; i++){
|
||||
if(hits[cTypes[i].cType]) cTypes.splice(i--, 1); // Remember postfix means post-evaluate!
|
||||
else hits[cTypes[i].cType] = cTypes[i];
|
||||
}
|
||||
|
||||
// Thermostats MUST include current temperature...so, for the Danfoss/Devolo radiator
|
||||
// thermostats, we have to fake one...
|
||||
if (hits[types.TARGET_TEMPERATURE_CTYPE] && !hits[types.CURRENT_TEMPERATURE_CTYPE]) {
|
||||
// Copy the "target" device to the "current" one, with necessary tweaks...
|
||||
var tcx = hits[types.TARGET_TEMPERATURE_CTYPE];
|
||||
var ccx = {};
|
||||
for(var p in tcx){
|
||||
if(tcx.hasOwnProperty(p)) ccx[p] = tcx[p];
|
||||
}
|
||||
ccx.cType = types.CURRENT_TEMPERATURE_CTYPE;
|
||||
ccx.onUpdate = null;
|
||||
ccx.perms = ["pr"];
|
||||
//ccx.onRead = null; // Override this??
|
||||
cTypes.push(ccx);
|
||||
}
|
||||
informationService
|
||||
.setCharacteristic(Characteristic.Name, this.name)
|
||||
.setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me")
|
||||
.setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)")
|
||||
.setCharacteristic(Characteristic.SerialNumber, "VDev-" + this.devDesc.devices[this.devDesc.primary].h) //FIXME: Is this valid?);
|
||||
|
||||
services.push({
|
||||
sType: sTypes[0],
|
||||
characteristics: cTypes
|
||||
});
|
||||
//...
|
||||
var services = [informationService];
|
||||
|
||||
services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.primary]));
|
||||
|
||||
if(this.devDesc.types["battery.Battery"])
|
||||
services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.types["battery.Battery"]]));
|
||||
|
||||
this.log("Loaded services for " + this.name);
|
||||
return services;
|
||||
|
||||
Reference in New Issue
Block a user