Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Andre Schröter
2015-10-19 21:16:35 +02:00
47 changed files with 4103 additions and 549 deletions
+7 -2
View File
@@ -52,13 +52,18 @@ You'll also need some patience, as Siri can be very strict about sentence struct
# Getting Started # Getting Started
OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. First, clone this repo: OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up.
**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed.
First, clone this repo:
$ git clone https://github.com/nfarina/homebridge.git $ git clone https://github.com/nfarina/homebridge.git
$ cd homebridge $ cd homebridge
$ npm install $ npm install
**Node**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load. **Note**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load.
Now you should be able to run the homebridge server: Now you should be able to run the homebridge server:
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var AD2USB = require('ad2usb'); var AD2USB = require('ad2usb');
var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8"; var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8";
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var carwings = require("carwingsjs"); var carwings = require("carwingsjs");
function CarwingsAccessory(log, config) { function CarwingsAccessory(log, config) {
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var elkington = require("elkington"); var elkington = require("elkington");
function ElkM1Accessory(log, config) { function ElkM1Accessory(log, config) {
+2 -2
View File
@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var chokidar = require("chokidar"); var chokidar = require("chokidar");
var debug = require("debug")("FileSensorAccessory"); var debug = require("debug")("FileSensorAccessory");
var crypto = require("crypto"); var crypto = require("crypto");
+2 -2
View File
@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var SerialPort = require("serialport").SerialPort; var SerialPort = require("serialport").SerialPort;
module.exports = { module.exports = {
+26 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var request = require("request"); var request = require("request");
function HomeMatic(log, config) { function HomeMatic(log, config) {
@@ -30,7 +30,31 @@ HomeMatic.prototype = {
} }
}); });
}, },
getPowerState: function(callback) {
var that = this;
this.log("Getting Power State of CCU");
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseString = response.body.substring(83,87);
//that.log(responseString);
switch(responseString){
case "true": {modvalue = "1";break;}
case "fals": {modvalue = "0";break;}
}
callback(parseInt(modvalue));
that.log("Getting Power State complete.");
}
else {
that.log("Error '"+err+"' getting Power State: " + body);
}
});
},
getServices: function() { getServices: function() {
var that = this; var that = this;
return [{ return [{
@@ -101,6 +125,7 @@ HomeMatic.prototype = {
},{ },{
cType: types.POWER_STATE_CTYPE, cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPowerState(value); }, onUpdate: function(value) { that.setPowerState(value); },
onRead: function(callback) { that.getPowerState(callback); },
perms: ["pw","pr","ev"], perms: ["pw","pr","ev"],
format: "bool", format: "bool",
initialValue: false, initialValue: false,
+264
View File
@@ -0,0 +1,264 @@
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function HomeMaticThermo(log, config) {
this.log = log;
this.name = config["name"];
this.ccuIDTargetTemp = config["ccu_id_TargetTemp"];
this.ccuIDCurrentTemp = config["ccu_id_CurrentTemp"];
this.ccuIDControlMode = config["ccu_id_ControlMode"];
this.ccuIDManuMode = config["ccu_id_ManuMode"];
this.ccuIDAutoMode = config["ccu_id_AutoMode"];
this.ccuIP = config["ccu_ip"];
}
HomeMaticThermo.prototype = {
setTargetTemperature: function(value) {
var that = this;
this.log("Setting target Temperature of CCU to " + value);
this.log(this.ccuIDTargetTemp + " " + value);
request.put({
url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuIDTargetTemp+"&new_value="+ value,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
that.log("State change complete.");
}
else {
that.log("Error '"+err+"' setting Temperature: " + body);
}
});
},
getCurrentTemperature: function(callback) {
var that = this;
this.log("Getting current Temperature of CCU");
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDCurrentTemp,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseString = response.body.substring(83,87);
//that.log(responseString);
callback(parseFloat(responseString));
//that.log("Getting current temperature complete.");
}
else {
that.log("Error '"+err+"' getting Temperature: " + body);
}
});
},
getTargetTemperature: function(callback) {
var that = this;
this.log("Getting target Temperature of CCU");
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDTargetTemp,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseString = response.body.substring(83,87);
//that.log(responseString);
callback(parseFloat(responseString));
//that.log("Getting target temperature complete.");
}
else {
that.log("Error '"+err+"' getting Temperature: " + body);
}
});
},
getMode: function(callback) {
var that = this;
//this.log("Getting target Mode of CCU");
//this.log(this.ccuID+ value);
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDControlMode,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseInt = response.body.substring(83,84);
//that.log(responseString);
if (responseInt == 1)
{ callback(parseInt("0")); }
if (responseInt == 0)
{ callback(parseInt("1")); }
//that.log("Getting mode complete.");
}
else {
that.log("Error '"+err+"' getting Mode: " + body);
}
});
},
setMode: function(value) {
var that = this;
//this.log("Seting target Mode of CCU:" + value);
var modvalue;
var dpID;
switch(value) {
case 3: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //auto
case 1: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //heating => auto
default: {modvalue = "1";dpID=this.ccuIDManuMode;} //default => off (manual)
}
request.put({
url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+dpID+"&new_value="+ modvalue,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Setting Mode complete.");
}
else {
that.log("Error '"+err+"' setting Mode: " + body);
}
});
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "test",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "test",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NREF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.THERMOSTAT_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.CURRENTHEATINGCOOLING_CTYPE,
onRead: function(callback) { that.getMode(callback); },
perms: ["pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Mode",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 2,
designedMinStep: 1,
},{
cType: types.TARGETHEATINGCOOLING_CTYPE,
onRead: function(callback) { that.getMode(callback); },
onUpdate: function(value) { that.setMode(value);},
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Mode",
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
},{
cType: types.CURRENT_TEMPERATURE_CTYPE,
onRead: function(callback) { that.getCurrentTemperature(callback); },
onUpdate: null,
perms: ["pr","ev"],
format: "float",
initialValue: 13.0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Temperature",
unit: "celsius"
},{
cType: types.TARGET_TEMPERATURE_CTYPE,
onUpdate: function(value) { that.setTargetTemperature(value); },
onRead: function(callback) { that.getTargetTemperature(callback); },
perms: ["pw","pr","ev"],
format: "float",
initialValue: 19.0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Temperature",
designedMinValue: 4,
designedMaxValue: 25,
designedMinStep: 0.1,
unit: "celsius"
},{
cType: types.TEMPERATURE_UNITS_CTYPE,
onUpdate: null,
perms: ["pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Unit"
}]
}];
}
};
module.exports.accessory = HomeMaticThermo;
+123
View File
@@ -0,0 +1,123 @@
var types = require("hap-nodejs/accessories/types.js");
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
function HomeMaticWindow(log, config) {
this.log = log;
this.name = config["name"];
this.ccuID = config["ccu_id"];
this.ccuIP = config["ccu_ip"];
}
HomeMaticWindow.prototype = {
getPowerState: function(callback) {
var that = this;
this.log("Getting Window State of CCU");
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseString = response.body.substring(83,84);
//that.log(responseString);
switch(responseString){
case "0": {callback(Characteristic.ContactSensorState.CONTACT_DETECTED);break;}
case "1": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;}
case "2": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;}
}
that.log("Getting Window State complete.");
}
else {
that.log("Error '"+err+"' getting Window State: " + body);
}
});
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Homematic",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "HM-Sec-RHS",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.CONTACT_SENSOR_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.CONTACT_SENSOR_STATE_CTYPE,
onRead: function(callback) { that.getPowerState(callback); },
perms: ["pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Get Window state of a Variable",
designedMaxLength: 1
}]
}];
}
};
module.exports.accessory = HomeMaticWindow;
+2 -2
View File
@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request"); var request = require("request");
module.exports = { module.exports = {
+71
View File
@@ -0,0 +1,71 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {
accessory: HygrometerAccessory
}
function HygrometerAccessory(log, config) {
this.log = log;
// url info
this.url = config["url"];
this.http_method = config["http_method"];
}
HygrometerAccessory.prototype = {
httpRequest: function(url, method, callback) {
request({
url: url,
method: method
},
function (error, response, body) {
callback(error, response, body)
})
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
getCurrentRelativeHumidity: function (callback) {
var that = this;
that.log ("getting CurrentCurrentRelativeHumidity");
this.httpRequest(this.url, this.http_method, function(error, response, body) {
if (error) {
this.log('HTTP function failed: %s', error);
callback(error);
}
else {
this.log('HTTP function succeeded - %s', body);
callback(null, Number(body));
}
}.bind(this));
},
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer")
.setCharacteristic(Characteristic.Model, "HTTP Hygrometer")
.setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number");
var humidityService = new Service.HumiditySensor();
humidityService
.getCharacteristic(Characteristic.CurrentRelativeHumidity)
.on('get', this.getCurrentRelativeHumidity.bind(this));
return [informationService, humidityService];
}
};
+79
View File
@@ -0,0 +1,79 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {
accessory: ThermometerAccessory
}
function ThermometerAccessory(log, config) {
this.log = log;
// url info
this.url = config["url"];
this.http_method = config["http_method"];
}
ThermometerAccessory.prototype = {
httpRequest: function(url, method, callback) {
request({
url: url,
method: method
},
function (error, response, body) {
callback(error, response, body)
})
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
getCurrentTemperature: function (callback) {
var that = this;
that.log ("getting CurrentTemperature");
this.httpRequest(this.url, this.http_method, function(error, response, body) {
if (error) {
this.log('HTTP function failed: %s', error);
callback(error);
}
else {
this.log('HTTP function succeeded - %s', body);
callback(null, Number(body));
}
}.bind(this));
},
getTemperatureUnits: function (callback) {
var that = this;
that.log ("getTemperature Units");
// 1 = F and 0 = C
callback (null, 0);
},
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer")
.setCharacteristic(Characteristic.Model, "HTTP Thermometer")
.setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number");
var temperatureService = new Service.TemperatureSensor();
temperatureService
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.getCurrentTemperature.bind(this));
return [informationService, temperatureService];
}
};
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var net = require('net'); var net = require('net');
var Color = require('color'); var Color = require('color');
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var request = require("request"); var request = require("request");
// This seems to be the "id" of the official LiftMaster iOS app // This seems to be the "id" of the official LiftMaster iOS app
+23 -16
View File
@@ -1,5 +1,5 @@
var Service = require('HAP-NodeJS').Service; var Service = require("hap-nodejs").Service;
var Characteristic = require('HAP-NodeJS').Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request"); var request = require("request");
module.exports = { module.exports = {
@@ -11,6 +11,17 @@ function LockitronAccessory(log, config) {
this.name = config["name"]; this.name = config["name"];
this.accessToken = config["api_token"]; this.accessToken = config["api_token"];
this.lockID = config["lock_id"]; this.lockID = config["lock_id"];
this.service = new Service.LockMechanism(this.name);
this.service
.getCharacteristic(Characteristic.LockCurrentState)
.on('get', this.getState.bind(this));
this.service
.getCharacteristic(Characteristic.LockTargetState)
.on('get', this.getState.bind(this))
.on('set', this.setState.bind(this));
} }
LockitronAccessory.prototype.getState = function(callback) { LockitronAccessory.prototype.getState = function(callback) {
@@ -36,7 +47,7 @@ LockitronAccessory.prototype.getState = function(callback) {
} }
LockitronAccessory.prototype.setState = function(state, callback) { LockitronAccessory.prototype.setState = function(state, callback) {
var lockitronState = (state == 1) ? "lock" : "unlock"; var lockitronState = (state == Characteristic.LockTargetState.SECURED) ? "lock" : "unlock";
this.log("Set state to %s", lockitronState); this.log("Set state to %s", lockitronState);
@@ -47,6 +58,14 @@ LockitronAccessory.prototype.setState = function(state, callback) {
if (!err && response.statusCode == 200) { if (!err && response.statusCode == 200) {
this.log("State change complete."); this.log("State change complete.");
// we succeeded, so update the "current" state as well
var currentState = (state == Characteristic.LockTargetState.SECURED) ?
Characteristic.LockCurrentState.SECURED : Characteristic.LockCurrentState.UNSECURED;
this.service
.setCharacteristic(Characteristic.LockCurrentState, currentState);
callback(null); // success callback(null); // success
} }
else { else {
@@ -57,17 +76,5 @@ LockitronAccessory.prototype.setState = function(state, callback) {
}, },
LockitronAccessory.prototype.getServices = function() { LockitronAccessory.prototype.getServices = function() {
return [this.service];
var service = new Service.LockMechanism(this.name);
service
.getCharacteristic(Characteristic.LockCurrentState)
.on('get', this.getState.bind(this));
service
.getCharacteristic(Characteristic.LockTargetState)
.on('get', this.getState.bind(this))
.on('set', this.setState.bind(this));
return [service];
} }
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var tesla = require("teslams"); var tesla = require("teslams");
function TeslaAccessory(log, config) { function TeslaAccessory(log, config) {
+12 -2
View File
@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var wemo = require('wemo'); var wemo = require('wemo');
module.exports = { module.exports = {
@@ -144,6 +144,16 @@ WeMoAccessory.prototype.getServices = function() {
return [garageDoorService]; return [garageDoorService];
} }
else if (this.service == "Light") {
var lightbulbService = new Service.Lightbulb(this.name);
lightbulbService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerOn.bind(this))
.on('set', this.setPowerOn.bind(this));
return [lightbulbService];
}
else if (this.service == "MotionSensor") { else if (this.service == "MotionSensor") {
var motionSensorService = new Service.MotionSensor(this.name); var motionSensorService = new Service.MotionSensor(this.name);
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var request = require("request"); var request = require("request");
function X10(log, config) { function X10(log, config) {
+2 -2
View File
@@ -1,6 +1,6 @@
var iControl = require('node-icontrol').iControl; var iControl = require('node-icontrol').iControl;
var Service = require('HAP-NodeJS').Service; var Service = require("hap-nodejs").Service;
var Characteristic = require('HAP-NodeJS').Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
module.exports = { module.exports = {
accessory: iControlAccessory accessory: iControlAccessory
+193 -137
View File
@@ -14,16 +14,23 @@ New 2015-09-18:
New 2015-09-19: New 2015-09-19:
- GarageDoorOpener Service - GarageDoorOpener Service
- MotionSensor Service - MotionSensor Service
New 2015-10-02:
- Check for valid group addresses
- new "R" flag allowed for Boolean addresses: 1/2/3R is the boolean not(1/2/3), i.e. 0 and 1 switched on read and write
New 2015-10-07:
- Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional)
* *
*/ */
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var knxd = require("eibd"); var knxd = require("eibd");
var knxd_registerGA = require('../platforms/KNX.js').registerGA; var knxd_registerGA = require('../platforms/KNX.js').registerGA;
var knxd_startMonitor = require('../platforms/KNX.js').startMonitor; var knxd_startMonitor = require('../platforms/KNX.js').startMonitor;
var milliTimeout = 300; // used to block responses while swiping var milliTimeout = 300; // used to block responses while swiping
var colorOn = "\x1b[30;47m";
var colorOff = "\x1b[0m";
function KNXDevice(log, config) { function KNXDevice(log, config) {
this.log = log; this.log = log;
@@ -33,6 +40,9 @@ function KNXDevice(log, config) {
if (config.name) { if (config.name) {
this.name = config.name; this.name = config.name;
} }
if (config.uuid_base) {
this.uuid_base = config.uuid_base;
}
if (config.knxd_ip){ if (config.knxd_ip){
this.knxd_ip = config.knxd_ip; this.knxd_ip = config.knxd_ip;
} else { } else {
@@ -115,6 +125,7 @@ KNXDevice.prototype = {
if (!groupAddress) { if (!groupAddress) {
return null; return null;
} }
this.log("[knxdevice:knxread] preparing knx request for "+groupAddress);
var knxdConnection = new knxd.Connection(); var knxdConnection = new knxd.Connection();
// this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port); // this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port);
knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() { knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() {
@@ -130,7 +141,7 @@ KNXDevice.prototype = {
if (err) { if (err) {
this.log("[ERROR] knxread:sendAPDU: " + err); this.log("[ERROR] knxread:sendAPDU: " + err);
} else { } else {
this.log("knx request sent for "+groupAddress); this.log("[knxdevice:knxread] knx request sent for "+groupAddress);
} }
}.bind(this)); }.bind(this));
} }
@@ -143,12 +154,12 @@ KNXDevice.prototype = {
// handle multiple addresses // handle multiple addresses
for (var i = 0; i < groupAddresses.length; i++) { for (var i = 0; i < groupAddresses.length; i++) {
if (groupAddresses[i]) { // do not bind empty addresses if (groupAddresses[i]) { // do not bind empty addresses
this.knxread (groupAddresses[i]); this.knxread (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0]); // clean address
} }
} }
} else { } else {
// it's only one // it's only one
this.knxread (groupAddresses); this.knxread (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0]); // regex for cleaning address
} }
}, },
@@ -158,70 +169,80 @@ KNXDevice.prototype = {
// boolean: get 0 or 1 from the bus, write boolean // boolean: get 0 or 1 from the bus, write boolean
knxregister_bool: function(addresses, characteristic) { knxregister_bool: function(addresses, characteristic) {
this.log("knx registering BOOLEAN " + addresses); this.log("knx registering BOOLEAN " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){ knxd_registerGA(addresses, function(val, src, dest, type, reverse){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName);
// iterate(characteristic); // iterate(characteristic);
characteristic.setValue(val ? 1 : 0, undefined, 'fromKNXBus');
}.bind(this)); characteristic.setValue(val ? (reverse ? 0:1) : (reverse ? 1:0), undefined, 'fromKNXBus');
},
knxregister_boolReverse: function(addresses, characteristic) {
this.log("knx registering BOOLEAN " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName);
// iterate(characteristic);
characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus');
}.bind(this)); }.bind(this));
}, },
// knxregister_boolReverse: function(addresses, characteristic) {
// this.log("knx registering BOOLEAN REVERSE " + addresses);
// knxd_registerGA(addresses, function(val, src, dest, type, reverse){
// this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName);
//// iterate(characteristic);
// characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus');
// }.bind(this));
// },
// percentage: get 0..255 from the bus, write 0..100 to characteristic // percentage: get 0..255 from the bus, write 0..100 to characteristic
knxregister_percent: function(addresses, characteristic) { knxregister_percent: function(addresses, characteristic) {
this.log("knx registering PERCENT " + addresses); this.log("knx registering PERCENT " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){ knxd_registerGA(addresses, function(val, src, dest, type, reverse){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
if (type !== "DPT5") { if (type !== "DPT5") {
this.log("[ERROR] Received value cannot be a percentage value"); this.log("[ERROR] Received value cannot be a percentage value");
} else { } else {
if (!characteristic.timeout) { characteristic.setValue(Math.round(( reverse ? (255-val):val)/255*100), undefined, 'fromKNXBus');
if (characteristic.timeout < Date.now()) {
characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus');
} else {
this.log("Blackout time");
}
} else {
characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus');
} // todo get the boolean logic right into one OR expresssion
} }
}.bind(this)); }.bind(this));
}, },
// float // float
knxregister_float: function(addresses, characteristic) { knxregister_float: function(addresses, characteristic) {
this.log("knx registering FLOAT " + addresses); // update for props refactor https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR50
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); var validValue = true;
var hk_value = Math.round(val*10)/10; var hk_value = 0.0;
if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) { this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering FLOAT " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type, reverse){
this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
// make hk_value compliant to properties
if (characteristic.props.minStep) {
// quantize
hk_value = Math.round(val/characteristic.props.minStep)/(1/characteristic.props.minStep);
} else {
hk_value = val;
}
// range check
validValue = true; // assume validity at beginning
if (characteristic.props.minValue) {
validValue = validValue && (hk_value>=characteristic.props.minValue);
}
if (characteristic.props.maxValue) {
validValue = validValue && (hk_value<=characteristic.props.maxValue);
}
if (validValue) {
characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit
} else { } else {
this.log("Value %s out of bounds %s...%s ",hk_value, characteristic.minimumValue, characteristic.maximumValue); this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, characteristic.props.minValue, characteristic.props.maxValue);
} }
}.bind(this)); }.bind(this));
}, },
//integer //integer
knxregister_int: function(addresses, characteristic) { knxregister_int: function(addresses, characteristic) {
this.log("knx registering FLOAT " + addresses); this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering INT " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){ knxd_registerGA(addresses, function(val, src, dest, type, reverse){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
if (val>=(characteristic.minimumValue || 0) && val<=(characteristic.maximumValue || 255)) { if (val>=(characteristic.props.minValue || 0) && val<=(characteristic.props.maxValue || 255)) {
characteristic.setValue(val, undefined, 'fromKNXBus'); characteristic.setValue(reverse ? (255-val):val, undefined, 'fromKNXBus');
} else { } else {
this.log("Value %s out of bounds %s...%s ",hk_value, (characteristic.minimumValue || 0), (characteristic.maximumValue || 255)); this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, (characteristic.props.minValue || 0), (characteristic.props.maxValue || 255));
} }
}.bind(this)); }.bind(this));
}, },
knxregister_HVAC: function(addresses, characteristic) { knxregister_HVAC: function(addresses, characteristic) {
this.log("knx registering HVAC " + addresses); this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering HVAC " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){ knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
var HAPvalue = 0; var HAPvalue = 0;
switch (val){ switch (val){
case 0: case 0:
@@ -261,9 +282,9 @@ KNXDevice.prototype = {
*/ */
// undefined, has to match! // undefined, has to match!
knxregister: function(addresses, characteristic) { knxregister: function(addresses, characteristic) {
this.log("knx registering " + addresses); this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){ knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
characteristic.setValue(val, undefined, 'fromKNXBus'); characteristic.setValue(val, undefined, 'fromKNXBus');
}.bind(this)); }.bind(this));
}, },
@@ -276,71 +297,74 @@ KNXDevice.prototype = {
* }.bind(this)); * }.bind(this));
* *
*/ */
setBooleanState: function(value, callback, context, gaddress) { setBooleanState: function(value, callback, context, gaddress, reverseflag) {
if (context === 'fromKNXBus') { if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!"); // this.log(gaddress + " event ping pong, exit!");
if (callback) { if (callback) {
callback(); callback();
} }
} else { } else {
var numericValue = 0; var numericValue = reverseflag ? 1:0;
if (value) { if (value) {
numericValue = 1; // need 0 or 1, not true or something numericValue = reverseflag ? 0:1; // need 0 or 1, not true or something
} }
this.log("Setting "+gaddress+" Boolean to %s", numericValue); this.log("["+ this.name +"]:Setting "+gaddress+" " + reverseflag ? " (reverse)":""+ " Boolean to %s", numericValue);
this.knxwrite(callback, gaddress,'DPT1',numericValue); this.knxwrite(callback, gaddress,'DPT1',numericValue);
} }
}, },
setBooleanReverseState: function(value, callback, context, gaddress) { // setBooleanReverseState: function(value, callback, context, gaddress) {
// if (context === 'fromKNXBus') {
//// this.log(gaddress + " event ping pong, exit!");
// if (callback) {
// callback();
// }
// } else {
// var numericValue = 0;
// if (!value) {
// numericValue = 1; // need 0 or 1, not true or something
// }
// this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue);
// this.knxwrite(callback, gaddress,'DPT1',numericValue);
// }
//
// },
setPercentage: function(value, callback, context, gaddress, reverseflag) {
if (context === 'fromKNXBus') { if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!"); // this.log(gaddress + "event ping pong, exit!");
if (callback) { if (callback) {
callback(); callback();
} }
} else { } else {
var numericValue = 0; var numericValue = 0;
if (!value) { value = ( value>=0 ? (value<=100 ? value:100):0 ); //ensure range 0..100
numericValue = 1; // need 0 or 1, not true or something if (reverseflag) {
} numericValue = 255 - Math.round(255*value/100); // convert 0..100 to 255..0 for KNX bus
this.log("Setting "+gaddress+" Boolean to %s", numericValue);
this.knxwrite(callback, gaddress,'DPT1',numericValue);
}
},
setPercentage: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log("event ping pong, exit!");
if (callback) {
callback();
}
} else { } else {
var numericValue = 0; numericValue = Math.round(255*value/100); // convert 0..100 to 0..255 for KNX bus
if (value) {
numericValue = Math.round(255*value/100); // convert 1..100 to 1..255 for KNX bus
} }
this.log("Setting "+gaddress+" percentage to %s (%s)", value, numericValue); this.log("["+ this.name +"]:Setting "+gaddress+" percentage to %s (%s)", value, numericValue);
this.knxwrite(callback, gaddress,'DPT5',numericValue); this.knxwrite(callback, gaddress,'DPT5',numericValue);
} }
}, },
setInt: function(value, callback, context, gaddress) { setInt: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') { if (context === 'fromKNXBus') {
this.log("event ping pong, exit!"); // this.log(gaddress + "event ping pong, exit!");
if (callback) { if (callback) {
callback(); callback();
} }
} else { } else {
var numericValue = 0; var numericValue = 0;
if (value && value>=0 && value<=255) { if (value && value>=0 && value<=255) {
numericValue = value; // assure 1..255 for KNX bus numericValue = value; // assure 0..255 for KNX bus
} }
this.log("Setting "+gaddress+" int to %s (%s)", value, numericValue); this.log("["+ this.name +"]:Setting "+gaddress+" int to %s (%s)", value, numericValue);
this.knxwrite(callback, gaddress,'DPT5',numericValue); this.knxwrite(callback, gaddress,'DPT5',numericValue);
} }
}, },
setFloat: function(value, callback, context, gaddress) { setFloat: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') { if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!"); // this.log(gaddress + " event ping pong, exit!");
if (callback) { if (callback) {
callback(); callback();
} }
@@ -349,13 +373,13 @@ KNXDevice.prototype = {
if (value) { if (value) {
numericValue = value; // homekit expects precision of 1 decimal numericValue = value; // homekit expects precision of 1 decimal
} }
this.log("Setting "+gaddress+" Float to %s", numericValue); this.log("["+ this.name +"]:Setting "+gaddress+" Float to %s", numericValue);
this.knxwrite(callback, gaddress,'DPT9',numericValue); this.knxwrite(callback, gaddress,'DPT9',numericValue);
} }
}, },
setHVACState: function(value, callback, context, gaddress) { setHVACState: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') { if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!"); // this.log(gaddress + " event ping pong, exit!");
if (callback) { if (callback) {
callback(); callback();
} }
@@ -378,7 +402,7 @@ KNXDevice.prototype = {
KNXvalue = 1; KNXvalue = 1;
} }
this.log("Setting "+gaddress+" HVAC to %s", KNXvalue); this.log("["+ this.name +"]:Setting "+gaddress+" HVAC to %s", KNXvalue);
this.knxwrite(callback, gaddress,'DPT5',KNXvalue); this.knxwrite(callback, gaddress,'DPT5',KNXvalue);
} }
@@ -387,33 +411,50 @@ KNXDevice.prototype = {
* *
*/ */
identify: function(callback) { identify: function(callback) {
this.log("Identify requested!"); this.log("["+ this.name +"]:Identify requested!");
callback(); // success callback(); // success
}, },
/** bindCharacteristic /** bindCharacteristic
* initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK) * initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK)
*/ */
bindCharacteristic: function(myService, characteristicType, valueType, config) { bindCharacteristic: function(myService, characteristicType, valueType, config, defaultValue) {
var myCharacteristic = myService.getCharacteristic(characteristicType); var myCharacteristic = myService.getCharacteristic(characteristicType);
var setGA = "";
var setReverse = false;
if (myCharacteristic === undefined) { if (myCharacteristic === undefined) {
throw new Error("unknown characteristics cannot be bound"); throw new Error("unknown characteristics cannot be bound");
} }
if (defaultValue) {
myCharacteristic.setValue(defaultValue);
}
if (config.Set) { if (config.Set) {
// can write // can write
// extract address and Reverse flag
setGA = config.Set.match(/\d*\/\d*\/\d*/);
if (setGA===null) {
this.log(colorOn + "["+ this.name +"]:["+myCharacteristic.displayName+"] Error in group adress: ["+ config.Set +"] "+colorOff);
throw new Error("EINVGROUPADRESS - Invalid group address given");
} else {
setGA=setGA[0]; // first element of returned array is the group address
}
setReverse = config.Set.match(/\d*\/\d*\/\d*(R)/) ? true:false;
switch (valueType) { switch (valueType) {
case "Bool": case "Bool":
myCharacteristic.on('set', function(value, callback, context) { myCharacteristic.on('set', function(value, callback, context) {
this.setBooleanState(value, callback, context, config.Set); this.setBooleanState(value, callback, context, setGA, setReverse); //NEW
}.bind(this));
break;
case "BoolReverse":
myCharacteristic.on('set', function(value, callback, context) {
this.setBooleanReverseState(value, callback, context, config.Set);
}.bind(this)); }.bind(this));
break; break;
// case "BoolReverse":
// this.log("["+ this.name +"]:["+myCharacteristic.displayName+"] \x1b[30;47m%s\x1b[0mWARNING in group adress: "+ config.Set +": Legacy BoolReverse used. Use " + config.Set +"R instead");
// myCharacteristic.on('set', function(value, callback, context) {
// this.setBooleanReverseState(value, callback, context, config.Set);
// }.bind(this));
// break;
case "Percent": case "Percent":
myCharacteristic.on('set', function(value, callback, context) { myCharacteristic.on('set', function(value, callback, context) {
this.setPercentage(value, callback, context, config.Set); this.setPercentage(value, callback, context, setGA, setReverse);
myCharacteristic.timeout = Date.now()+milliTimeout; myCharacteristic.timeout = Date.now()+milliTimeout;
}.bind(this)); }.bind(this));
break; break;
@@ -433,7 +474,7 @@ KNXDevice.prototype = {
}.bind(this)); }.bind(this));
break; break;
default: { default: {
this.log("[ERROR] unknown type passed"); this.log(colorOn + "[ERROR] unknown type passed: [" + valueType+"]"+ colorOff);
throw new Error("[ERROR] unknown type passed"); throw new Error("[ERROR] unknown type passed");
} }
} }
@@ -445,9 +486,9 @@ KNXDevice.prototype = {
case "Bool": case "Bool":
this.knxregister_bool([config.Set].concat(config.Listen || []), myCharacteristic); this.knxregister_bool([config.Set].concat(config.Listen || []), myCharacteristic);
break; break;
case "BoolReverse": // case "BoolReverse":
this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic); // this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic);
break; // break;
case "Percent": case "Percent":
this.knxregister_percent([config.Set].concat(config.Listen || []), myCharacteristic); this.knxregister_percent([config.Set].concat(config.Listen || []), myCharacteristic);
break; break;
@@ -461,10 +502,10 @@ KNXDevice.prototype = {
this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic); this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic);
break; break;
default: default:
this.log("[ERROR] unknown type passed"); this.log(colorOn+ "[ERROR] unknown type passed: ["+valueType+"]"+colorOff);
throw new Error("[ERROR] unknown type passed"); throw new Error("[ERROR] unknown type passed");
} }
this.log("Issuing read requests on the KNX bus..."); this.log("["+ this.name +"]:["+myCharacteristic.displayName+"]: Issuing read requests on the KNX bus...");
this.knxreadarray([config.Set].concat(config.Listen || [])); this.knxreadarray([config.Set].concat(config.Listen || []));
} }
return myCharacteristic; // for chaining or whatsoever return myCharacteristic; // for chaining or whatsoever
@@ -494,30 +535,30 @@ KNXDevice.prototype = {
var myService = new Service.ContactSensor(config.name,config.name); var myService = new Service.ContactSensor(config.name,config.name);
if (config.ContactSensorState) { if (config.ContactSensorState) {
this.log("ContactSensor ContactSensorState characteristic enabled"); this.log("["+ this.name +"]:ContactSensor ContactSensorState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState); this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState);
} else if (config.ContactSensorStateContact1) { } else if (config.ContactSensorStateContact1) {
this.log("ContactSensor ContactSensorStateContact1 characteristic enabled"); this.log(colorOn+ "[ERROR] outdated type passed: [ContactSensorStateContact1]"+colorOff);
this.bindCharacteristic(myService, Characteristic.ContactSensorState, "BoolReverse", config.ContactSensorStateContact1); throw new Error("[ERROR] outdated type passed");
} }
//optionals //optionals
if (config.StatusActive) { if (config.StatusActive) {
this.log("ContactSensor StatusActive characteristic enabled"); this.log("["+ this.name +"]:ContactSensor StatusActive characteristic enabled");
myService.addCharacteristic(Characteristic.StatusActive); myService.addCharacteristic(Characteristic.StatusActive);
this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive);
} }
if (config.StatusFault) { if (config.StatusFault) {
this.log("ContactSensor StatusFault characteristic enabled"); this.log("["+ this.name +"]:ContactSensor StatusFault characteristic enabled");
myService.addCharacteristic(Characteristic.StatusFault); myService.addCharacteristic(Characteristic.StatusFault);
this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault);
} }
if (config.StatusTampered) { if (config.StatusTampered) {
this.log("ContactSensor StatusTampered characteristic enabled"); this.log("["+ this.name +"]:ContactSensor StatusTampered characteristic enabled");
myService.addCharacteristic(Characteristic.StatusTampered); myService.addCharacteristic(Characteristic.StatusTampered);
this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered);
} }
if (config.StatusLowBattery) { if (config.StatusLowBattery) {
this.log("ContactSensor StatusLowBattery characteristic enabled"); this.log("["+ this.name +"]:ContactSensor StatusLowBattery characteristic enabled");
myService.addCharacteristic(Characteristic.StatusLowBattery); myService.addCharacteristic(Characteristic.StatusLowBattery);
this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery);
} }
@@ -555,27 +596,27 @@ KNXDevice.prototype = {
var myService = new Service.GarageDoorOpener(config.name,config.name); var myService = new Service.GarageDoorOpener(config.name,config.name);
if (config.CurrentDoorState) { if (config.CurrentDoorState) {
this.log("GarageDoorOpener CurrentDoorState characteristic enabled"); this.log("["+ this.name +"]:GarageDoorOpener CurrentDoorState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState); this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState);
} }
if (config.TargetDoorState) { if (config.TargetDoorState) {
this.log("GarageDoorOpener TargetDoorState characteristic enabled"); this.log("["+ this.name +"]:GarageDoorOpener TargetDoorState characteristic enabled");
//myService.getCharacteristic(Characteristic.TargetDoorState).minimumValue=0; // //myService.getCharacteristic(Characteristic.TargetDoorState).minimumValue=0; //
//myService.getCharacteristic(Characteristic.TargetDoorState).maximumValue=4; // //myService.getCharacteristic(Characteristic.TargetDoorState).maximumValue=4; //
this.bindCharacteristic(myService, Characteristic.TargetDoorState, "Int", config.TargetDoorState); this.bindCharacteristic(myService, Characteristic.TargetDoorState, "Int", config.TargetDoorState);
} }
if (config.ObstructionDetected) { if (config.ObstructionDetected) {
this.log("GarageDoorOpener ObstructionDetected characteristic enabled"); this.log("["+ this.name +"]:GarageDoorOpener ObstructionDetected characteristic enabled");
this.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected); this.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected);
} }
//optionals //optionals
if (config.LockCurrentState) { if (config.LockCurrentState) {
this.log("GarageDoorOpener LockCurrentState characteristic enabled"); this.log("["+ this.name +"]:GarageDoorOpener LockCurrentState characteristic enabled");
myService.addCharacteristic(Characteristic.LockCurrentState); myService.addCharacteristic(Characteristic.LockCurrentState);
this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Int", config.LockCurrentState); this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Int", config.LockCurrentState);
} }
if (config.LockTargetState) { if (config.LockTargetState) {
this.log("GarageDoorOpener LockTargetState characteristic enabled"); this.log("["+ this.name +"]:GarageDoorOpener LockTargetState characteristic enabled");
myService.addCharacteristic(Characteristic.LockTargetState); myService.addCharacteristic(Characteristic.LockTargetState);
this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState);
} }
@@ -596,12 +637,12 @@ KNXDevice.prototype = {
var myService = new Service.Lightbulb(config.name,config.name); var myService = new Service.Lightbulb(config.name,config.name);
// On (and Off) // On (and Off)
if (config.On) { if (config.On) {
this.log("Lightbulb on/off characteristic enabled"); this.log("["+ this.name +"]:Lightbulb on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On);
} // On characteristic } // On characteristic
// Brightness if available // Brightness if available
if (config.Brightness) { if (config.Brightness) {
this.log("Lightbulb Brightness characteristic enabled"); this.log("["+ this.name +"]:Lightbulb Brightness characteristic enabled");
myService.addCharacteristic(Characteristic.Brightness); // it's an optional myService.addCharacteristic(Characteristic.Brightness); // it's an optional
this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.Brightness); this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.Brightness);
} }
@@ -623,7 +664,7 @@ KNXDevice.prototype = {
var myService = new Service.LightSensor(config.name,config.name); var myService = new Service.LightSensor(config.name,config.name);
// CurrentTemperature) // CurrentTemperature)
if (config.CurrentAmbientLightLevel) { if (config.CurrentAmbientLightLevel) {
this.log("LightSensor CurrentAmbientLightLevel characteristic enabled"); this.log("["+ this.name +"]:LightSensor CurrentAmbientLightLevel characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentAmbientLightLevel, "Float", config.CurrentAmbientLightLevel); this.bindCharacteristic(myService, Characteristic.CurrentAmbientLightLevel, "Float", config.CurrentAmbientLightLevel);
} }
return myService; return myService;
@@ -648,20 +689,20 @@ KNXDevice.prototype = {
// LockCurrentState // LockCurrentState
if (config.LockCurrentState) { if (config.LockCurrentState) {
// for normal contacts: Secured = 1 // for normal contacts: Secured = 1
this.log("LockMechanism LockCurrentState characteristic enabled"); this.log("["+ this.name +"]:LockMechanism LockCurrentState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Bool", config.LockCurrentState); this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Bool", config.LockCurrentState);
} else if (config.LockCurrentStateSecured0) { } else if (config.LockCurrentStateSecured0) {
// for reverse contacts Secured = 0 // for reverse contacts Secured = 0
this.log("LockMechanism LockCurrentState characteristic enabled"); this.log(colorOn+ "[ERROR] outdated type passed: [LockCurrentStateSecured0]"+colorOff);
this.bindCharacteristic(myService, Characteristic.LockCurrentState, "BoolReverse", config.LockCurrentStateSecured0); throw new Error("[ERROR] outdated type passed");
} }
// LockTargetState // LockTargetState
if (config.LockTargetState) { if (config.LockTargetState) {
this.log("LockMechanism LockTargetState characteristic enabled"); this.log("["+ this.name +"]:LockMechanism LockTargetState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState);
} else if (config.LockTargetStateSecured0) { } else if (config.LockTargetStateSecured0) {
this.log("LockMechanism LockTargetState characteristic enabled"); this.log(colorOn+ "[ERROR] outdated type passed: [LockTargetStateSecured0]"+colorOff);
this.bindCharacteristic(myService, Characteristic.LockTargetState, "BoolReverse", config.LockTargetStateSecured0); throw new Error("[ERROR] outdated type passed");
} }
//iterate(myService); //iterate(myService);
@@ -683,27 +724,27 @@ KNXDevice.prototype = {
var myService = new Service.MotionSensor(config.name,config.name); var myService = new Service.MotionSensor(config.name,config.name);
if (config.MotionDetected) { if (config.MotionDetected) {
this.log("MotionSensor MotionDetected characteristic enabled"); this.log("["+ this.name +"]:MotionSensor MotionDetected characteristic enabled");
this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected); this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected);
} }
//optionals //optionals
if (config.StatusActive) { if (config.StatusActive) {
this.log("MotionSensor StatusActive characteristic enabled"); this.log("["+ this.name +"]:MotionSensor StatusActive characteristic enabled");
myService.addCharacteristic(Characteristic.StatusActive); myService.addCharacteristic(Characteristic.StatusActive);
this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive);
} }
if (config.StatusFault) { if (config.StatusFault) {
this.log("MotionSensor StatusFault characteristic enabled"); this.log("["+ this.name +"]:MotionSensor StatusFault characteristic enabled");
myService.addCharacteristic(Characteristic.StatusFault); myService.addCharacteristic(Characteristic.StatusFault);
this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault);
} }
if (config.StatusTampered) { if (config.StatusTampered) {
this.log("MotionSensor StatusTampered characteristic enabled"); this.log("["+ this.name +"]:MotionSensor StatusTampered characteristic enabled");
myService.addCharacteristic(Characteristic.StatusTampered); myService.addCharacteristic(Characteristic.StatusTampered);
this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered);
} }
if (config.StatusLowBattery) { if (config.StatusLowBattery) {
this.log("MotionSensor StatusLowBattery characteristic enabled"); this.log("["+ this.name +"]:MotionSensor StatusLowBattery characteristic enabled");
myService.addCharacteristic(Characteristic.StatusLowBattery); myService.addCharacteristic(Characteristic.StatusLowBattery);
this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery);
} }
@@ -726,11 +767,11 @@ KNXDevice.prototype = {
var myService = new Service.Outlet(config.name,config.name); var myService = new Service.Outlet(config.name,config.name);
// On (and Off) // On (and Off)
if (config.On) { if (config.On) {
this.log("Outlet on/off characteristic enabled"); this.log("["+ this.name +"]:Outlet on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On);
} // OutletInUse characteristic } // OutletInUse characteristic
if (config.OutletInUse) { if (config.OutletInUse) {
this.log("Outlet on/off characteristic enabled"); this.log("["+ this.name +"]:Outlet on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.OutletInUse, "Bool", config.OutletInUse); this.bindCharacteristic(myService, Characteristic.OutletInUse, "Bool", config.OutletInUse);
} }
return myService; return myService;
@@ -748,7 +789,7 @@ KNXDevice.prototype = {
var myService = new Service.Switch(config.name,config.name); var myService = new Service.Switch(config.name,config.name);
// On (and Off) // On (and Off)
if (config.On) { if (config.On) {
this.log("Switch on/off characteristic enabled"); this.log("["+ this.name +"]:Switch on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On);
} // On characteristic } // On characteristic
@@ -775,26 +816,35 @@ KNXDevice.prototype = {
var myService = new Service.Thermostat(config.name,config.name); var myService = new Service.Thermostat(config.name,config.name);
// CurrentTemperature) // CurrentTemperature)
// props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108
if (config.CurrentTemperature) { if (config.CurrentTemperature) {
this.log("Thermostat CurrentTemperature characteristic enabled"); this.log("["+ this.name +"]:Thermostat CurrentTemperature characteristic enabled");
myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({
minValue: config.CurrentTemperature.minValue || -40,
maxValue: config.CurrentTemperature.maxValue || 60
}); // °C by default
this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature);
} }
// TargetTemperature if available // TargetTemperature if available
if (config.TargetTemperature) { if (config.TargetTemperature) {
this.log("Thermostat TargetTemperature characteristic enabled"); this.log("["+ this.name +"]:Thermostat TargetTemperature characteristic enabled");
// default boundary too narrow for thermostats // default boundary too narrow for thermostats
myService.getCharacteristic(Characteristic.TargetTemperature).minimumValue=0; // °C // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108
myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C myService.getCharacteristic(Characteristic.TargetTemperature).setProps({
minValue: config.TargetTemperature.minValue || 0,
maxValue: config.TargetTemperature.maxValue || 40
});
this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature); this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature);
} }
// HVAC // HVAC
if (config.CurrentHeatingCoolingState) { if (config.CurrentHeatingCoolingState) {
this.log("Thermostat CurrentHeatingCoolingState characteristic enabled"); this.log("["+ this.name +"]:Thermostat CurrentHeatingCoolingState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState); this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState);
} }
// HVAC // HVAC
if (config.TargetHeatingCoolingState) { if (config.TargetHeatingCoolingState) {
this.log("Thermostat TargetHeatingCoolingState characteristic enabled"); this.log("["+ this.name +"]:Thermostat TargetHeatingCoolingState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.TargetHeatingCoolingState, "HVAC", config.TargetHeatingCoolingState); this.bindCharacteristic(myService, Characteristic.TargetHeatingCoolingState, "HVAC", config.TargetHeatingCoolingState);
} }
return myService; return myService;
@@ -812,10 +862,16 @@ KNXDevice.prototype = {
} }
var myService = new Service.TemperatureSensor(config.name,config.name); var myService = new Service.TemperatureSensor(config.name,config.name);
// CurrentTemperature) // CurrentTemperature)
// props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108
if (config.CurrentTemperature) { if (config.CurrentTemperature) {
this.log("TemperatureSensor CurrentTemperature characteristic enabled"); this.log("["+ this.name +"]:TemperatureSensor CurrentTemperature characteristic enabled");
myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({
minValue: config.CurrentTemperature.minValue || -40,
maxValue: config.CurrentTemperature.maxValue || 60
}); // °C by default
this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature);
} }
return myService; return myService;
}, },
getWindowService: function(config) { getWindowService: function(config) {
@@ -845,16 +901,16 @@ KNXDevice.prototype = {
var myService = new Service.Window(config.name,config.name); var myService = new Service.Window(config.name,config.name);
if (config.CurrentPosition) { if (config.CurrentPosition) {
this.log("Window CurrentPosition characteristic enabled"); this.log("["+ this.name +"]:Window CurrentPosition characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition);
} }
if (config.TargetPosition) { if (config.TargetPosition) {
this.log("Window TargetPosition characteristic enabled"); this.log("["+ this.name +"]:Window TargetPosition characteristic enabled");
this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition);
} }
if (config.PositionState) { if (config.PositionState) {
this.log("Window PositionState characteristic enabled"); this.log("["+ this.name +"]:Window PositionState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); this.bindCharacteristic(myService, Characteristic.PositionState, "Int", config.PositionState);
} }
return myService; return myService;
}, },
@@ -880,15 +936,15 @@ KNXDevice.prototype = {
var myService = new Service.WindowCovering(config.name,config.name); var myService = new Service.WindowCovering(config.name,config.name);
if (config.CurrentPosition) { if (config.CurrentPosition) {
this.log("WindowCovering CurrentPosition characteristic enabled"); this.log("["+ this.name +"]:WindowCovering CurrentPosition characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition);
} }
if (config.TargetPosition) { if (config.TargetPosition) {
this.log("WindowCovering TargetPosition characteristic enabled"); this.log("["+ this.name +"]:WindowCovering TargetPosition characteristic enabled");
this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition);
} }
if (config.PositionState) { if (config.PositionState) {
this.log("WindowCovering PositionState characteristic enabled"); this.log("["+ this.name +"]:WindowCovering PositionState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState);
} }
return myService; return myService;
@@ -910,7 +966,7 @@ KNXDevice.prototype = {
informationService informationService
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community") .setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
.setCharacteristic(Characteristic.Model, "KNX Universal Device") .setCharacteristic(Characteristic.Model, "KNX Universal Device")
.setCharacteristic(Characteristic.SerialNumber, "Version 1.1.2"); .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.4");
accessoryServices.push(informationService); accessoryServices.push(informationService);
@@ -965,8 +1021,8 @@ KNXDevice.prototype = {
accessoryServices.push(this.getWindowCoveringService(configService)); accessoryServices.push(this.getWindowCoveringService(configService));
break; break;
default: default:
this.log("[ERROR] unknown 'type' property of '"+configService.type+"' for service "+ configService.name + " in config.json. KNX platform section fault "); this.log("[ERROR] unknown 'type' property of ["+configService.type+"] for service ["+ configService.name + "] in config.json. KNX platform section fault ");
//throw new Error("[ERROR] unknown 'type' property for service "+ configService.name + " in config.json. KNX platform section fault "); throw new Error("[ERROR] unknown 'type' property of ["+configService.type+"] for service '"+ configService.name + "' in config.json. KNX platform section fault ");
} }
} }
// start listening for events on the bus (if not started yet - will prevent itself) // start listening for events on the bus (if not started yet - will prevent itself)
+2 -2
View File
@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request"); var request = require("request");
var komponist = require('komponist') var komponist = require('komponist')
+14 -14
View File
@@ -1,14 +1,14 @@
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var storage = require('node-persist'); var storage = require('node-persist');
var hap = require('HAP-NodeJS'); var hap = require("hap-nodejs");
var uuid = require('HAP-NodeJS').uuid; var uuid = require("hap-nodejs").uuid;
var Bridge = require('HAP-NodeJS').Bridge; var Bridge = require("hap-nodejs").Bridge;
var Accessory = require('HAP-NodeJS').Accessory; var Accessory = require("hap-nodejs").Accessory;
var Service = require('HAP-NodeJS').Service; var Service = require("hap-nodejs").Service;
var Characteristic = require('HAP-NodeJS').Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var accessoryLoader = require('HAP-NodeJS').AccessoryLoader; var accessoryLoader = require("hap-nodejs").AccessoryLoader;
var once = require('HAP-NodeJS/lib/util/once').once; var once = require("hap-nodejs/lib/util/once").once;
console.log("Starting HomeBridge server..."); console.log("Starting HomeBridge server...");
@@ -86,7 +86,7 @@ function loadAccessories() {
log("Initializing %s accessory...", accessoryType); log("Initializing %s accessory...", accessoryType);
var accessoryInstance = new accessoryConstructor(log, accessoryConfig); var accessoryInstance = new accessoryConstructor(log, accessoryConfig);
var accessory = createAccessory(accessoryInstance, accessoryName); var accessory = createAccessory(accessoryInstance, accessoryName, accessoryType, accessoryConfig.uuid_base); //pass accessoryType for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
// add it to the bridge // add it to the bridge
bridge.addBridgedAccessory(accessory); bridge.addBridgedAccessory(accessory);
@@ -113,11 +113,11 @@ function loadPlatforms() {
log("Initializing %s platform...", platformType); log("Initializing %s platform...", platformType);
var platformInstance = new platformConstructor(log, platformConfig); var platformInstance = new platformConstructor(log, platformConfig);
loadPlatformAccessories(platformInstance, log); loadPlatformAccessories(platformInstance, log, platformType);
} }
} }
function loadPlatformAccessories(platformInstance, log) { function loadPlatformAccessories(platformInstance, log, platformType) {
asyncCalls++; asyncCalls++;
platformInstance.accessories(once(function(foundAccessories){ platformInstance.accessories(once(function(foundAccessories){
asyncCalls--; asyncCalls--;
@@ -129,7 +129,7 @@ function loadPlatformAccessories(platformInstance, log) {
log("Initializing platform accessory '%s'...", accessoryName); log("Initializing platform accessory '%s'...", accessoryName);
var accessory = createAccessory(accessoryInstance, accessoryName); var accessory = createAccessory(accessoryInstance, accessoryName, platformType, accessoryInstance.uuid_base);
// add it to the bridge // add it to the bridge
bridge.addBridgedAccessory(accessory); bridge.addBridgedAccessory(accessory);
@@ -141,7 +141,7 @@ function loadPlatformAccessories(platformInstance, log) {
})); }));
} }
function createAccessory(accessoryInstance, displayName) { function createAccessory(accessoryInstance, displayName, accessoryType, uuid_base) {
var services = accessoryInstance.getServices(); var services = accessoryInstance.getServices();
@@ -159,7 +159,7 @@ function createAccessory(accessoryInstance, displayName) {
// The returned "services" for this accessory are simply an array of new-API-style // The returned "services" for this accessory are simply an array of new-API-style
// Service instances which we can add to a created HAP-NodeJS Accessory directly. // Service instances which we can add to a created HAP-NodeJS Accessory directly.
var accessoryUUID = uuid.generate(accessoryInstance.constructor.name + ":" + displayName); var accessoryUUID = uuid.generate(accessoryType + ":" + (uuid_base || displayName));
var accessory = new Accessory(displayName, accessoryUUID); var accessory = new Accessory(displayName, accessoryUUID);
+41 -1
View File
@@ -23,6 +23,10 @@
"token" : "telldus token", "token" : "telldus token",
"token_secret" : "telldus token secret" "token_secret" : "telldus token secret"
}, },
{
"platform" : "Telldus",
"name" : "Telldus"
},
{ {
"platform": "Wink", "platform": "Wink",
"name": "Wink", "name": "Wink",
@@ -96,6 +100,11 @@
"host": "http://192.168.1.10:8123", "host": "http://192.168.1.10:8123",
"password": "XXXXX", "password": "XXXXX",
"supported_types": ["light", "switch", "media_player", "scene"] "supported_types": ["light", "switch", "media_player", "scene"]
},
{
"platform": "LIFx",
"name": "LIFx",
"access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings"
} }
], ],
@@ -144,6 +153,24 @@
"ccu_id": "The XMP-API id of your HomeMatic device", "ccu_id": "The XMP-API id of your HomeMatic device",
"ccu_ip": "The IP-Adress of your HomeMatic CCU device" "ccu_ip": "The IP-Adress of your HomeMatic CCU device"
}, },
{
"accessory": "HomeMaticWindow",
"name": "Contact",
"description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)",
"ccu_id": "The XMP-API id of your HomeMatic device (type HM-Sec-RHS)",
"ccu_ip": "The IP-Adress of your HomeMatic CCU device"
},
{
"accessory": "HomeMaticThermo",
"name": "Contact",
"description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)",
"ccu_id_TargetTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_id_CurrentTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_id_ControlMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_id_ManuMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_id_AutoMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_ip": "The IP-Adress of your HomeMatic CCU device"
},
{ {
"accessory": "X10", "accessory": "X10",
"name": "Lamp", "name": "Lamp",
@@ -159,7 +186,20 @@
"off_url": "https://192.168.1.22:3030/devices/23222/off", "off_url": "https://192.168.1.22:3030/devices/23222/off",
"brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b", "brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b",
"http_method": "POST" "http_method": "POST"
},{ },
{
"accessory": "HttpHygrometer",
"name": "Kitchen",
"url": "http://host/URL",
"http_method": "GET"
},
{
"accessory": "HttpThermometer",
"name": "Garage",
"url": "http://home/URL",
"http_method": "GET"
},
{
"accessory": "ELKM1", "accessory": "ELKM1",
"name": "Security System", "name": "Security System",
"description": "Allows basic control of Elk M1 security system. You can use 1 of 3 arm modes: Away, Stay, Night. If you need to access all 3, create 3 accessories with different names.", "description": "Allows basic control of Elk M1 security system. You can use 1 of 3 arm modes: Away, Stay, Night. If you need to access all 3, create 3 accessories with different names.",
+9 -8
View File
@@ -12,34 +12,35 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local", "ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local",
"async": "^1.4.2",
"carwingsjs": "0.0.x", "carwingsjs": "0.0.x",
"chokidar": "^1.0.5", "chokidar": "^1.0.5",
"color": "0.10.x", "color": "0.10.x",
"debug": "^2.2.0",
"eibd": "^0.3.1", "eibd": "^0.3.1",
"elkington": "kevinohara80/elkington", "elkington": "kevinohara80/elkington",
"hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#4650e771f356a220868d873d16564a6be6603ff7", "hap-nodejs": "^0.0.2",
"harmonyhubjs-client": "^1.1.4", "harmonyhubjs-client": "^1.1.4",
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
"lifx-api": "^1.0.1", "komponist": "0.1.0",
"lifx": "git+https://github.com/magicmonkey/lifxjs.git", "lifx": "git+https://github.com/magicmonkey/lifxjs.git",
"lifx-api": "^1.0.1",
"mdns": "^2.2.4", "mdns": "^2.2.4",
"node-hue-api": "^1.0.5", "node-hue-api": "^1.0.5",
"node-icontrol": "^0.1.4", "node-icontrol": "^0.1.5",
"node-milight-promise": "0.0.x", "node-milight-promise": "0.0.x",
"node-persist": "0.0.x", "node-persist": "0.0.x",
"q": "1.4.x", "q": "1.4.x",
"tough-cookie": "^2.0.0",
"request": "2.49.x", "request": "2.49.x",
"sonos": "0.8.x", "sonos": "0.8.x",
"telldus-live": "0.2.x", "telldus-live": "^0.2.1",
"teslams": "1.0.1", "teslams": "1.0.1",
"tough-cookie": "^2.0.0",
"unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98",
"wemo": "0.2.x", "wemo": "0.2.x",
"wink-js": "0.0.5", "wink-js": "0.0.5",
"xml2js": "0.4.x", "xml2js": "0.4.x",
"xmldoc": "0.1.x", "xmldoc": "0.1.x",
"komponist" : "0.1.0", "yamaha-nodejs": "0.4.x"
"yamaha-nodejs": "0.4.x",
"debug": "^2.2.0"
} }
} }
+1 -1
View File
@@ -50,7 +50,7 @@
// When you attempt to add a device, it will ask for a "PIN code". // When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154. // The default code for all HomeBridge accessories is 031-45-154.
// //
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var request = require("request"); var request = require("request");
function DomoticzPlatform(log, config){ function DomoticzPlatform(log, config){
-1
View File
@@ -26,7 +26,6 @@ try {
Characteristic = require("hap-nodejs").Characteristic; Characteristic = require("hap-nodejs").Characteristic;
} }
var util = require('util'); var util = require('util');
+253
View File
@@ -0,0 +1,253 @@
// Fibaro Home Center 2 Platform Shim for HomeBridge
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "FibaroHC2",
// "name": "FibaroHC2",
// "host": "PUT IP ADDRESS OF YOUR HC2 HERE",
// "username": "PUT USERNAME OF YOUR HC2 HERE",
// "password": "PUT PASSWORD OF YOUR HC2 HERE"
// }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
var types = require("hap-nodejs/accessories/types.js");
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
function FibaroHC2Platform(log, config){
this.log = log;
this.host = config["host"];
this.username = config["username"];
this.password = config["password"];
this.auth = "Basic " + new Buffer(this.username + ":" + this.password).toString("base64");
this.url = "http://"+this.host+"/api/devices";
startPollingUpdate( this );
}
FibaroHC2Platform.prototype = {
accessories: function(callback) {
this.log("Fetching Fibaro Home Center devices...");
var that = this;
var foundAccessories = [];
request.get({
url: this.url,
headers : {
"Authorization" : this.auth
},
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json != undefined) {
json.map(function(s) {
that.log("Found: " + s.type);
if (s.visible == true) {
var accessory = null;
if (s.type == "com.fibaro.multilevelSwitch")
accessory = new FibaroAccessory(new Service.Lightbulb(s.name), [Characteristic.On, Characteristic.Brightness]);
else if (s.type == "com.fibaro.FGRM222" || s.type == "com.fibaro.FGR221")
accessory = new FibaroAccessory(new Service.WindowCovering(s.name), [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]);
else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch")
accessory = new FibaroAccessory(new Service.Switch(s.name), [Characteristic.On]);
else if (s.type == "com.fibaro.FGMS001" || s.type == "com.fibaro.motionSensor")
accessory = new FibaroAccessory(new Service.MotionSensor(s.name), [Characteristic.MotionDetected]);
else if (s.type == "com.fibaro.temperatureSensor")
accessory = new FibaroAccessory(new Service.TemperatureSensor(s.name), [Characteristic.CurrentTemperature]);
else if (s.type == "com.fibaro.doorSensor")
accessory = new FibaroAccessory(new Service.ContactSensor(s.name), [Characteristic.ContactSensorState]);
else if (s.type == "com.fibaro.lightSensor")
accessory = new FibaroAccessory(new Service.LightSensor(s.name), [Characteristic.CurrentAmbientLightLevel]);
else if (s.type == "com.fibaro.FGWP101")
accessory = new FibaroAccessory(new Service.Outlet(s.name), [Characteristic.On, Characteristic.OutletInUse]);
if (accessory != null) {
accessory.getServices = function() {
return that.getServices(accessory);
};
accessory.platform = that;
accessory.remoteAccessory = s;
accessory.id = s.id;
accessory.name = s.name;
accessory.model = s.type;
accessory.manufacturer = "Fibaro";
accessory.serialNumber = "<unknown>";
foundAccessories.push(accessory);
}
}
})
}
callback(foundAccessories);
} else {
that.log("There was a problem connecting with FibaroHC2.");
}
});
},
command: function(c,value, that) {
var url = "http://"+this.host+"/api/devices/"+that.id+"/action/"+c;
var body = value != undefined ? JSON.stringify({
"args": [ value ]
}) : null;
var method = "post";
request({
url: url,
body: body,
method: method,
headers: {
"Authorization" : this.auth
},
}, function(err, response) {
if (err) {
that.platform.log("There was a problem sending command " + c + " to" + that.name);
that.platform.log(url);
} else {
that.platform.log(that.name + " sent command " + c);
that.platform.log(url);
}
});
},
getAccessoryValue: function(callback, returnBoolean, homebridgeAccessory, powerValue) {
var url = "http://"+homebridgeAccessory.platform.host+"/api/devices/"+homebridgeAccessory.id+"/properties/";
if (powerValue)
url = url + "power";
else
url = url + "value";
request.get({
headers : {
"Authorization" : homebridgeAccessory.platform.auth
},
json: true,
url: url
}, function(err, response, json) {
homebridgeAccessory.platform.log(url);
if (!err && response.statusCode == 200) {
if (powerValue) {
callback(undefined, parseFloat(json.value) > 1.0 ? true : false);
} else if (returnBoolean)
callback(undefined, json.value == 0 ? 0 : 1);
else
callback(undefined, json.value);
} else {
homebridgeAccessory.platform.log("There was a problem getting value from" + homebridgeAccessory.id);
}
})
},
getInformationService: function(homebridgeAccessory) {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Name, homebridgeAccessory.name)
.setCharacteristic(Characteristic.Manufacturer, homebridgeAccessory.manufacturer)
.setCharacteristic(Characteristic.Model, homebridgeAccessory.model)
.setCharacteristic(Characteristic.SerialNumber, homebridgeAccessory.serialNumber);
return informationService;
},
bindCharacteristicEvents: function(characteristic, homebridgeAccessory) {
var onOff = characteristic.props.format == "bool" ? true : false;
var readOnly = true;
for (var i = 0; i < characteristic.props.perms.length; i++)
if (characteristic.props.perms[i] == "pw")
readOnly = false;
var powerValue = (characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") ? true : false;
subscribeUpdate(characteristic, homebridgeAccessory, onOff);
if (!readOnly) {
characteristic
.on('set', function(value, callback, context) {
if( context !== 'fromFibaro' ) {
if (onOff)
homebridgeAccessory.platform.command(value == 0 ? "turnOff": "turnOn", null, homebridgeAccessory);
else
homebridgeAccessory.platform.command("setValue", value, homebridgeAccessory);
}
callback();
}.bind(this) );
}
characteristic
.on('get', function(callback) {
homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory, powerValue);
}.bind(this) );
},
getServices: function(homebridgeAccessory) {
var informationService = homebridgeAccessory.platform.getInformationService(homebridgeAccessory);
for (var i=0; i < homebridgeAccessory.characteristics.length; i++) {
var characteristic = homebridgeAccessory.controlService.getCharacteristic(homebridgeAccessory.characteristics[i]);
if (characteristic == undefined)
characteristic = homebridgeAccessory.controlService.addCharacteristic(homebridgeAccessory.characteristics[i]);
homebridgeAccessory.platform.bindCharacteristicEvents(characteristic, homebridgeAccessory);
}
return [informationService, homebridgeAccessory.controlService];
}
}
function FibaroAccessory(controlService, characteristics) {
this.controlService = controlService;
this.characteristics = characteristics;
}
var lastPoll=0;
var pollingUpdateRunning = false;
function startPollingUpdate( platform )
{
if( pollingUpdateRunning )
return;
pollingUpdateRunning = true;
var updateUrl = "http://"+platform.host+"/api/refreshStates?last="+lastPoll;
request.get({
url: updateUrl,
headers : {
"Authorization" : platform.auth
},
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json != undefined) {
lastPoll = json.last;
if (json.changes != undefined) {
json.changes.map(function(s) {
if (s.value != undefined) {
var value=parseInt(s.value);
if (isNaN(value))
value=(s.value === "true");
for (i=0;i<updateSubscriptions.length; i++) {
var subscription = updateSubscriptions[i];
if (subscription.id == s.id) {
if (s.power != undefined && subscription.characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") {
subscription.characteristic.setValue(parseFloat(s.power) > 1.0 ? true : false, undefined, 'fromFibaro');
} else if ((subscription.onOff && typeof(value) == "boolean") || !subscription.onOff)
subscription.characteristic.setValue(value, undefined, 'fromFibaro');
else
subscription.characteristic.setValue(value == 0 ? false : true, undefined, 'fromFibaro');
}
}
}
})
}
}
} else {
platform.log("There was a problem connecting with FibaroHC2.");
}
pollingUpdateRunning = false;
setTimeout( function(){startPollingUpdate(platform)}, 2000 );
});
}
var updateSubscriptions = [];
function subscribeUpdate(characteristic, accessory, onOff)
{
// TODO: optimized management of updateSubscription data structure (no array with sequential access)
updateSubscriptions.push({ 'id': accessory.id, 'characteristic': characteristic, 'accessory': accessory, 'onOff': onOff });
}
module.exports.platform = FibaroHC2Platform;
+11 -2
View File
@@ -67,8 +67,8 @@
// When you attempt to add a device, it will ask for a "PIN code". // When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154. // The default code for all HomeBridge accessories is 031-45-154.
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var url = require('url') var url = require('url')
var request = require("request"); var request = require("request");
@@ -152,15 +152,24 @@ HomeAssistantPlatform.prototype = {
entity = data[i] entity = data[i]
entity_type = entity.entity_id.split('.')[0] entity_type = entity.entity_id.split('.')[0]
// ignore devices that are not in the list of supported types
if (that.supportedTypes.indexOf(entity_type) == -1) { if (that.supportedTypes.indexOf(entity_type) == -1) {
continue; continue;
} }
// ignore hidden devices
if (entity.attributes && entity.attributes.hidden) {
continue;
}
var accessory = null var accessory = null
if (entity_type == 'light') { if (entity_type == 'light') {
accessory = new HomeAssistantLight(that.log, entity, that) accessory = new HomeAssistantLight(that.log, entity, that)
}else if (entity_type == 'switch'){ }else if (entity_type == 'switch'){
console.log(JSON.stringify(entity))
console.log("");
console.log("");
accessory = new HomeAssistantSwitch(that.log, entity, that) accessory = new HomeAssistantSwitch(that.log, entity, that)
}else if (entity_type == 'scene'){ }else if (entity_type == 'scene'){
accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene') accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene')
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var xml2js = require('xml2js'); var xml2js = require('xml2js');
var request = require('request'); var request = require('request');
var util = require('util'); var util = require('util');
+552
View File
@@ -0,0 +1,552 @@
// Indigo Platform Shim for HomeBridge
// Written by Mike Riccio (https://github.com/webdeck)
// Based on many of the other HomeBridge plartform modules
// See http://www.indigodomo.com/ for more info on Indigo
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "Indigo", // required
// "name": "Indigo", // required
// "host": "127.0.0.1", // required
// "port": "8176", // required
// "username": "username", // optional
// "password": "password" // optional
// }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//
var types = require("hap-nodejs/accessories/types.js");
var Characteristic = require("hap-nodejs").Characteristic;
var request = require('request');
var async = require('async');
function IndigoPlatform(log, config) {
this.log = log;
this.baseURL = "http://" + config["host"] + ":" + config["port"];
if (config["username"] && config["password"]) {
this.auth = {
'user': config["username"],
'pass': config["password"],
'sendImmediately': false
};
}
}
IndigoPlatform.prototype = {
accessories: function(callback) {
var that = this;
this.log("Discovering Indigo Devices.");
var options = {
url: this.baseURL + "/devices.json/",
method: 'GET'
};
if (this.auth) {
options['auth'] = this.auth;
}
this.foundAccessories = [];
this.callback = callback;
request(options, function(error, response, body) {
if (error) {
console.trace("Requesting Indigo devices.");
that.log(error);
return error;
}
// Cheesy hack because response may have an extra comma at the start of the array, which is invalid
var firstComma = body.indexOf(",");
if (firstComma < 10) {
body = "[" + body.substr(firstComma + 1);
}
var json = JSON.parse(body);
async.eachSeries(json, function(item, asyncCallback) {
var deviceURL = that.baseURL + item.restURL;
var deviceOptions = {
url: deviceURL,
method: 'GET'
};
if (that.auth) {
deviceOptions['auth'] = that.auth;
}
request(deviceOptions, function(deviceError, deviceResponse, deviceBody) {
if (deviceError) {
console.trace("Requesting Indigo device info: " + deviceURL + "\nError: " + deviceError + "\nResponse: " + deviceBody);
}
else {
try {
var deviceJson = JSON.parse(deviceBody);
that.log("Discovered " + deviceJson.type + ": " + deviceJson.name);
that.foundAccessories.push(
new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson));
}
catch (e) {
that.log("Error parsing Indigo device info: " + deviceURL + "\nException: " + e + "\nResponse: " + deviceBody);
}
}
asyncCallback();
});
}, function(asyncError) {
// This will be called after all the requests complete
if (asyncError) {
console.trace("Requesting Indigo device info.");
that.log(asyncError);
}
that.callback(that.foundAccessories.sort(function (a,b) {
return (a.name > b.name) - (a.name < b.name);
}));
});
});
}
}
function IndigoAccessory(log, auth, deviceURL, json) {
this.log = log;
this.auth = auth;
this.deviceURL = deviceURL;
for (var prop in json) {
if (json.hasOwnProperty(prop)) {
this[prop] = json[prop];
}
}
}
IndigoAccessory.prototype = {
getStatus: function(callback) {
var that = this;
var options = {
url: this.deviceURL,
method: 'GET'
};
if (this.auth) {
options['auth'] = this.auth;
}
request(options, function(error, response, body) {
if (error) {
console.trace("Requesting Device Status.");
that.log(error);
}
else {
that.log("getStatus of " + that.name + ": " + body);
try {
var json = JSON.parse(body);
callback(json);
}
catch (e) {
console.trace("Requesting Device Status.");
that.log("Exception: " + e + "\nResponse: " + body);
}
}
});
},
updateStatus: function(params) {
var that = this;
var options = {
url: this.deviceURL + "?" + params,
method: 'PUT'
};
if (this.auth) {
options['auth'] = this.auth;
}
this.log("updateStatus of " + that.name + ": " + params);
request(options, function(error, response, body) {
if (error) {
console.trace("Updating Device Status.");
that.log(error);
return error;
}
});
},
query: function(prop, callback) {
this.getStatus(function(json) {
callback(json[prop]);
});
},
turnOn: function() {
if (this.typeSupportsOnOff) {
this.updateStatus("isOn=1");
}
},
turnOff: function() {
if (this.typeSupportsOnOff) {
this.updateStatus("isOn=0");
}
},
setBrightness: function(brightness) {
if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) {
this.updateStatus("brightness=" + brightness);
}
},
setSpeedIndex: function(speedIndex) {
if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) {
this.updateStatus("speedIndex=" + speedIndex);
}
},
getCurrentHeatingCooling: function(callback) {
this.getStatus(function(json) {
var mode = 0;
if (json["hvacOperatonModeIsHeat"]) {
mode = 1;
}
else if (json["hvacOperationModeIsCool"]) {
mode = 2;
}
else if (json["hvacOperationModeIsAuto"]) {
mode = 3;
}
callback(mode);
});
},
setTargetHeatingCooling: function(mode) {
if (mode == 0) {
param = "Off";
}
else if (mode == 1) {
param = "Heat";
}
else if (mode == 2) {
param = "Cool";
}
else if (mode == 3) {
param = "Auto";
}
if (param) {
this.updateStatus("hvacOperationModeIs" + param + "=true");
}
},
// Note: HomeKit wants all temperature values to be in celsius
getCurrentTemperature: function(callback) {
this.query("displayRawState", function(temperature) {
callback((temperature - 32.0) * 5.0 / 9.0);
});
},
getTargetTemperature: function(callback) {
this.getStatus(function(json) {
var temperature;
if (json["hvacOperatonModeIsHeat"]) {
temperature = json["setpointHeat"];
}
else if (json["hvacOperationModeIsCool"]) {
temperature = json["setpointCool"];
}
else {
temperature = (json["setpointHeat"] + json["setpointCool"]) / 2.0;
}
callback((temperature - 32.0) * 5.0 / 9.0);
});
},
setTargetTemperature: function(temperature) {
var that = this;
var t = (temperature * 9.0 / 5.0) + 32.0;
this.getStatus(function(json) {
if (json["hvacOperatonModeIsHeat"]) {
that.updateStatus("setpointHeat=" + t);
}
else if (json["hvacOperationModeIsCool"]) {
that.updateStatus("setpointCool=" + t);
}
else {
var cool = t + 5;
var heat = t - 5;
that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat);
}
});
},
informationCharacteristics: function() {
return [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: "Indigo",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: this.type,
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: this.addressStr,
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.WRITE],
format: Characteristic.Formats.BOOL,
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
]
},
controlCharacteristics: function(that) {
var hasAType = false;
var cTypes = [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: that.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
}];
if (that.typeSupportsDim) {
hasAType = true;
cTypes.push({
cType: types.BRIGHTNESS_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: that.brightness,
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: Characteristic.Units.PERCENTAGE,
onUpdate: function(value) {
that.setBrightness(value);
},
onRead: function(callback) {
that.query("brightness", callback);
}
});
}
if (that.typeSupportsSpeedControl) {
hasAType = true;
cTypes.push({
cType: types.ROTATION_SPEED_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the speed of the fan",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
onUpdate: function(value) {
that.setSpeedIndex(value);
},
onRead: function(callback) {
that.query("speedIndex", callback);
}
});
}
if (that.typeSupportsHVAC) {
hasAType = true;
cTypes.push({
cType: types.CURRENTHEATINGCOOLING_CTYPE,
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Mode",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
onUpdate: null,
onRead: function(callback) {
that.getCurrentHeatingCooling(callback);
}
});
cTypes.push({
cType: types.TARGETHEATINGCOOLING_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Mode",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
onUpdate: function(value) {
that.setTargetHeatingCooling(value);
},
onRead: function(callback) {
that.getCurrentHeatingCooling(callback);
}
});
cTypes.push({
cType: types.CURRENT_TEMPERATURE_CTYPE,
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
designedMinValue: 16,
designedMaxValue: 38,
designedMinStep: 1,
initialValue: 20,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Temperature",
unit: Characteristic.Units.FAHRENHEIT,
onUpdate: null,
onRead: function(callback) {
that.getCurrentTemperature(callback);
}
});
cTypes.push({
cType: types.TARGET_TEMPERATURE_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
designedMinValue: 16,
designedMaxValue: 38,
designedMinStep: 1,
initialValue: 20,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Temperature",
unit: Characteristic.Units.FAHRENHEIT,
onUpdate: function(value) {
that.setTargetTemperature(value);
},
onRead: function(callback) {
that.getTargetTemperature(callback);
}
});
cTypes.push({
cType: types.TEMPERATURE_UNITS_CTYPE,
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: 1,
supportEvents: false,
supportBonjour: false,
manfDescription: "Unit",
onUpdate: null,
onRead: function(callback) {
callback(1);
}
});
}
if (that.typeSupportsOnOff || !hasAType) {
cTypes.push({
cType: types.POWER_STATE_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.BOOL,
initialValue: (that.isOn) ? 1 : 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1,
onUpdate: function(value) {
if (value == 0) {
that.turnOff();
} else {
that.turnOn();
}
},
onRead: function(callback) {
that.query("isOn", function(isOn) {
callback((isOn) ? 1 : 0);
});
}
});
}
return cTypes;
},
sType: function() {
if (this.typeSupportsHVAC) {
return types.THERMOSTAT_STYPE;
} else if (this.typeSupportsDim) {
return types.LIGHTBULB_STYPE;
} else if (this.typeSupportsSpeedControl) {
return types.FAN_STYPE;
} else if (this.typeSupportsOnOff) {
return types.SWITCH_STYPE;
}
return types.SWITCH_STYPE;
},
getServices: function() {
var that = this;
var services = [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: that.informationCharacteristics(),
},
{
sType: that.sType(),
characteristics: that.controlCharacteristics(that)
}];
that.log("Loaded services for " + that.name);
return services;
}
};
module.exports.accessory = IndigoAccessory;
module.exports.platform = IndigoPlatform;
+9 -7
View File
@@ -8,7 +8,7 @@
"description": "This is an example configuration file for KNX platform shim", "description": "This is an example configuration file for KNX platform shim",
"hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration", "hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration",
"hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!", "hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!",
"hint3": "For valid services and their characteristics have a look at the knxdevice.md file in folder accessories!", "hint3": "For valid services and their characteristics have a look at the KNX.md file in folder platforms!",
"platforms": [ "platforms": [
{ {
"platform": "KNX", "platform": "KNX",
@@ -61,13 +61,13 @@
"services": [ "services": [
{ {
"type": "LockMechanism", "type": "LockMechanism",
"description": "iOS8 Lock mechanism, Supports LockCurrentStateSecured0 OR LockCurrentState, LockTargetStateSecured0 OR LockTargetState, use depending if LOCKED is 0 or 1", "description": "iOS8 Lock mechanism, Supports LockCurrentState, LockTargetState, append R to the addresses if LOCKED is 1",
"name": "Office Window Lock", "name": "Office Window Lock",
"LockCurrentStateSecured0": { "LockCurrentState": {
"Listen": "5/3/15" "Listen": "5/3/15R"
}, },
"LockTargetStateSecured0": { "LockTargetState": {
"Listen": "5/3/15" "Listen": "5/3/16R"
} }
} }
] ]
@@ -150,5 +150,7 @@
] ]
} }
], ],
"accessories": [] "accessories": [
]
} }
+11 -8
View File
@@ -2,7 +2,7 @@
* based on Sonos platform * based on Sonos platform
*/ */
'use strict'; 'use strict';
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
//var hardware = require('myHardwareSupport'); //require any additional hardware packages //var hardware = require('myHardwareSupport'); //require any additional hardware packages
var knxd = require('eibd'); var knxd = require('eibd');
@@ -116,8 +116,8 @@ function groupsocketlisten(opts, callback) {
} }
var registerSingleGA = function registerSingleGA (groupAddress, callback) { var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) {
subscriptions.push({address: groupAddress, callback: callback }); subscriptions.push({address: groupAddress, callback: callback, reverse:reverse });
} }
/* /*
@@ -143,7 +143,7 @@ var startMonitor = function startMonitor(opts) { // using { host: name-ip, port
if (subscriptions[i].address === dest) { if (subscriptions[i].address === dest) {
// found one, notify // found one, notify
console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']'); console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']');
subscriptions[i].callback(val, src, dest, type); subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse);
} }
} }
}); });
@@ -156,7 +156,7 @@ var startMonitor = function startMonitor(opts) { // using { host: name-ip, port
if (subscriptions[i].address === dest) { if (subscriptions[i].address === dest) {
// found one, notify // found one, notify
// console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']'); // console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']');
subscriptions[i].callback(val, src, dest, type); subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse);
} }
} }
@@ -185,13 +185,16 @@ var registerGA = function (groupAddresses, callback) {
if (groupAddresses.constructor.toString().indexOf("Array") > -1) { if (groupAddresses.constructor.toString().indexOf("Array") > -1) {
// handle multiple addresses // handle multiple addresses
for (var i = 0; i < groupAddresses.length; i++) { for (var i = 0; i < groupAddresses.length; i++) {
if (groupAddresses[i]) { // do not bind empty addresses if (groupAddresses[i] && groupAddresses[i].match(/(\d*\/\d*\/\d*)/)) { // do not bind empty addresses or invalid addresses
registerSingleGA (groupAddresses[i], callback); // clean the addresses
registerSingleGA (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0], callback,groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false );
} }
} }
} else { } else {
// it's only one // it's only one
registerSingleGA (groupAddresses, callback); if (groupAddresses.match(/(\d*\/\d*\/\d*)/)) {
registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false);
}
} }
// console.log("listeners now: " + subscriptions.length); // console.log("listeners now: " + subscriptions.length);
}; };
+55 -12
View File
@@ -47,7 +47,7 @@ You have to add services in the following syntax:
{ {
"type": "SERVICENAME", "type": "SERVICENAME",
"description": "This is just for you to remember things", "description": "This is just for you to remember things",
"name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory", "name": "beer tap thermostat",
"CHARACTERISTIC1": { "CHARACTERISTIC1": {
"Set": "1/1/6", "Set": "1/1/6",
"Listen": [ "Listen": [
@@ -68,11 +68,54 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres
`"Listen":["1/2/3","1/2/4","1/2/5"]` is an array of addresses that are listened to additionally. To these addresses never values get written, but the on startup the service will issue *KNX read requests* to ALL addresses listed in `Set:` and in `Listen:` `"Listen":["1/2/3","1/2/4","1/2/5"]` is an array of addresses that are listened to additionally. To these addresses never values get written, but the on startup the service will issue *KNX read requests* to ALL addresses listed in `Set:` and in `Listen:`
# Supported Services and their characteristics For two characteristics there are additional minValue and maxValue attributes. These are CurrentTemperature and TargetTemperature, and are used in TemperatureSensor and Thermostat.
So the charcteristic section may look like:
````json
{
"type": "Thermostat",
"description": "Sample thermostat",
"name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory",
"CurrentTemperature": {
"Set": "1/1/6",
"Listen": [
"1/1/63"
],
"minValue": -18,
"maxValue": 30
},
"TargetTemperature": {
"Set": "1/1/62",
"Listen": [
"1/1/64"
],
"minValue": -4,
"maxValue": 12
}
}
````
## reversal of values for characteristics
In general, all DPT1 types can be reversed. If you need a 1 for "contact" of a contact senser, you can append an "R" to the group address.
Likewise, all percentages of DPT5 can be reversed, if you need a 100% (=255) for window closed, append an "R" to the group address. Do not forget the listening addresses!
````json
{
"type": "ContactSensor",
"description": "Sample ContactSensor with 1 as contact (0 is Apple's default)",
"name": "WindowContact1",
"ContactSensorState": {
"Listen": [
"1/1/100R"
]
}
}
````
# Supported Services and their characteristics
## ContactSensor ## ContactSensor
- ContactSensorState: DPT 1.002, 0 as contact **OR** - ContactSensorState: DPT 1.002, 0 as contact
- ContactSensorStateContact1: DPT 1.002, 1 as contact - ~~ContactSensorStateContact1: DPT 1.002, 1 as contact~~
- StatusActive: DPT 1.011, 1 as true - StatusActive: DPT 1.011, 1 as true
- StatusFault: DPT 1.011, 1 as true - StatusFault: DPT 1.011, 1 as true
@@ -113,10 +156,10 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres
- CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux - CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux
## LockMechanism (This is poorly mapped!) ## LockMechanism (This is poorly mapped!)
- LockCurrentState: DPT 1, 1 as secured **OR (but not both:)** - LockCurrentState: DPT 1, 1 as secured
- LockCurrentStateSecured0: DPT 1, 0 as secured - ~~LockCurrentStateSecured0: DPT 1, 0 as secured~~
- LockTargetState: DPT 1, 1 as secured **OR** - LockTargetState: DPT 1, 1 as secured
- LockTargetStateSecured0: DPT 1, 0 as secured - ~~LockTargetStateSecured0: DPT 1, 0 as secured~~
*ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3* *ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3*
@@ -136,11 +179,11 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres
- On: DPT 1.001, 1 as on, 0 as off - On: DPT 1.001, 1 as on, 0 as off
## TemperatureSensor ## TemperatureSensor
- CurrentTemperature: DPT9.001 in °C [listen only] - CurrentTemperature: DPT9.001 in °C [listen only]
## Thermostat ## Thermostat
- CurrentTemperature: DPT9.001 in °C [listen only] - CurrentTemperature: DPT9.001 in °C [listen only], -40 to 80°C if not overriden as shown above
- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored - TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored
- CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] - CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only]
- TargetHeatingCoolingState: DPT20.102 HVAC, as above - TargetHeatingCoolingState: DPT20.102 HVAC, as above
@@ -152,7 +195,7 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres
## WindowCovering ## WindowCovering
- CurrentPosition: DPT5 percentage - CurrentPosition: DPT5 percentage
- TargetPosition: DPT5 percentage - TargetPosition: DPT5 percentage
- PositionState: DPT5 value [listen only] - PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 Stopped]
### not yet supported ### not yet supported
- HoldPosition - HoldPosition
+2 -2
View File
@@ -16,8 +16,8 @@
// The default code for all HomeBridge accessories is 031-45-154. // The default code for all HomeBridge accessories is 031-45-154.
// //
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var lifxRemoteObj = require('lifx-api'); var lifxRemoteObj = require('lifx-api');
var lifx_remote; var lifx_remote;
+1 -1
View File
@@ -17,7 +17,7 @@
// //
var types = require('HAP-NodeJS/accessories/types.js'); var types = require('hap-nodejs/accessories/types.js');
var harmonyDiscover = require('harmonyhubjs-discover'); var harmonyDiscover = require('harmonyhubjs-discover');
var harmony = require('harmonyhubjs-client'); var harmony = require('harmonyhubjs-client');
+2 -2
View File
@@ -47,8 +47,8 @@ TODO:
*/ */
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var Milight = require('node-milight-promise').MilightController; var Milight = require('node-milight-promise').MilightController;
var commands = require('node-milight-promise').commands; var commands = require('node-milight-promise').commands;
+5 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var nest = require('unofficial-nest-api'); var nest = require('unofficial-nest-api');
function NestPlatform(log, config){ function NestPlatform(log, config){
@@ -44,7 +44,11 @@ NestPlatform.prototype = {
function NestThermostatAccessory(log, name, device, deviceId) { function NestThermostatAccessory(log, name, device, deviceId) {
// device info // device info
if (name) {
this.name = name; this.name = name;
} else {
this.name = "Nest";
}
this.model = device.model_version; this.model = device.model_version;
this.serial = device.serial_number; this.serial = device.serial_number;
this.deviceId = deviceId; this.deviceId = deviceId;
+347
View File
@@ -0,0 +1,347 @@
// OpenHAB Platform Shim for HomeBridge
// Written by Tommaso Marchionni
// Based on many of the other HomeBridge platform modules
//
// Revisions:
//
// 17 October 2015 [tommasomarchionni]
// - Initial release
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "Openhab",
// "name": "Openhab",
// "server": "127.0.0.1",
// "port": "8080",
// "sitemap": "demo"
// }
// ],
//
// Example of sitemap in OpenHAB:
// sitemap homekit label="HomeKit" {
// Switch item=Light_1 label="Light 1"
// }
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
var Service = require("hap-nodejs/lib/Service.js").Service;
var Characteristic = require("hap-nodejs").Characteristic;
function OpenhabPlatform(log, config){
this.log = log;
this.user = config["user"];
this.password = config["password"];
this.server = config["server"];
this.port = config["port"];
this.protocol = "http";
this.sitemap = "demo";
if (typeof config["sitemap"] != 'undefined') {
this.sitemap = config["sitemap"];
}
}
OpenhabPlatform.prototype = {
sitemapUrl: function() {
var serverString = this.server;
//TODO da verificare
if (this.user && this.password) {
serverString = this.user + ":" + this.password + "@" + serverString;
}
return this.protocol + "://" + serverString + ":" + this.port + "/rest/sitemaps/" + this.sitemap + "?type=json";
},
parseSitemap: function(sitemap) {
var widgets = [].concat(sitemap.homepage.widget);
var result = [];
for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i];
if (!widget.item) {
//TODO to handle frame
this.log("WARN: The widget '" + widget.label + "' does not reference an item.");
continue;
}
if (widget.item.type=="SwitchItem" || widget.item.type=="DimmerItem" || widget.item.type == "RollershutterItem"){
accessory = new OpenhabAccessory(this.log,this,widget.widgetId,widget.label,widget.item)
this.log("Accessory Found: " + widget.label);
result.push(accessory);
}
}
return result;
},
accessories: function(callback) {
this.log("Fetching OpenHAB devices.");
var that = this;
url = that.sitemapUrl();
this.log("Connecting to " + url);
request.get({
url: url,
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
callback(that.parseSitemap(json));
} else {
that.log("There was a problem connecting to OpenHAB.");
}
});
}
};
function OpenhabAccessory(log, platform, widgetId, label, detail) {
this.log = log;
this.platform = platform;
this.idx = widgetId;
this.name = label;
this.label = label;
this.type = detail.type;
this.deviceURL = detail.link;
this.addressStr = "n/a";
this.state = detail.state;
if (this.type == "DimmerItem") {
this.typeSupportsOnOff = true;
this.typeSupportsDim = true;
}
if (this.type == "SwitchItem") {
this.typeSupportsOnOff = true;
}
if (this.type == "RollershutterItem") {
this.typeSupportsWindowCovering = true;
}
}
OpenhabAccessory.prototype = {
updateStatus: function(command) {
var that = this;
var options = {
url: this.deviceURL,
method: 'POST',
body: "" + command
};
if (this.auth) {
options['auth'] = this.auth;
}
that.log("eseguo post");
request(options, function(error, response, body) {
if (error) {
console.trace("Updating Device Status.");
that.log(error);
return error;
}
that.log("updateStatus of " + that.name + ": " + command);
});
},
getServiceType: function() {
if (this.typeSupportsWindowCovering){
return new Service.WindowCovering;
} else if (this.typeSupportsDim) {
return new Service.Lightbulb;
} else if (this.typeSupportsOnOff) {
return new Service.Switch;
}
},
updateStatus: function(command, callback) {
var that = this;
var options = {
url: this.deviceURL,
method: 'POST',
body: "" + command
};
if (this.auth) {
options['auth'] = this.auth;
}
request(options, function(error, response, body) {
if (error) {
//console.trace("Updating Device Status.");
//that.log(error);
//return error;
callback(new Error(error));
} else {
that.log("updateStatus of " + that.name + ": " + command);
callback(true);
}
}.bind(this));
},
setPowerState: function(powerOn, callback) {
var that = this;
if (this.typeSupportsOnOff) {
if (powerOn) {
var command = "ON";
} else {
var command = "OFF";
}
this.log("Setting power state on the '"+this.name+"' to " + command);
this.updateStatus(command, function(noError){
if (noError) {
that.log("Successfully set '"+that.name+"' to " + command);
callback();
} else {
callback(new Error('Can not communicate with OpenHAB.'));
}
}.bind(this));
}else{
callback(new Error(this.name + " not supports ONOFF"));
}
},
getStatus: function(callback){
var that = this;
this.log("Fetching status brightness for: " + this.name);
var options = {
url: this.deviceURL + '/state?type=json',
method: 'GET'
};
if (this.auth) {
options['auth'] = this.auth;
}
request(options, function(error, response, body) {
if (error) {
//console.trace("Requesting Device Status.");
//that.log(error);
//return error;
callback(new Error('Can not communicate with Home Assistant.'));
} else {
that.log("getStatus of " + that.name + ": " + body);
callback(null,body);
}
}.bind(this));
},
getCurrentPosition: function(callback){
callback(100);
},
getPositionState: function(callback){
this.log("Fetching position state for: " + this.name);
callback(Characteristic.PositionState.STOPPED);
},
setTargetPosition: function(level, callback) {
var that = this;
this.log("Setting target position on the '"+this.name+"' to " + level);
this.updateStatus(level, function(noError){
if (noError) {
that.log("Successfully set position on the '"+that.name+"' to " + level);
callback();
} else {
callback(new Error('Can not communicate with OpenHAB.'));
}
}.bind(this));
},
setBrightness: function(level, callback) {
var that = this;
if (this.typeSupportsDim && level >= 0 && level <= 100) {
this.log("Setting brightness on the '"+this.name+"' to " + level);
this.updateStatus(level, function(noError){
if (noError) {
that.log("Successfully set brightness on the '"+that.name+"' to " + level);
callback();
} else {
callback(new Error('Can not communicate with OpenHAB.'));
}
}.bind(this));
}
},
getServices: function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "OpenHAB")
.setCharacteristic(Characteristic.Model, this.type)
.setCharacteristic(Characteristic.SerialNumber, "1234567890")
.setCharacteristic(Characteristic.Name, this.label);
var otherService = this.getServiceType();
if (this.typeSupportsOnOff) {
otherService
.getCharacteristic(Characteristic.On)
.on('get', this.getStatus.bind(this))
.on('set', this.setPowerState.bind(this));
}
if (this.typeSupportsDim) {
otherService
.addCharacteristic(Characteristic.Brightness)
.on('get', this.getStatus.bind(this))
.on('set', this.setBrightness.bind(this));
}
if (this.typeSupportsWindowCovering) {
var currentPosition = 100;
otherService
.getCharacteristic(Characteristic.CurrentPosition)
.on('get', this.getCurrentPosition.bind(this))
.setValue(currentPosition);
otherService
.getCharacteristic(Characteristic.PositionState)
.on('get', this.getPositionState.bind(this))
.setValue(Characteristic.PositionState.STOPPED);
otherService
.getCharacteristic(Characteristic.TargetPosition)
.on('get', this.getCurrentPosition.bind(this))
.on('set', this.setTargetPosition.bind(this));
}
console.log(informationService);
return [informationService, otherService];
}
}
module.exports.accessory = OpenhabAccessory;
module.exports.platform = OpenhabPlatform;
+1 -1
View File
@@ -33,7 +33,7 @@ var hue = require("node-hue-api"),
HueApi = hue.HueApi, HueApi = hue.HueApi,
lightState = hue.lightState; lightState = hue.lightState;
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
function PhilipsHuePlatform(log, config) { function PhilipsHuePlatform(log, config) {
this.log = log; this.log = log;
+1 -1
View File
@@ -1,7 +1,7 @@
// SmartThings JSON API SmartApp required // SmartThings JSON API SmartApp required
// https://github.com/jnewland/SmartThings/blob/master/JSON.groovy // https://github.com/jnewland/SmartThings/blob/master/JSON.groovy
// //
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var request = require("request"); var request = require("request");
function SmartThingsPlatform(log, config){ function SmartThingsPlatform(log, config){
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var sonos = require('sonos'); var sonos = require('sonos');
function SonosPlatform(log, config){ function SonosPlatform(log, config){
+265
View File
@@ -0,0 +1,265 @@
var types = require("hap-nodejs/accessories/types.js");
var telldus = require('telldus');
function TelldusPlatform(log, config) {
var that = this;
that.log = log;
}
TelldusPlatform.prototype = {
accessories: function(callback) {
var that = this;
that.log("Fetching devices...");
var devices = telldus.getDevicesSync();
that.log("Found " + devices.length + " devices...");
var foundAccessories = [];
// Clean non device
for (var i = 0; i < devices.length; i++) {
if (devices[i].type != 'DEVICE') {
devices.splice(i, 1);
}
}
for (var i = 0; i < devices.length; i++) {
if (devices[i].type === 'DEVICE') {
TelldusAccessory.create(that.log, devices[i], function(err, accessory) {
if (!!err) that.log("Couldn't load device info");
foundAccessories.push(accessory);
if (foundAccessories.length >= devices.length) {
callback(foundAccessories);
}
});
}
}
}
};
var TelldusAccessory = function TelldusAccessory(log, device) {
this.log = log;
var m = device.model.split(':');
this.dimTimeout = false;
// Set accessory info
this.device = device;
this.id = device.id;
this.name = device.name;
this.manufacturer = "Telldus"; // NOTE: Change this later
this.model = device.model;
this.status = device.status;
switch (device.status.name) {
case 'OFF':
this.state = 0;
this.stateValue = 0;
break;
case 'ON':
this.state = 2;
this.stateValue = 1;
break;
case 'DIM':
this.state = 16;
this.stateValue = device.status.level;
break;
}
};
TelldusAccessory.create = function (log, device, callback) {
callback(null, new TelldusAccessory(log, device));
};
TelldusAccessory.prototype = {
dimmerValue: function() {
if (this.state === 1) {
return 100;
}
if (this.state === 16 && this.stateValue != "unde") {
return parseInt(this.stateValue * 100 / 255);
}
return 0;
},
informationCharacteristics: function() {
var that = this;
informationCharacteristics = [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.manufacturer,
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.model,
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: function () {
telldus.turnOff(that.id, function(err){
if (!!err) that.log("Error: " + err.message);
telldus.turnOn(that.id, function(err){
if (!!err) that.log("Error: " + err.message);
telldus.turnOff(that.id, function(err){
if (!!err) that.log("Error: " + err.message);
telldus.turnOn(that.id, function(err){
if (!!err) that.log("Error: " + err.message);
});
});
});
});
},
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
];
return informationCharacteristics;
},
controlCharacteristics: function() {
var that = this;
cTypes = [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.name,
supportEvents: true,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
}]
cTypes.push({
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) {
if (value) {
telldus.turnOn(that.id, function(err){
if (!!err) {
that.log("Error: " + err.message)
} else {
that.log(that.name + " - Updated power state: ON");
}
});
} else {
telldus.turnOff(that.id, function(err){
if (!!err) {
that.log("Error: " + err.message)
} else {
that.log(that.name + " - Updated power state: OFF");
}
});
}
},
perms: ["pw","pr","ev"],
format: "bool",
initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != 0)) ? 1 : 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1
})
if (that.model === "selflearning-dimmer") {
cTypes.push({
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function (value) {
if (that.dimTimeout) {
clearTimeout(that.dimTimeout);
}
that.dimTimeout = setTimeout(function(){
telldus.dim(that.id, (255 * (value / 100)), function(err, result){
if (!!err) {
that.log("Error: " + err.message);
} else {
that.log(that.name + " - Updated brightness: " + value);
}
});
that.dimTimeout = false;
}, 250);
},
perms: ["pw", "pr", "ev"],
format: "int",
initialValue: that.dimmerValue(),
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
})
}
return cTypes
},
getServices: function() {
var services = [
{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: this.informationCharacteristics()
},
{
sType: types.LIGHTBULB_STYPE,
characteristics: this.controlCharacteristics()
}
];
return services;
}
};
module.exports.platform = TelldusPlatform;
module.exports.accessory = TelldusAccessory;
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var TellduAPI = require("telldus-live"); var TellduAPI = require("telldus-live");
function TelldusLivePlatform(log, config) { function TelldusLivePlatform(log, config) {
+1 -1
View File
@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var wink = require('wink-js'); var wink = require('wink-js');
var model = { var model = {
+162 -95
View File
@@ -1,5 +1,10 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var inherits = require('util').inherits;
var debug = require('debug')('YamahaAVR');
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var Yamaha = require('yamaha-nodejs'); var Yamaha = require('yamaha-nodejs');
var Q = require('q');
var mdns = require('mdns'); var mdns = require('mdns');
//workaround for raspberry pi //workaround for raspberry pi
var sequence = [ var sequence = [
@@ -12,10 +17,54 @@ function YamahaAVRPlatform(log, config){
this.log = log; this.log = log;
this.config = config; this.config = config;
this.playVolume = config["play_volume"]; this.playVolume = config["play_volume"];
this.minVolume = config["min_volume"] || -50.0;
this.maxVolume = config["max_volume"] || -20.0;
this.gapVolume = this.maxVolume - this.minVolume;
this.setMainInputTo = config["setMainInputTo"]; this.setMainInputTo = config["setMainInputTo"];
this.expectedDevices = config["expected_devices"] || 100;
this.discoveryTimeout = config["discovery_timeout"] || 30;
this.manualAddresses = config["manual_addresses"] || {};
this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence}); this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence});
} }
// Custom Characteristics and service...
YamahaAVRPlatform.AudioVolume = function() {
Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377');
this.setProps({
format: Characteristic.Formats.UINT8,
unit: Characteristic.Units.PERCENTAGE,
maxValue: 100,
minValue: 0,
minStep: 1,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
inherits(YamahaAVRPlatform.AudioVolume, Characteristic);
YamahaAVRPlatform.Muting = function() {
Characteristic.call(this, 'Muting', '00001002-0000-1000-8000-135D67EC4377');
this.setProps({
format: Characteristic.Formats.UINT8,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
inherits(YamahaAVRPlatform.Muting, Characteristic);
YamahaAVRPlatform.AudioDeviceService = function(displayName, subtype) {
Service.call(this, displayName, '00000001-0000-1000-8000-135D67EC4377', subtype);
// Required Characteristics
this.addCharacteristic(YamahaAVRPlatform.AudioVolume);
// Optional Characteristics
this.addOptionalCharacteristic(YamahaAVRPlatform.Muting);
};
inherits(YamahaAVRPlatform.AudioDeviceService, Service);
YamahaAVRPlatform.prototype = { YamahaAVRPlatform.prototype = {
accessories: function(callback) { accessories: function(callback) {
this.log("Getting Yamaha AVR devices."); this.log("Getting Yamaha AVR devices.");
@@ -24,38 +73,86 @@ YamahaAVRPlatform.prototype = {
var browser = this.browser; var browser = this.browser;
browser.stop(); browser.stop();
browser.removeAllListeners('serviceUp'); // cleanup listeners browser.removeAllListeners('serviceUp'); // cleanup listeners
var accessories = [];
var timer, timeElapsed = 0, checkCyclePeriod = 5000;
browser.on('serviceUp', function(service){ // Hmm... seems we need to prevent double-listing via manual and Bonjour...
var sysIds = {};
var setupFromService = function(service){
var name = service.name; var name = service.name;
//console.log('Found HTTP service "' + name + '"'); //console.log('Found HTTP service "' + name + '"');
// We can't tell just from mdns if this is an AVR... // We can't tell just from mdns if this is an AVR...
if (service.port != 80) return; // yamaha-nodejs assumes this, so finding one on another port wouldn't do any good anyway. if (service.port != 80) return; // yamaha-nodejs assumes this, so finding one on another port wouldn't do any good anyway.
var yamaha = new Yamaha(service.host); var yamaha = new Yamaha(service.host);
yamaha.getSystemConfig().then(function(sysConfig){ yamaha.getSystemConfig().then(
function(sysConfig){
var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0];
var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0];
that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); if(sysIds[sysId]){
var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig); this.log("WARN: Got multiple systems with ID " + sysId + "! Omitting duplicate!");
callback([accessory]);
}, function(err){
return; return;
}) }
sysIds[sysId] = true;
this.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\"");
var accessory = new YamahaAVRAccessory(this.log, this.config, name, yamaha, sysConfig);
accessories.push(accessory);
if(accessories.length >= this.expectedDevices)
timeoutFunction(); // We're done, call the timeout function now.
}.bind(this)
);
}.bind(this);
// process manually specified devices...
for(var key in this.manualAddresses){
if(!this.manualAddresses.hasOwnProperty(key)) continue;
setupFromService({
name: key,
host: this.manualAddresses[key],
port: 80
}); });
}
browser.on('serviceUp', setupFromService);
browser.start(); browser.start();
// The callback can only be called once...so we'll have to find as many as we can
// in a fixed time and then call them in.
var timeoutFunction = function(){
if(accessories.length >= that.expectedDevices){
clearTimeout(timer);
} else {
timeElapsed += checkCyclePeriod;
if(timeElapsed > that.discoveryTimeout * 1000){
that.log("Waited " + that.discoveryTimeout + " seconds, stopping discovery.");
} else {
timer = setTimeout(timeoutFunction, checkCyclePeriod);
return;
}
}
browser.stop();
browser.removeAllListeners('serviceUp');
that.log("Discovery finished, found " + accessories.length + " Yamaha AVR devices.");
callback(accessories);
};
timer = setTimeout(timeoutFunction, checkCyclePeriod);
} }
}; };
function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) { function YamahaAVRAccessory(log, config, name, yamaha, sysConfig) {
this.log = log; this.log = log;
this.config = config; this.config = config;
this.mdnsService = mdnsService;
this.yamaha = yamaha; this.yamaha = yamaha;
this.sysConfig = sysConfig; this.sysConfig = sysConfig;
this.name = mdnsService.name; this.nameSuffix = config["name_suffix"] || " Speakers";
this.serviceName = mdnsService.name + " Speakers"; this.name = name;
this.serviceName = name + this.nameSuffix;
this.setMainInputTo = config["setMainInputTo"]; this.setMainInputTo = config["setMainInputTo"];
this.playVolume = this.config["play_volume"]; this.playVolume = this.config["play_volume"];
this.minVolume = config["min_volume"] || -50.0;
this.maxVolume = config["max_volume"] || -20.0;
this.gapVolume = this.maxVolume - this.minVolume;
} }
YamahaAVRAccessory.prototype = { YamahaAVRAccessory.prototype = {
@@ -66,104 +163,74 @@ YamahaAVRAccessory.prototype = {
if (playing) { if (playing) {
yamaha.powerOn().then(function(){ return yamaha.powerOn().then(function(){
if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10); if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10);
else return { then: function(f, r){ f(); } }; else return Q();
}).then(function(){ }).then(function(){
if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo); if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo);
else return { then: function(f, r){ f(); } }; else return Q();
}).then(function(){ }).then(function(){
if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver( if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver(
'<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>' '<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>'
); );
else return { then: function(f, r){ f(); } }; else return Q();
//else return Promise.fulfilled(undefined);
}); });
} }
else { else {
yamaha.powerOff(); return yamaha.powerOff();
} }
}, },
getServices: function() { getServices: function() {
var that = this; var that = this;
return [{ var informationService = new Service.AccessoryInformation();
sType: types.ACCESSORY_INFORMATION_STYPE, var yamaha = this.yamaha;
characteristics: [{
cType: types.NAME_CTYPE, informationService
onUpdate: null, .setCharacteristic(Characteristic.Name, this.name)
perms: ["pr"], .setCharacteristic(Characteristic.Manufacturer, "Yamaha")
format: "string", .setCharacteristic(Characteristic.Model, this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0])
initialValue: this.name, .setCharacteristic(Characteristic.SerialNumber, this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]);
supportEvents: false,
supportBonjour: false, var switchService = new Service.Switch("Power State");
manfDescription: "Name of the accessory", switchService.getCharacteristic(Characteristic.On)
designedMaxLength: 255 .on('get', function(callback, context){
},{ yamaha.isOn().then(function(result){
cType: types.MANUFACTURER_CTYPE, callback(false, result);
onUpdate: null, }.bind(this));
perms: ["pr"], }.bind(this))
format: "string", .on('set', function(powerOn, callback){
initialValue: "Yamaha", this.setPlaying(powerOn).then(function(){
supportEvents: false, callback(false, powerOn);
supportBonjour: false, }, function(error){
manfDescription: "Manufacturer", callback(error, !powerOn); //TODO: Actually determine and send real new status.
designedMaxLength: 255 });
},{ }.bind(this));
cType: types.MODEL_CTYPE,
onUpdate: null, var audioDeviceService = new YamahaAVRPlatform.AudioDeviceService("Audio Functions");
perms: ["pr"], audioDeviceService.getCharacteristic(YamahaAVRPlatform.AudioVolume)
format: "string", .on('get', function(callback, context){
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0], yamaha.getBasicInfo().done(function(basicInfo){
supportEvents: false, var v = basicInfo.getVolume()/10.0;
supportBonjour: false, var p = 100 * ((v - that.minVolume) / that.gapVolume);
manfDescription: "Model", p = p < 0 ? 0 : p > 100 ? 100 : Math.round(p);
designedMaxLength: 255 debug("Got volume percent of " + p + "%");
},{ callback(false, p);
cType: types.SERIAL_NUMBER_CTYPE, });
onUpdate: null, })
perms: ["pr"], .on('set', function(p, callback){
format: "string", var v = ((p / 100) * that.gapVolume) + that.minVolume;
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0], v = Math.round(v*10.0);
supportEvents: false, debug("Setting volume to " + v);
supportBonjour: false, yamaha.setVolumeTo(v).then(function(){
manfDescription: "SN", callback(false, p);
designedMaxLength: 255 });
},{ })
cType: types.IDENTIFY_CTYPE, .getValue(null, null); // force an asynchronous get
onUpdate: null,
perms: ["pw"],
format: "bool", return [informationService, switchService, audioDeviceService];
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.serviceName,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPlaying(value); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the playback state of the Yamaha AV Receiver",
designedMaxLength: 1
}]
}];
} }
}; };
+317 -54
View File
@@ -1,7 +1,7 @@
var debug = require('debug')('ZWayServer'); var debug = require('debug')('ZWayServer');
var Service = require("HAP-NodeJS").Service; var Service = require("hap-nodejs").Service;
var Characteristic = require("HAP-NodeJS").Characteristic; var Characteristic = require("hap-nodejs").Characteristic;
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("hap-nodejs/accessories/types.js");
var request = require("request"); var request = require("request");
var tough = require('tough-cookie'); var tough = require('tough-cookie');
var Q = require("q"); var Q = require("q");
@@ -11,6 +11,7 @@ function ZWayServerPlatform(log, config){
this.url = config["url"]; this.url = config["url"];
this.login = config["login"]; this.login = config["login"];
this.password = config["password"]; this.password = config["password"];
this.opt_in = config["opt_in"];
this.name_overrides = config["name_overrides"]; this.name_overrides = config["name_overrides"];
this.batteryLow = config["battery_low_level"] || 15; this.batteryLow = config["battery_low_level"] || 15;
this.pollInterval = config["poll_interval"] || 2; this.pollInterval = config["poll_interval"] || 2;
@@ -82,7 +83,20 @@ ZWayServerPlatform.prototype = {
return deferred.promise; return deferred.promise;
} }
, ,
getTagValue: function(vdev, tagStem){
if(!(vdev.tags && vdev.tags.length > 0)) return false;
var tagStem = "Homebridge." + tagStem;
if(vdev.tags.indexOf(tagStem) >= 0) return true;
var tags = vdev.tags, l = tags.length, tag;
for(var i = 0; i < l; i++){
tag = tags[i];
if(tag.indexOf(tagStem + ":") === 0){
return tag.substr(tagStem.length + 1);
}
}
return false;
}
,
accessories: function(callback) { accessories: function(callback) {
debug("Fetching Z-Way devices..."); debug("Fetching Z-Way devices...");
@@ -90,10 +104,10 @@ ZWayServerPlatform.prototype = {
//Note: Order matters! //Note: Order matters!
var primaryDeviceClasses = [ var primaryDeviceClasses = [
"thermostat", "thermostat",
"sensorMultilevel.Temperature",
"switchMultilevel", "switchMultilevel",
"switchBinary", "switchBinary",
"sensorBinary.Door/Window" "sensorBinary.Door/Window",
"sensorMultilevel.Temperature"
]; ];
var that = this; var that = this;
@@ -109,14 +123,49 @@ ZWayServerPlatform.prototype = {
var groupedDevices = {}; var groupedDevices = {};
for(var i = 0; i < devices.length; i++){ for(var i = 0; i < devices.length; i++){
var vdev = devices[i]; var vdev = devices[i];
if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; } if(this.getTagValue("Skip")) { debug("Tag says skip!"); continue; }
var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); if(this.opt_in && !this.getTagValue(vdev, "Include")) continue;
var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, primary: undefined});
gd.devices.push(vdev); var gdid = this.getTagValue(vdev, "Accessory.Id");
gd.types[ZWayServerPlatform.getVDevTypeKey(vdev)] = gd.devices.length - 1; if(!gdid){
gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2');
} }
//TODO: Make a second pass, re-splitting any devices that don't make sense together
var gd = groupedDevices[gdid] || (groupedDevices[gdid] = { devices: [], types: {}, extras: {}, primary: undefined, cxmap: {} });
gd.devices.push(vdev);
var vdevIndex = gd.devices.length - 1;
var tk = ZWayServerPlatform.getVDevTypeKey(vdev);
// If this is explicitly set as primary, set it now...
if(this.getTagValue(vdev, "IsPrimary")){
// everybody out of the way! Can't be in "extras" if you're the primary...
if(gd.types[tk] !== undefined){
gd.extras[tk] = gd.extras[tk] || [];
gd.extras[tk].push(gd.types[tk]);
delete gd.types[tk]; // clear the way for this one to be set here below...
}
gd.primary = vdevIndex;
//gd.types[tk] = gd.primary;
}
if(gd.types[tk] === undefined){
gd.types[tk] = vdevIndex;
} else {
gd.extras[tk] = gd.extras[tk] || [];
gd.extras[tk].push(vdevIndex);
}
if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = vdevIndex; // also include the deviceType only as a possibility
// Create a map entry when Homebridge.Characteristic.Type is set...
var ctype = this.getTagValue(vdev, "Characteristic.Type");
if(ctype && Characteristic[ctype]){
var cx = new Characteristic[ctype]();
gd.cxmap[cx.UUID] = vdevIndex;
}
}
for(var gdid in groupedDevices) { for(var gdid in groupedDevices) {
if(!groupedDevices.hasOwnProperty(gdid)) continue; if(!groupedDevices.hasOwnProperty(gdid)) continue;
@@ -128,12 +177,17 @@ ZWayServerPlatform.prototype = {
} }
var accessory = null; var accessory = null;
for(var ti = 0; ti < primaryDeviceClasses.length; ti++){ if(gd.primary !== undefined){
var pd = gd.devices[gd.primary];
var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id;
accessory = new ZWayServerAccessory(name, gd, that);
}
else for(var ti = 0; ti < primaryDeviceClasses.length; ti++){
if(gd.types[primaryDeviceClasses[ti]] !== undefined){ if(gd.types[primaryDeviceClasses[ti]] !== undefined){
gd.primary = gd.types[primaryDeviceClasses[ti]]; gd.primary = gd.types[primaryDeviceClasses[ti]];
var pd = gd.devices[gd.primary]; var pd = gd.devices[gd.primary];
var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id;
debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary."); //debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary.");
accessory = new ZWayServerAccessory(name, gd, that); accessory = new ZWayServerAccessory(name, gd, that);
break; break;
} }
@@ -145,7 +199,6 @@ ZWayServerPlatform.prototype = {
foundAccessories.push(accessory); foundAccessories.push(accessory);
} }
//foundAccessories = foundAccessories.slice(0, 10); // Limit to a few devices for testing...
callback(foundAccessories); callback(foundAccessories);
// Start the polling process... // Start the polling process...
@@ -172,6 +225,11 @@ ZWayServerPlatform.prototype = {
if(this.cxVDevMap[upd.id]){ if(this.cxVDevMap[upd.id]){
var vdev = this.vDevStore[upd.id]; var vdev = this.vDevStore[upd.id];
vdev.metrics.level = upd.metrics.level; vdev.metrics.level = upd.metrics.level;
if(upd.metrics.color){
vdev.metrics.r = upd.metrics.r;
vdev.metrics.g = upd.metrics.g;
vdev.metrics.b = upd.metrics.b;
}
vdev.updateTime = upd.updateTime; vdev.updateTime = upd.updateTime;
var cxs = this.cxVDevMap[upd.id]; var cxs = this.cxVDevMap[upd.id];
for(var j = 0; j < cxs.length; j++){ for(var j = 0; j < cxs.length; j++){
@@ -222,31 +280,98 @@ ZWayServerAccessory.prototype = {
}); });
}, },
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;
var max, min, d, h, s, v;
min = Math.min(r, Math.min(g, b));
max = Math.max(r, Math.max(g, b));
if (min === max) {
// shade of gray
return {h: 0, s: 0, v: r * 100};
}
var d = (r === min) ? g - b : ((b === min) ? r - g : b - r);
h = (r === min) ? 3 : ((b === min) ? 1 : 5);
h = 60 * (h - d/(max - min));
s = (max - min) / max;
v = max;
return {"h": h, "s": s * 100, "v": v * 100};
}
,
hsv2rgb: function(obj) {
// H: 0-360; S,V: 0-100; RGB: 0-255
var r, g, b;
var sfrac = obj.s / 100;
var vfrac = obj.v / 100;
if(sfrac === 0){
var vbyte = Math.round(vfrac*255);
return { r: vbyte, g: vbyte, b: vbyte };
}
var hdb60 = (obj.h % 360) / 60;
var sector = Math.floor(hdb60);
var fpart = hdb60 - sector;
var c = vfrac * (1 - sfrac);
var x1 = vfrac * (1 - sfrac * fpart);
var x2 = vfrac * (1 - sfrac * (1 - fpart));
switch(sector){
case 0:
r = vfrac; g = x2; b = c; break;
case 1:
r = x1; g = vfrac; b = c; break;
case 2:
r = c; g = vfrac; b = x2; break;
case 3:
r = c; g = x1; b = vfrac; break;
case 4:
r = x2; g = c; b = vfrac; break;
case 5:
default:
r = vfrac; g = c; b = x1; break;
}
return { "r": Math.round(255 * r), "g": Math.round(255 * g), "b": Math.round(255 * b) };
}
,
getVDevServices: function(vdev){ getVDevServices: function(vdev){
var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev);
var services = [], service; var services = [], service;
switch (typeKey) { switch (typeKey) {
case "thermostat": case "thermostat":
services.push(new Service.Thermostat(vdev.metrics.title)); services.push(new Service.Thermostat(vdev.metrics.title, vdev.id));
break;
case "sensorMultilevel.Temperature":
services.push(new Service.TemperatureSensor(vdev.metrics.title));
break;
case "switchMultilevel":
services.push(new Service.Lightbulb(vdev.metrics.title));
break;
case "battery.Battery":
services.push(new Service.BatteryService(vdev.metrics.title));
break; break;
case "switchBinary": case "switchBinary":
services.push(new Service.Switch(vdev.metrics.title)); services.push(new Service.Switch(vdev.metrics.title, vdev.id));
break;
case "switchRGBW":
case "switchMultilevel":
if(this.platform.getTagValue(vdev, "Service.Type") === "Switch"){
services.push(new Service.Switch(vdev.metrics.title, vdev.id));
} else {
services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id));
}
break; break;
case "sensorBinary.Door/Window": case "sensorBinary.Door/Window":
services.push(new Service.GarageDoorOpener(vdev.metrics.title)); services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id));
break;
case "sensorMultilevel.Temperature":
services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id));
break;
case "battery.Battery":
services.push(new Service.BatteryService(vdev.metrics.title, vdev.id));
break; break;
case "sensorMultilevel.Luminiscence": case "sensorMultilevel.Luminiscence":
services.push(new Service.LightSensor(vdev.metrics.title)); services.push(new Service.LightSensor(vdev.metrics.title, vdev.id));
break; break;
case "sensorBinary":
var stype = this.platform.getTagValue(vdev, "Service.Type");
if(stype === "MotionSensor"){
services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id));
}
} }
var validServices =[]; var validServices =[];
@@ -267,11 +392,19 @@ ZWayServerAccessory.prototype = {
} }
, ,
getVDevForCharacteristic: function(cx, vdevPreferred){ getVDevForCharacteristic: function(cx, vdevPreferred){
// If we know which vdev should be used for this Characteristic, we're done!
if(this.devDesc.cxmap[cx.UUID] !== undefined){
return this.devDesc.devices[this.devDesc.cxmap[cx.UUID]];
}
var map = this.uuidToTypeKeyMap; var map = this.uuidToTypeKeyMap;
if(!map){ if(!map){
this.uuidToTypeKeyMap = map = {}; this.uuidToTypeKeyMap = map = {};
map[(new Characteristic.On).UUID] = ["switchBinary","switchMultilevel"]; map[(new Characteristic.On).UUID] = ["switchBinary","switchMultilevel"];
map[(new Characteristic.Brightness).UUID] = ["switchMultilevel"]; map[(new Characteristic.Brightness).UUID] = ["switchMultilevel"];
map[(new Characteristic.Hue).UUID] = ["switchRGBW"];
map[(new Characteristic.Saturation).UUID] = ["switchRGBW"];
map[(new Characteristic.CurrentTemperature).UUID] = ["sensorMultilevel.Temperature","thermostat"]; map[(new Characteristic.CurrentTemperature).UUID] = ["sensorMultilevel.Temperature","thermostat"];
map[(new Characteristic.TargetTemperature).UUID] = ["thermostat"]; map[(new Characteristic.TargetTemperature).UUID] = ["thermostat"];
map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result
@@ -309,8 +442,8 @@ ZWayServerAccessory.prototype = {
return null; return null;
} }
, ,
configureCharacteristic: function(cx, vdev){ configureCharacteristic: function(cx, vdev, service){
var that = this; var accessory = this;
// Add this combination to the maps... // Add this combination to the maps...
if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = []; if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = [];
@@ -324,12 +457,17 @@ ZWayServerAccessory.prototype = {
cx.value = cx.zway_getValueFromVDev(vdev); cx.value = cx.zway_getValueFromVDev(vdev);
cx.on('get', function(callback, context){ cx.on('get', function(callback, context){
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, that.name); callback(false, accessory.name);
}); });
cx.writable = false;
return cx; return cx;
} }
// We don't want to override "Name"'s name...so we just move this below that block.
var descOverride = this.platform.getTagValue(vdev, "Characteristic.Description");
if(descOverride){
cx.displayName = descOverride;
}
if(cx instanceof Characteristic.On){ if(cx instanceof Characteristic.On){
cx.zway_getValueFromVDev = function(vdev){ cx.zway_getValueFromVDev = function(vdev){
var val = false; var val = false;
@@ -381,6 +519,64 @@ ZWayServerAccessory.prototype = {
return cx; return cx;
} }
if(cx instanceof Characteristic.Hue){
cx.zway_getValueFromVDev = function(vdev){
debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).h + " for hue.");
return accessory.rgb2hsv(vdev.metrics.color).h;
};
cx.value = cx.zway_getValueFromVDev(vdev);
cx.on('get', function(callback, context){
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
this.getVDev(vdev).then(function(result){
debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + ".");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('set', function(hue, callback){
var scx = service.getCharacteristic(Characteristic.Saturation);
var vcx = service.getCharacteristic(Characteristic.Brightness);
if(!scx || !vcx){
debug("Hue without Saturation and Brightness is not supported! Cannot set value!")
callback(true, cx.value);
}
var rgb = this.hsv2rgb({ h: hue, s: scx.value, v: vcx.value });
this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){
callback();
});
}.bind(this));
return cx;
}
if(cx instanceof Characteristic.Saturation){
cx.zway_getValueFromVDev = function(vdev){
debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).s + " for saturation.");
return accessory.rgb2hsv(vdev.metrics.color).s;
};
cx.value = cx.zway_getValueFromVDev(vdev);
cx.on('get', function(callback, context){
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
this.getVDev(vdev).then(function(result){
debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + ".");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('set', function(saturation, callback){
var hcx = service.getCharacteristic(Characteristic.Hue);
var vcx = service.getCharacteristic(Characteristic.Brightness);
if(!hcx || !vcx){
debug("Saturation without Hue and Brightness is not supported! Cannot set value!")
callback(true, cx.value);
}
var rgb = this.hsv2rgb({ h: hcx.value, s: saturation, v: vcx.value });
this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){
callback();
});
}.bind(this));
return cx;
}
if(cx instanceof Characteristic.CurrentTemperature){ if(cx instanceof Characteristic.CurrentTemperature){
cx.zway_getValueFromVDev = function(vdev){ cx.zway_getValueFromVDev = function(vdev){
return vdev.metrics.level; return vdev.metrics.level;
@@ -393,8 +589,10 @@ ZWayServerAccessory.prototype = {
callback(false, cx.zway_getValueFromVDev(result.data)); callback(false, cx.zway_getValueFromVDev(result.data));
}); });
}.bind(this)); }.bind(this));
cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40; cx.setProps({
cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999; minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40,
maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999
});
return cx; return cx;
} }
@@ -416,8 +614,10 @@ ZWayServerAccessory.prototype = {
callback(); callback();
}); });
}.bind(this)); }.bind(this));
cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5; cx.setProps({
cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40; minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5,
maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40
});
return cx; return cx;
} }
@@ -431,7 +631,9 @@ ZWayServerAccessory.prototype = {
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, Characteristic.TemperatureDisplayUnits.CELSIUS); callback(false, Characteristic.TemperatureDisplayUnits.CELSIUS);
}); });
cx.writable = false; cx.setProps({
perms: [Characteristic.Perms.READ]
});
return cx; return cx;
} }
@@ -459,7 +661,6 @@ ZWayServerAccessory.prototype = {
callback(false, Characteristic.TargetHeatingCoolingState.HEAT); 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. // 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.writable = true;
cx.on('set', function(newValue, callback){ cx.on('set', function(newValue, callback){
debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!") debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!")
callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT); callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT);
@@ -494,8 +695,9 @@ ZWayServerAccessory.prototype = {
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, Characteristic.TargetDoorState.CLOSED); callback(false, Characteristic.TargetDoorState.CLOSED);
}); });
//cx.readable = false; cx.setProps({
cx.writable = false; perms: [Characteristic.Perms.READ]
});
} }
if(cx instanceof Characteristic.ObstructionDetected){ if(cx instanceof Characteristic.ObstructionDetected){
@@ -508,8 +710,6 @@ ZWayServerAccessory.prototype = {
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, false); callback(false, false);
}); });
//cx.readable = false;
cx.writable = false;
} }
if(cx instanceof Characteristic.BatteryLevel){ if(cx instanceof Characteristic.BatteryLevel){
@@ -528,7 +728,7 @@ ZWayServerAccessory.prototype = {
if(cx instanceof Characteristic.StatusLowBattery){ if(cx instanceof Characteristic.StatusLowBattery){
cx.zway_getValueFromVDev = function(vdev){ cx.zway_getValueFromVDev = function(vdev){
return vdev.metrics.level <= that.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; return vdev.metrics.level <= accessory.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
}; };
cx.value = cx.zway_getValueFromVDev(vdev); cx.value = cx.zway_getValueFromVDev(vdev);
cx.on('get', function(callback, context){ cx.on('get', function(callback, context){
@@ -550,8 +750,6 @@ ZWayServerAccessory.prototype = {
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, Characteristic.ChargingState.NOT_CHARGING); callback(false, Characteristic.ChargingState.NOT_CHARGING);
}); });
//cx.readable = false;
cx.writable = false;
} }
if(cx instanceof Characteristic.CurrentAmbientLightLevel){ if(cx instanceof Characteristic.CurrentAmbientLightLevel){
@@ -560,8 +758,8 @@ ZWayServerAccessory.prototype = {
// Completely unscientific guess, based on test-fit data and Wikipedia real-world lux values. // Completely unscientific guess, based on test-fit data and Wikipedia real-world lux values.
// This will probably change! // This will probably change!
var lux = 0.0005 * (vdev.metrics.level^3.6); var lux = 0.0005 * (vdev.metrics.level^3.6);
if(lux < cx.minimumValue) return cx.minimumValue; // Bounds checking now done upstream!
if(lux > cx.maximumValue) return cx.maximumValue; //if(lux < cx.minimumValue) return cx.minimumValue; if(lux > cx.maximumValue) return cx.maximumValue;
return lux; return lux;
} else { } else {
return vdev.metrics.level; return vdev.metrics.level;
@@ -580,6 +778,43 @@ ZWayServerAccessory.prototype = {
}); });
return cx; return cx;
} }
if(cx instanceof Characteristic.MotionDetected){
cx.zway_getValueFromVDev = function(vdev){
return vdev.metrics.level === "off" ? false : true;
};
cx.value = cx.zway_getValueFromVDev(vdev);
cx.on('get', function(callback, context){
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
this.getVDev(vdev).then(function(result){
debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + ".");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('change', function(ev){
debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue);
});
return cx;
}
if(cx instanceof Characteristic.StatusTampered){
cx.zway_getValueFromVDev = function(vdev){
return vdev.metrics.level === "off" ? Characteristic.StatusTampered.NOT_TAMPERED : Characteristic.StatusTampered.TAMPERED;
};
cx.value = cx.zway_getValueFromVDev(vdev);
cx.on('get', function(callback, context){
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
this.getVDev(vdev).then(function(result){
debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + ".");
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.on('change', function(ev){
debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue);
});
return cx;
}
} }
, ,
configureService: function(service, vdev){ configureService: function(service, vdev){
@@ -591,32 +826,60 @@ ZWayServerAccessory.prototype = {
success = false; success = false;
debug("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!"); debug("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!");
} }
cx = this.configureCharacteristic(cx, vdev); cx = this.configureCharacteristic(cx, vdev, service);
} }
for(var i = 0; i < service.optionalCharacteristics.length; i++){ for(var i = 0; i < service.optionalCharacteristics.length; i++){
var cx = service.optionalCharacteristics[i]; var cx = service.optionalCharacteristics[i];
var vdev = this.getVDevForCharacteristic(cx); var vdev = this.getVDevForCharacteristic(cx, vdev);
if(!vdev) continue; if(!vdev) continue;
cx = this.configureCharacteristic(cx, vdev);
//NOTE: Questionable logic, but if the vdev has already been used for the same
// characteristic type elsewhere, lets not duplicate it just for the sake of an
// optional characteristic. This eliminates the problem with RGB+W+W bulbs
// having the HSV controls shown again, but might have unintended consequences...
var othercx, othercxs = this.platform.cxVDevMap[vdev.id];
if(othercxs) for(var j = 0; j < othercxs.length; j++) if(othercxs[j].UUID === cx.UUID) othercx = othercxs[j];
if(othercx)
continue;
cx = this.configureCharacteristic(cx, vdev, service);
try {
if(cx) service.addCharacteristic(cx); if(cx) service.addCharacteristic(cx);
} }
catch (ex) {
debug('Adding Characteristic "' + cx.displayName + '" failed with message "' + ex.message + '". This may be expected.');
}
}
return success; return success;
} }
, ,
getServices: function() { getServices: function() {
var that = this; var that = this;
var vdevPrimary = this.devDesc.devices[this.devDesc.primary];
var accId = this.platform.getTagValue(vdevPrimary, "Accessory.Id");
if(!accId){
accId = "VDev-" + vdevPrimary.h; //FIXME: Is this valid?
}
var informationService = new Service.AccessoryInformation(); var informationService = new Service.AccessoryInformation();
informationService informationService
.setCharacteristic(Characteristic.Name, this.name) .setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me") .setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me")
.setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)") .setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)")
.setCharacteristic(Characteristic.SerialNumber, "VDev-" + this.devDesc.devices[this.devDesc.primary].h) //FIXME: Is this valid?); .setCharacteristic(Characteristic.SerialNumber, accId);
var services = [informationService]; var services = [informationService];
services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.primary])); services = services.concat(this.getVDevServices(vdevPrimary));
// 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]];
var xservice = this.getVDevServices(xvdev);
services = services.concat(xservice);
}
if(this.platform.splitServices){ if(this.platform.splitServices){
if(this.devDesc.types["battery.Battery"]){ if(this.devDesc.types["battery.Battery"]){
@@ -655,7 +918,7 @@ ZWayServerAccessory.prototype = {
extraCxs = []; // to wipe out any already setup cxs. extraCxs = []; // to wipe out any already setup cxs.
break; break;
} }
this.configureCharacteristic(cx, vdev2); this.configureCharacteristic(cx, vdev2, service);
extraCxs.push(cx); extraCxs.push(cx);
} }
for(var j = 0; j < extraCxs.length; j++) for(var j = 0; j < extraCxs.length; j++)