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

View File

@@ -52,14 +52,19 @@ You'll also need some patience, as Siri can be very strict about sentence struct
# 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
$ cd homebridge
$ 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:
$ cd homebridge

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 CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8";

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");
function CarwingsAccessory(log, config) {

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");
function ElkM1Accessory(log, config) {

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var chokidar = require("chokidar");
var debug = require("debug")("FileSensorAccessory");
var crypto = require("crypto");

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var SerialPort = require("serialport").SerialPort;
module.exports = {

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");
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() {
var that = this;
return [{
@@ -101,6 +125,7 @@ HomeMatic.prototype = {
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPowerState(value); },
onRead: function(callback) { that.getPowerState(callback); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,

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;

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;

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {

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];
}
};

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];
}
};

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 Color = require('color');

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");
// This seems to be the "id" of the official LiftMaster iOS app

View File

@@ -1,5 +1,5 @@
var Service = require('HAP-NodeJS').Service;
var Characteristic = require('HAP-NodeJS').Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {
@@ -11,6 +11,17 @@ function LockitronAccessory(log, config) {
this.name = config["name"];
this.accessToken = config["api_token"];
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) {
@@ -36,7 +47,7 @@ LockitronAccessory.prototype.getState = function(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);
@@ -47,6 +58,14 @@ LockitronAccessory.prototype.setState = function(state, callback) {
if (!err && response.statusCode == 200) {
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
}
else {
@@ -57,17 +76,5 @@ LockitronAccessory.prototype.setState = function(state, callback) {
},
LockitronAccessory.prototype.getServices = function() {
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];
return [this.service];
}

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");
function TeslaAccessory(log, config) {

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var wemo = require('wemo');
module.exports = {
@@ -144,6 +144,16 @@ WeMoAccessory.prototype.getServices = function() {
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") {
var motionSensorService = new Service.MotionSensor(this.name);

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");
function X10(log, config) {

View File

@@ -1,6 +1,6 @@
var iControl = require('node-icontrol').iControl;
var Service = require('HAP-NodeJS').Service;
var Characteristic = require('HAP-NodeJS').Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
module.exports = {
accessory: iControlAccessory

View File

@@ -14,16 +14,23 @@ New 2015-09-18:
New 2015-09-19:
- GarageDoorOpener 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 Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var knxd = require("eibd");
var knxd_registerGA = require('../platforms/KNX.js').registerGA;
var knxd_startMonitor = require('../platforms/KNX.js').startMonitor;
var milliTimeout = 300; // used to block responses while swiping
var colorOn = "\x1b[30;47m";
var colorOff = "\x1b[0m";
function KNXDevice(log, config) {
this.log = log;
@@ -33,6 +40,9 @@ function KNXDevice(log, config) {
if (config.name) {
this.name = config.name;
}
if (config.uuid_base) {
this.uuid_base = config.uuid_base;
}
if (config.knxd_ip){
this.knxd_ip = config.knxd_ip;
} else {
@@ -115,6 +125,7 @@ KNXDevice.prototype = {
if (!groupAddress) {
return null;
}
this.log("[knxdevice:knxread] preparing knx request for "+groupAddress);
var knxdConnection = new knxd.Connection();
// 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() {
@@ -130,7 +141,7 @@ KNXDevice.prototype = {
if (err) {
this.log("[ERROR] knxread:sendAPDU: " + err);
} else {
this.log("knx request sent for "+groupAddress);
this.log("[knxdevice:knxread] knx request sent for "+groupAddress);
}
}.bind(this));
}
@@ -143,12 +154,12 @@ KNXDevice.prototype = {
// handle multiple addresses
for (var i = 0; i < groupAddresses.length; i++) {
if (groupAddresses[i]) { // do not bind empty addresses
this.knxread (groupAddresses[i]);
this.knxread (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0]); // clean address
}
}
} else {
// 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
knxregister_bool: 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);
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 ? 1 : 0, undefined, 'fromKNXBus');
}.bind(this));
},
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');
characteristic.setValue(val ? (reverse ? 0:1) : (reverse ? 1:0), undefined, 'fromKNXBus');
}.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
knxregister_percent: function(addresses, characteristic) {
this.log("knx registering PERCENT " + 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);
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);
if (type !== "DPT5") {
this.log("[ERROR] Received value cannot be a percentage value");
} else {
if (!characteristic.timeout) {
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
characteristic.setValue(Math.round(( reverse ? (255-val):val)/255*100), undefined, 'fromKNXBus');
}
}.bind(this));
},
// float
knxregister_float: function(addresses, characteristic) {
this.log("knx registering FLOAT " + 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);
var hk_value = Math.round(val*10)/10;
if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) {
// update for props refactor https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR50
var validValue = true;
var hk_value = 0.0;
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
} 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));
},
//integer
knxregister_int: function(addresses, characteristic) {
this.log("knx registering FLOAT " + 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);
if (val>=(characteristic.minimumValue || 0) && val<=(characteristic.maximumValue || 255)) {
characteristic.setValue(val, undefined, 'fromKNXBus');
this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering INT " + 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);
if (val>=(characteristic.props.minValue || 0) && val<=(characteristic.props.maxValue || 255)) {
characteristic.setValue(reverse ? (255-val):val, undefined, 'fromKNXBus');
} 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));
},
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){
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;
switch (val){
case 0:
@@ -261,9 +282,9 @@ KNXDevice.prototype = {
*/
// undefined, has to match!
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){
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');
}.bind(this));
},
@@ -276,71 +297,74 @@ KNXDevice.prototype = {
* }.bind(this));
*
*/
setBooleanState: function(value, callback, context, gaddress) {
setBooleanState: function(value, callback, context, gaddress, reverseflag) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
// this.log(gaddress + " event ping pong, exit!");
if (callback) {
callback();
}
} else {
var numericValue = 0;
var numericValue = reverseflag ? 1:0;
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);
}
},
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') {
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("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!");
// this.log(gaddress + "event ping pong, exit!");
if (callback) {
callback();
}
} else {
var numericValue = 0;
if (value) {
numericValue = Math.round(255*value/100); // convert 1..100 to 1..255 for KNX bus
value = ( value>=0 ? (value<=100 ? value:100):0 ); //ensure range 0..100
if (reverseflag) {
numericValue = 255 - Math.round(255*value/100); // convert 0..100 to 255..0 for KNX bus
} else {
numericValue = Math.round(255*value/100); // convert 0..100 to 0..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);
}
},
setInt: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log("event ping pong, exit!");
// this.log(gaddress + "event ping pong, exit!");
if (callback) {
callback();
}
} else {
var numericValue = 0;
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);
}
},
setFloat: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
// this.log(gaddress + " event ping pong, exit!");
if (callback) {
callback();
}
@@ -349,13 +373,13 @@ KNXDevice.prototype = {
if (value) {
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);
}
},
setHVACState: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
// this.log(gaddress + " event ping pong, exit!");
if (callback) {
callback();
}
@@ -378,7 +402,7 @@ KNXDevice.prototype = {
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);
}
@@ -387,33 +411,50 @@ KNXDevice.prototype = {
*
*/
identify: function(callback) {
this.log("Identify requested!");
this.log("["+ this.name +"]:Identify requested!");
callback(); // success
},
/** bindCharacteristic
* 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 setGA = "";
var setReverse = false;
if (myCharacteristic === undefined) {
throw new Error("unknown characteristics cannot be bound");
}
if (defaultValue) {
myCharacteristic.setValue(defaultValue);
}
if (config.Set) {
// 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) {
case "Bool":
myCharacteristic.on('set', function(value, callback, context) {
this.setBooleanState(value, callback, context, config.Set);
}.bind(this));
break;
case "BoolReverse":
myCharacteristic.on('set', function(value, callback, context) {
this.setBooleanReverseState(value, callback, context, config.Set);
this.setBooleanState(value, callback, context, setGA, setReverse); //NEW
}.bind(this));
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":
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;
}.bind(this));
break;
@@ -433,7 +474,7 @@ KNXDevice.prototype = {
}.bind(this));
break;
default: {
this.log("[ERROR] unknown type passed");
this.log(colorOn + "[ERROR] unknown type passed: [" + valueType+"]"+ colorOff);
throw new Error("[ERROR] unknown type passed");
}
}
@@ -445,9 +486,9 @@ KNXDevice.prototype = {
case "Bool":
this.knxregister_bool([config.Set].concat(config.Listen || []), myCharacteristic);
break;
case "BoolReverse":
this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic);
break;
// case "BoolReverse":
// this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic);
// break;
case "Percent":
this.knxregister_percent([config.Set].concat(config.Listen || []), myCharacteristic);
break;
@@ -461,10 +502,10 @@ KNXDevice.prototype = {
this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic);
break;
default:
this.log("[ERROR] unknown type passed");
throw new Error("[ERROR] unknown type passed");
this.log(colorOn+ "[ERROR] unknown type passed: ["+valueType+"]"+colorOff);
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 || []));
}
return myCharacteristic; // for chaining or whatsoever
@@ -494,30 +535,30 @@ KNXDevice.prototype = {
var myService = new Service.ContactSensor(config.name,config.name);
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);
} else if (config.ContactSensorStateContact1) {
this.log("ContactSensor ContactSensorStateContact1 characteristic enabled");
this.bindCharacteristic(myService, Characteristic.ContactSensorState, "BoolReverse", config.ContactSensorStateContact1);
this.log(colorOn+ "[ERROR] outdated type passed: [ContactSensorStateContact1]"+colorOff);
throw new Error("[ERROR] outdated type passed");
}
//optionals
if (config.StatusActive) {
this.log("ContactSensor StatusActive characteristic enabled");
this.log("["+ this.name +"]:ContactSensor StatusActive characteristic enabled");
myService.addCharacteristic(Characteristic.StatusActive);
this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive);
}
if (config.StatusFault) {
this.log("ContactSensor StatusFault characteristic enabled");
this.log("["+ this.name +"]:ContactSensor StatusFault characteristic enabled");
myService.addCharacteristic(Characteristic.StatusFault);
this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault);
}
if (config.StatusTampered) {
this.log("ContactSensor StatusTampered characteristic enabled");
this.log("["+ this.name +"]:ContactSensor StatusTampered characteristic enabled");
myService.addCharacteristic(Characteristic.StatusTampered);
this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered);
}
if (config.StatusLowBattery) {
this.log("ContactSensor StatusLowBattery characteristic enabled");
this.log("["+ this.name +"]:ContactSensor StatusLowBattery characteristic enabled");
myService.addCharacteristic(Characteristic.StatusLowBattery);
this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery);
}
@@ -555,27 +596,27 @@ KNXDevice.prototype = {
var myService = new Service.GarageDoorOpener(config.name,config.name);
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);
}
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).maximumValue=4; //
this.bindCharacteristic(myService, Characteristic.TargetDoorState, "Int", config.TargetDoorState);
}
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);
}
//optionals
if (config.LockCurrentState) {
this.log("GarageDoorOpener LockCurrentState characteristic enabled");
this.log("["+ this.name +"]:GarageDoorOpener LockCurrentState characteristic enabled");
myService.addCharacteristic(Characteristic.LockCurrentState);
this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Int", config.LockCurrentState);
}
if (config.LockTargetState) {
this.log("GarageDoorOpener LockTargetState characteristic enabled");
this.log("["+ this.name +"]:GarageDoorOpener LockTargetState characteristic enabled");
myService.addCharacteristic(Characteristic.LockTargetState);
this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState);
}
@@ -596,12 +637,12 @@ KNXDevice.prototype = {
var myService = new Service.Lightbulb(config.name,config.name);
// On (and Off)
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);
} // On characteristic
// Brightness if available
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
this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.Brightness);
}
@@ -623,7 +664,7 @@ KNXDevice.prototype = {
var myService = new Service.LightSensor(config.name,config.name);
// CurrentTemperature)
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);
}
return myService;
@@ -648,20 +689,20 @@ KNXDevice.prototype = {
// LockCurrentState
if (config.LockCurrentState) {
// 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);
} else if (config.LockCurrentStateSecured0) {
// for reverse contacts Secured = 0
this.log("LockMechanism LockCurrentState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.LockCurrentState, "BoolReverse", config.LockCurrentStateSecured0);
this.log(colorOn+ "[ERROR] outdated type passed: [LockCurrentStateSecured0]"+colorOff);
throw new Error("[ERROR] outdated type passed");
}
// 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);
} else if (config.LockTargetStateSecured0) {
this.log("LockMechanism LockTargetState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.LockTargetState, "BoolReverse", config.LockTargetStateSecured0);
this.log(colorOn+ "[ERROR] outdated type passed: [LockTargetStateSecured0]"+colorOff);
throw new Error("[ERROR] outdated type passed");
}
//iterate(myService);
@@ -683,27 +724,27 @@ KNXDevice.prototype = {
var myService = new Service.MotionSensor(config.name,config.name);
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);
}
//optionals
if (config.StatusActive) {
this.log("MotionSensor StatusActive characteristic enabled");
this.log("["+ this.name +"]:MotionSensor StatusActive characteristic enabled");
myService.addCharacteristic(Characteristic.StatusActive);
this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive);
}
if (config.StatusFault) {
this.log("MotionSensor StatusFault characteristic enabled");
this.log("["+ this.name +"]:MotionSensor StatusFault characteristic enabled");
myService.addCharacteristic(Characteristic.StatusFault);
this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault);
}
if (config.StatusTampered) {
this.log("MotionSensor StatusTampered characteristic enabled");
this.log("["+ this.name +"]:MotionSensor StatusTampered characteristic enabled");
myService.addCharacteristic(Characteristic.StatusTampered);
this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered);
}
if (config.StatusLowBattery) {
this.log("MotionSensor StatusLowBattery characteristic enabled");
this.log("["+ this.name +"]:MotionSensor StatusLowBattery characteristic enabled");
myService.addCharacteristic(Characteristic.StatusLowBattery);
this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery);
}
@@ -726,11 +767,11 @@ KNXDevice.prototype = {
var myService = new Service.Outlet(config.name,config.name);
// On (and Off)
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);
} // OutletInUse characteristic
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);
}
return myService;
@@ -748,7 +789,7 @@ KNXDevice.prototype = {
var myService = new Service.Switch(config.name,config.name);
// On (and Off)
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);
} // On characteristic
@@ -775,26 +816,35 @@ KNXDevice.prototype = {
var myService = new Service.Thermostat(config.name,config.name);
// CurrentTemperature)
// props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108
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);
}
// TargetTemperature if available
if (config.TargetTemperature) {
this.log("Thermostat TargetTemperature characteristic enabled");
this.log("["+ this.name +"]:Thermostat TargetTemperature characteristic enabled");
// default boundary too narrow for thermostats
myService.getCharacteristic(Characteristic.TargetTemperature).minimumValue=0; // °C
myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C
// props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108
myService.getCharacteristic(Characteristic.TargetTemperature).setProps({
minValue: config.TargetTemperature.minValue || 0,
maxValue: config.TargetTemperature.maxValue || 40
});
this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature);
}
// HVAC
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);
}
// HVAC
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);
}
return myService;
@@ -812,10 +862,16 @@ KNXDevice.prototype = {
}
var myService = new Service.TemperatureSensor(config.name,config.name);
// CurrentTemperature)
// props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108
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);
}
return myService;
},
getWindowService: function(config) {
@@ -845,16 +901,16 @@ KNXDevice.prototype = {
var myService = new Service.Window(config.name,config.name);
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);
}
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);
}
if (config.PositionState) {
this.log("Window PositionState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState);
this.log("["+ this.name +"]:Window PositionState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.PositionState, "Int", config.PositionState);
}
return myService;
},
@@ -880,15 +936,15 @@ KNXDevice.prototype = {
var myService = new Service.WindowCovering(config.name,config.name);
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);
}
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);
}
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);
}
return myService;
@@ -910,7 +966,7 @@ KNXDevice.prototype = {
informationService
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
.setCharacteristic(Characteristic.Model, "KNX Universal Device")
.setCharacteristic(Characteristic.SerialNumber, "Version 1.1.2");
.setCharacteristic(Characteristic.SerialNumber, "Version 1.1.4");
accessoryServices.push(informationService);
@@ -965,8 +1021,8 @@ KNXDevice.prototype = {
accessoryServices.push(this.getWindowCoveringService(configService));
break;
default:
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 ");
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 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)

View File

@@ -1,5 +1,5 @@
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
var komponist = require('komponist')

28
app.js
View File

@@ -1,14 +1,14 @@
var fs = require('fs');
var path = require('path');
var storage = require('node-persist');
var hap = require('HAP-NodeJS');
var uuid = require('HAP-NodeJS').uuid;
var Bridge = require('HAP-NodeJS').Bridge;
var Accessory = require('HAP-NodeJS').Accessory;
var Service = require('HAP-NodeJS').Service;
var Characteristic = require('HAP-NodeJS').Characteristic;
var accessoryLoader = require('HAP-NodeJS').AccessoryLoader;
var once = require('HAP-NodeJS/lib/util/once').once;
var hap = require("hap-nodejs");
var uuid = require("hap-nodejs").uuid;
var Bridge = require("hap-nodejs").Bridge;
var Accessory = require("hap-nodejs").Accessory;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var accessoryLoader = require("hap-nodejs").AccessoryLoader;
var once = require("hap-nodejs/lib/util/once").once;
console.log("Starting HomeBridge server...");
@@ -86,7 +86,7 @@ function loadAccessories() {
log("Initializing %s accessory...", accessoryType);
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
bridge.addBridgedAccessory(accessory);
@@ -113,11 +113,11 @@ function loadPlatforms() {
log("Initializing %s platform...", platformType);
var platformInstance = new platformConstructor(log, platformConfig);
loadPlatformAccessories(platformInstance, log);
loadPlatformAccessories(platformInstance, log, platformType);
}
}
function loadPlatformAccessories(platformInstance, log) {
function loadPlatformAccessories(platformInstance, log, platformType) {
asyncCalls++;
platformInstance.accessories(once(function(foundAccessories){
asyncCalls--;
@@ -129,7 +129,7 @@ function loadPlatformAccessories(platformInstance, log) {
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
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();
@@ -159,7 +159,7 @@ function createAccessory(accessoryInstance, displayName) {
// 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.
var accessoryUUID = uuid.generate(accessoryInstance.constructor.name + ":" + displayName);
var accessoryUUID = uuid.generate(accessoryType + ":" + (uuid_base || displayName));
var accessory = new Accessory(displayName, accessoryUUID);

View File

@@ -23,6 +23,10 @@
"token" : "telldus token",
"token_secret" : "telldus token secret"
},
{
"platform" : "Telldus",
"name" : "Telldus"
},
{
"platform": "Wink",
"name": "Wink",
@@ -89,14 +93,19 @@
"delay": 30,
"repeat": 3,
"zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"]
},
{
},
{
"platform": "HomeAssistant",
"name": "HomeAssistant",
"host": "http://192.168.1.10:8123",
"password": "XXXXX",
"supported_types": ["light", "switch", "media_player", "scene"]
}
},
{
"platform": "LIFx",
"name": "LIFx",
"access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings"
}
],
"accessories": [
@@ -144,6 +153,24 @@
"ccu_id": "The XMP-API id of your HomeMatic 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",
"name": "Lamp",
@@ -159,7 +186,20 @@
"off_url": "https://192.168.1.22:3030/devices/23222/off",
"brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b",
"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",
"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.",

View File

@@ -12,34 +12,35 @@
"license": "ISC",
"dependencies": {
"ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local",
"async": "^1.4.2",
"carwingsjs": "0.0.x",
"chokidar": "^1.0.5",
"color": "0.10.x",
"debug": "^2.2.0",
"eibd": "^0.3.1",
"elkington": "kevinohara80/elkington",
"hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#4650e771f356a220868d873d16564a6be6603ff7",
"hap-nodejs": "^0.0.2",
"harmonyhubjs-client": "^1.1.4",
"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-api": "^1.0.1",
"mdns": "^2.2.4",
"node-hue-api": "^1.0.5",
"node-icontrol": "^0.1.4",
"node-icontrol": "^0.1.5",
"node-milight-promise": "0.0.x",
"node-persist": "0.0.x",
"q": "1.4.x",
"tough-cookie": "^2.0.0",
"request": "2.49.x",
"sonos": "0.8.x",
"telldus-live": "0.2.x",
"telldus-live": "^0.2.1",
"teslams": "1.0.1",
"tough-cookie": "^2.0.0",
"unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98",
"wemo": "0.2.x",
"wink-js": "0.0.5",
"xml2js": "0.4.x",
"xmldoc": "0.1.x",
"komponist" : "0.1.0",
"yamaha-nodejs": "0.4.x",
"debug": "^2.2.0"
"yamaha-nodejs": "0.4.x"
}
}

View File

@@ -50,7 +50,7 @@
// 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 types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function DomoticzPlatform(log, config){

View File

@@ -26,7 +26,6 @@ try {
Characteristic = require("hap-nodejs").Characteristic;
}
var util = require('util');

253
platforms/FibaroHC2.js Normal file
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;

View File

@@ -67,8 +67,8 @@
// 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 Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var url = require('url')
var request = require("request");
@@ -152,15 +152,24 @@ HomeAssistantPlatform.prototype = {
entity = data[i]
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) {
continue;
}
// ignore hidden devices
if (entity.attributes && entity.attributes.hidden) {
continue;
}
var accessory = null
if (entity_type == 'light') {
accessory = new HomeAssistantLight(that.log, entity, that)
}else if (entity_type == 'switch'){
console.log(JSON.stringify(entity))
console.log("");
console.log("");
accessory = new HomeAssistantSwitch(that.log, entity, that)
}else if (entity_type == 'scene'){
accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene')

1066
platforms/HomeSeer.js Normal file

File diff suppressed because it is too large Load Diff

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 request = require('request');
var util = require('util');

552
platforms/Indigo.js Normal file
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;

View File

@@ -1,154 +1,156 @@
{
"bridge": {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"port": 51826,
"pin": "031-45-154"
},
"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",
"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!",
"platforms": [
{
"platform": "KNX",
"name": "KNX",
"knxd_ip": "192.168.178.205",
"knxd_port": 6720,
"accessories": [
{
"accessory_type": "knxdevice",
"description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.",
"name": "Living Room North Lamp",
"services": [
{
"type": "Lightbulb",
"description": "iOS8 Lightbulb type, supports On (Switch) and Brightness",
"name": "Living Room North Lamp",
"On": {
"Set": "1/1/6",
"Listen": [
"1/1/63"
]
},
"Brightness": {
"Set": "1/1/62",
"Listen": [
"1/1/64"
]
}
}
],
"services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome"
},
{
"accessory_type": "knxdevice",
"name": "Office Temperature",
"description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature",
"services": [
{
"type": "TemperatureSensor",
"name": "Raumtemperatur",
"CurrentTemperature": {
"Listen": "3/3/44"
}
}
]
},
{
"accessory_type": "knxdevice",
"name": "Office Window Lock",
"services": [
{
"type": "LockMechanism",
"description": "iOS8 Lock mechanism, Supports LockCurrentStateSecured0 OR LockCurrentState, LockTargetStateSecured0 OR LockTargetState, use depending if LOCKED is 0 or 1",
"name": "Office Window Lock",
"LockCurrentStateSecured0": {
"Listen": "5/3/15"
},
"LockTargetStateSecured0": {
"Listen": "5/3/15"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample device with multiple services. Multiple services of different types are widely supported",
"name": "Office",
"services": [
{
"type": "Lightbulb",
"name": "Office Lamp",
"On": {
"Set": "1/3/5"
}
},
{
"type": "Thermostat",
"description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ",
"name": "Raumtemperatur",
"CurrentTemperature": {
"Listen": "3/3/44"
},
"TargetTemperature": {
"Set": "3/3/94"
},
"CurrentHeatingCoolingState": {
"Listen": "3/3/64"
}
},
{
"type": "WindowCovering",
"description": "iOS9 Window covering (blinds etc) type, still WIP",
"name": "Blinds",
"TargetPosition": {
"Set": "1/2/3",
"Listen": "1/2/4"
},
"CurrentPosition": {
"Set": "1/3/1",
"Listen": "1/3/2"
},
"PositionState": {
"Listen": "2/7/1"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample contact sensor device",
"name": "Office Contact",
"services": [
{
"type": "ContactSensor",
"name": "Office Door",
"ContactSensorState": {
"Listen": "5/3/5"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample garage door opener",
"name": "Office Garage",
"services": [
{
"type": "GarageDoorOpener",
"name": "Office Garage Opener",
"CurrentDoorState": {
"Listen": "5/4/5"
},
"TargetDoorState": {
"Listen": "5/4/6"
}
}
]
}
]
}
],
"accessories": []
"bridge": {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"port": 51826,
"pin": "031-45-154"
},
"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",
"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 KNX.md file in folder platforms!",
"platforms": [
{
"platform": "KNX",
"name": "KNX",
"knxd_ip": "192.168.178.205",
"knxd_port": 6720,
"accessories": [
{
"accessory_type": "knxdevice",
"description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.",
"name": "Living Room North Lamp",
"services": [
{
"type": "Lightbulb",
"description": "iOS8 Lightbulb type, supports On (Switch) and Brightness",
"name": "Living Room North Lamp",
"On": {
"Set": "1/1/6",
"Listen": [
"1/1/63"
]
},
"Brightness": {
"Set": "1/1/62",
"Listen": [
"1/1/64"
]
}
}
],
"services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome"
},
{
"accessory_type": "knxdevice",
"name": "Office Temperature",
"description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature",
"services": [
{
"type": "TemperatureSensor",
"name": "Raumtemperatur",
"CurrentTemperature": {
"Listen": "3/3/44"
}
}
]
},
{
"accessory_type": "knxdevice",
"name": "Office Window Lock",
"services": [
{
"type": "LockMechanism",
"description": "iOS8 Lock mechanism, Supports LockCurrentState, LockTargetState, append R to the addresses if LOCKED is 1",
"name": "Office Window Lock",
"LockCurrentState": {
"Listen": "5/3/15R"
},
"LockTargetState": {
"Listen": "5/3/16R"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample device with multiple services. Multiple services of different types are widely supported",
"name": "Office",
"services": [
{
"type": "Lightbulb",
"name": "Office Lamp",
"On": {
"Set": "1/3/5"
}
},
{
"type": "Thermostat",
"description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ",
"name": "Raumtemperatur",
"CurrentTemperature": {
"Listen": "3/3/44"
},
"TargetTemperature": {
"Set": "3/3/94"
},
"CurrentHeatingCoolingState": {
"Listen": "3/3/64"
}
},
{
"type": "WindowCovering",
"description": "iOS9 Window covering (blinds etc) type, still WIP",
"name": "Blinds",
"TargetPosition": {
"Set": "1/2/3",
"Listen": "1/2/4"
},
"CurrentPosition": {
"Set": "1/3/1",
"Listen": "1/3/2"
},
"PositionState": {
"Listen": "2/7/1"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample contact sensor device",
"name": "Office Contact",
"services": [
{
"type": "ContactSensor",
"name": "Office Door",
"ContactSensorState": {
"Listen": "5/3/5"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample garage door opener",
"name": "Office Garage",
"services": [
{
"type": "GarageDoorOpener",
"name": "Office Garage Opener",
"CurrentDoorState": {
"Listen": "5/4/5"
},
"TargetDoorState": {
"Listen": "5/4/6"
}
}
]
}
]
}
],
"accessories": [
]
}

View File

@@ -2,7 +2,7 @@
* based on Sonos platform
*/
'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 knxd = require('eibd');
@@ -116,8 +116,8 @@ function groupsocketlisten(opts, callback) {
}
var registerSingleGA = function registerSingleGA (groupAddress, callback) {
subscriptions.push({address: groupAddress, callback: callback });
var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) {
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) {
// found one, notify
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) {
// found one, notify
// 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) {
// handle multiple addresses
for (var i = 0; i < groupAddresses.length; i++) {
if (groupAddresses[i]) { // do not bind empty addresses
registerSingleGA (groupAddresses[i], callback);
if (groupAddresses[i] && groupAddresses[i].match(/(\d*\/\d*\/\d*)/)) { // do not bind empty addresses or invalid addresses
// clean the addresses
registerSingleGA (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0], callback,groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false );
}
}
} else {
// 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);
};

View File

@@ -47,7 +47,7 @@ You have to add services in the following syntax:
{
"type": "SERVICENAME",
"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": {
"Set": "1/1/6",
"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:`
# 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
- ContactSensorState: DPT 1.002, 0 as contact **OR**
- ContactSensorStateContact1: DPT 1.002, 1 as contact
- ContactSensorState: DPT 1.002, 0 as contact
- ~~ContactSensorStateContact1: DPT 1.002, 1 as contact~~
- StatusActive: 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
## LockMechanism (This is poorly mapped!)
- LockCurrentState: DPT 1, 1 as secured **OR (but not both:)**
- LockCurrentStateSecured0: DPT 1, 0 as secured
- LockTargetState: DPT 1, 1 as secured **OR**
- LockTargetStateSecured0: DPT 1, 0 as secured
- LockCurrentState: DPT 1, 1 as secured
- ~~LockCurrentStateSecured0: DPT 1, 0 as secured~~
- LockTargetState: DPT 1, 1 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*
@@ -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
## TemperatureSensor
- CurrentTemperature: DPT9.001 in °C [listen only]
- CurrentTemperature: DPT9.001 in °C [listen only]
## Thermostat
- CurrentTemperature: DPT9.001 in °C [listen only]
- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored
- 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
- CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only]
- 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
- CurrentPosition: DPT5 percentage
- TargetPosition: DPT5 percentage
- PositionState: DPT5 value [listen only]
- PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 Stopped]
### not yet supported
- HoldPosition

View File

@@ -16,8 +16,8 @@
// The default code for all HomeBridge accessories is 031-45-154.
//
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var lifxRemoteObj = require('lifx-api');
var lifx_remote;

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 harmony = require('harmonyhubjs-client');

View File

@@ -47,8 +47,8 @@ TODO:
*/
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var Milight = require('node-milight-promise').MilightController;
var commands = require('node-milight-promise').commands;

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');
function NestPlatform(log, config){
@@ -44,7 +44,11 @@ NestPlatform.prototype = {
function NestThermostatAccessory(log, name, device, deviceId) {
// device info
this.name = name;
if (name) {
this.name = name;
} else {
this.name = "Nest";
}
this.model = device.model_version;
this.serial = device.serial_number;
this.deviceId = deviceId;
@@ -390,4 +394,4 @@ NestThermostatAccessory.prototype = {
}
module.exports.accessory = NestThermostatAccessory;
module.exports.platform = NestPlatform;
module.exports.platform = NestPlatform;

347
platforms/Openhab.js Normal file
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;

View File

@@ -33,7 +33,7 @@ var hue = require("node-hue-api"),
HueApi = hue.HueApi,
lightState = hue.lightState;
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
function PhilipsHuePlatform(log, config) {
this.log = log;

View File

@@ -1,7 +1,7 @@
// SmartThings JSON API SmartApp required
// 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");
function SmartThingsPlatform(log, config){

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');
function SonosPlatform(log, config){

265
platforms/Telldus.js Normal file
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;

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");
function TelldusLivePlatform(log, config) {

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 model = {

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 Q = require('q');
var mdns = require('mdns');
//workaround for raspberry pi
var sequence = [
@@ -12,10 +17,54 @@ function YamahaAVRPlatform(log, config){
this.log = log;
this.config = config;
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.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});
}
// 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 = {
accessories: function(callback) {
this.log("Getting Yamaha AVR devices.");
@@ -24,38 +73,86 @@ YamahaAVRPlatform.prototype = {
var browser = this.browser;
browser.stop();
browser.removeAllListeners('serviceUp'); // cleanup listeners
browser.on('serviceUp', function(service){
var accessories = [];
var timer, timeElapsed = 0, checkCyclePeriod = 5000;
// Hmm... seems we need to prevent double-listing via manual and Bonjour...
var sysIds = {};
var setupFromService = function(service){
var name = service.name;
//console.log('Found HTTP service "' + name + '"');
// 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.
var yamaha = new Yamaha(service.host);
yamaha.getSystemConfig().then(function(sysConfig){
var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0];
var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0];
that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\"");
var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig);
callback([accessory]);
}, function(err){
return;
})
});
yamaha.getSystemConfig().then(
function(sysConfig){
var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0];
var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0];
if(sysIds[sysId]){
this.log("WARN: Got multiple systems with ID " + sysId + "! Omitting duplicate!");
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();
// 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.config = config;
this.mdnsService = mdnsService;
this.yamaha = yamaha;
this.sysConfig = sysConfig;
this.name = mdnsService.name;
this.serviceName = mdnsService.name + " Speakers";
this.nameSuffix = config["name_suffix"] || " Speakers";
this.name = name;
this.serviceName = name + this.nameSuffix;
this.setMainInputTo = config["setMainInputTo"];
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 = {
@@ -66,104 +163,74 @@ YamahaAVRAccessory.prototype = {
if (playing) {
yamaha.powerOn().then(function(){
return yamaha.powerOn().then(function(){
if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10);
else return { then: function(f, r){ f(); } };
else return Q();
}).then(function(){
if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo);
else return { then: function(f, r){ f(); } };
else return Q();
}).then(function(){
if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver(
'<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>'
);
else return { then: function(f, r){ f(); } };
//else return Promise.fulfilled(undefined);
else return Q();
});
}
else {
yamaha.powerOff();
return yamaha.powerOff();
}
},
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: "Yamaha",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0],
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0],
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.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
}]
}];
var informationService = new Service.AccessoryInformation();
var yamaha = this.yamaha;
informationService
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.Manufacturer, "Yamaha")
.setCharacteristic(Characteristic.Model, this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0])
.setCharacteristic(Characteristic.SerialNumber, this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]);
var switchService = new Service.Switch("Power State");
switchService.getCharacteristic(Characteristic.On)
.on('get', function(callback, context){
yamaha.isOn().then(function(result){
callback(false, result);
}.bind(this));
}.bind(this))
.on('set', function(powerOn, callback){
this.setPlaying(powerOn).then(function(){
callback(false, powerOn);
}, function(error){
callback(error, !powerOn); //TODO: Actually determine and send real new status.
});
}.bind(this));
var audioDeviceService = new YamahaAVRPlatform.AudioDeviceService("Audio Functions");
audioDeviceService.getCharacteristic(YamahaAVRPlatform.AudioVolume)
.on('get', function(callback, context){
yamaha.getBasicInfo().done(function(basicInfo){
var v = basicInfo.getVolume()/10.0;
var p = 100 * ((v - that.minVolume) / that.gapVolume);
p = p < 0 ? 0 : p > 100 ? 100 : Math.round(p);
debug("Got volume percent of " + p + "%");
callback(false, p);
});
})
.on('set', function(p, callback){
var v = ((p / 100) * that.gapVolume) + that.minVolume;
v = Math.round(v*10.0);
debug("Setting volume to " + v);
yamaha.setVolumeTo(v).then(function(){
callback(false, p);
});
})
.getValue(null, null); // force an asynchronous get
return [informationService, switchService, audioDeviceService];
}
};

View File

@@ -1,7 +1,7 @@
var debug = require('debug')('ZWayServer');
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var types = require("HAP-NodeJS/accessories/types.js");
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
var tough = require('tough-cookie');
var Q = require("q");
@@ -11,6 +11,7 @@ function ZWayServerPlatform(log, config){
this.url = config["url"];
this.login = config["login"];
this.password = config["password"];
this.opt_in = config["opt_in"];
this.name_overrides = config["name_overrides"];
this.batteryLow = config["battery_low_level"] || 15;
this.pollInterval = config["poll_interval"] || 2;
@@ -82,7 +83,20 @@ ZWayServerPlatform.prototype = {
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) {
debug("Fetching Z-Way devices...");
@@ -90,10 +104,10 @@ ZWayServerPlatform.prototype = {
//Note: Order matters!
var primaryDeviceClasses = [
"thermostat",
"sensorMultilevel.Temperature",
"switchMultilevel",
"switchBinary",
"sensorBinary.Door/Window"
"sensorBinary.Door/Window",
"sensorMultilevel.Temperature"
];
var that = this;
@@ -109,14 +123,49 @@ ZWayServerPlatform.prototype = {
var groupedDevices = {};
for(var i = 0; i < devices.length; i++){
var vdev = devices[i];
if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; }
var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2');
var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, primary: undefined});
if(this.getTagValue("Skip")) { debug("Tag says skip!"); continue; }
if(this.opt_in && !this.getTagValue(vdev, "Include")) continue;
var gdid = this.getTagValue(vdev, "Accessory.Id");
if(!gdid){
gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2');
}
var gd = groupedDevices[gdid] || (groupedDevices[gdid] = { devices: [], types: {}, extras: {}, primary: undefined, cxmap: {} });
gd.devices.push(vdev);
gd.types[ZWayServerPlatform.getVDevTypeKey(vdev)] = gd.devices.length - 1;
gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility
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;
}
}
//TODO: Make a second pass, re-splitting any devices that don't make sense together
for(var gdid in groupedDevices) {
if(!groupedDevices.hasOwnProperty(gdid)) continue;
@@ -128,12 +177,17 @@ ZWayServerPlatform.prototype = {
}
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){
gd.primary = gd.types[primaryDeviceClasses[ti]];
var pd = gd.devices[gd.primary];
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);
break;
}
@@ -145,7 +199,6 @@ ZWayServerPlatform.prototype = {
foundAccessories.push(accessory);
}
//foundAccessories = foundAccessories.slice(0, 10); // Limit to a few devices for testing...
callback(foundAccessories);
// Start the polling process...
@@ -172,6 +225,11 @@ ZWayServerPlatform.prototype = {
if(this.cxVDevMap[upd.id]){
var vdev = this.vDevStore[upd.id];
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;
var cxs = this.cxVDevMap[upd.id];
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){
var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev);
var services = [], service;
switch (typeKey) {
case "thermostat":
services.push(new Service.Thermostat(vdev.metrics.title));
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));
case "thermostat":
services.push(new Service.Thermostat(vdev.metrics.title, vdev.id));
break;
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;
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;
case "sensorMultilevel.Luminiscence":
services.push(new Service.LightSensor(vdev.metrics.title));
services.push(new Service.LightSensor(vdev.metrics.title, vdev.id));
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 =[];
@@ -267,11 +392,19 @@ ZWayServerAccessory.prototype = {
}
,
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;
if(!map){
this.uuidToTypeKeyMap = map = {};
map[(new Characteristic.On).UUID] = ["switchBinary","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.TargetTemperature).UUID] = ["thermostat"];
map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result
@@ -287,7 +420,7 @@ ZWayServerAccessory.prototype = {
}
if(cx instanceof Characteristic.Name) return vdevPreferred;
// Special case!: If cx is a CurrentTemperature, ignore the preferred device...we want the sensor if available!
if(cx instanceof Characteristic.CurrentTemperature) vdevPreferred = null;
//
@@ -309,8 +442,8 @@ ZWayServerAccessory.prototype = {
return null;
}
,
configureCharacteristic: function(cx, vdev){
var that = this;
configureCharacteristic: function(cx, vdev, service){
var accessory = this;
// Add this combination to the maps...
if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = [];
@@ -324,12 +457,17 @@ ZWayServerAccessory.prototype = {
cx.value = cx.zway_getValueFromVDev(vdev);
cx.on('get', function(callback, context){
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, that.name);
callback(false, accessory.name);
});
cx.writable = false;
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){
cx.zway_getValueFromVDev = function(vdev){
var val = false;
@@ -381,6 +519,64 @@ ZWayServerAccessory.prototype = {
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){
cx.zway_getValueFromVDev = function(vdev){
return vdev.metrics.level;
@@ -393,8 +589,10 @@ ZWayServerAccessory.prototype = {
callback(false, cx.zway_getValueFromVDev(result.data));
});
}.bind(this));
cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40;
cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999;
cx.setProps({
minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40,
maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999
});
return cx;
}
@@ -416,8 +614,10 @@ ZWayServerAccessory.prototype = {
callback();
});
}.bind(this));
cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5;
cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40;
cx.setProps({
minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5,
maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40
});
return cx;
}
@@ -431,7 +631,9 @@ ZWayServerAccessory.prototype = {
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, Characteristic.TemperatureDisplayUnits.CELSIUS);
});
cx.writable = false;
cx.setProps({
perms: [Characteristic.Perms.READ]
});
return cx;
}
@@ -459,7 +661,6 @@ ZWayServerAccessory.prototype = {
callback(false, Characteristic.TargetHeatingCoolingState.HEAT);
});
// Hmm... apparently if this is not setable, we can't add a thermostat change to a scene. So, make it writable but a no-op.
cx.writable = true;
cx.on('set', function(newValue, callback){
debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!")
callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT);
@@ -494,8 +695,9 @@ ZWayServerAccessory.prototype = {
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, Characteristic.TargetDoorState.CLOSED);
});
//cx.readable = false;
cx.writable = false;
cx.setProps({
perms: [Characteristic.Perms.READ]
});
}
if(cx instanceof Characteristic.ObstructionDetected){
@@ -508,8 +710,6 @@ ZWayServerAccessory.prototype = {
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, false);
});
//cx.readable = false;
cx.writable = false;
}
if(cx instanceof Characteristic.BatteryLevel){
@@ -528,7 +728,7 @@ ZWayServerAccessory.prototype = {
if(cx instanceof Characteristic.StatusLowBattery){
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.on('get', function(callback, context){
@@ -550,8 +750,6 @@ ZWayServerAccessory.prototype = {
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, Characteristic.ChargingState.NOT_CHARGING);
});
//cx.readable = false;
cx.writable = false;
}
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.
// This will probably change!
var lux = 0.0005 * (vdev.metrics.level^3.6);
if(lux < cx.minimumValue) return cx.minimumValue;
if(lux > cx.maximumValue) return cx.maximumValue;
// Bounds checking now done upstream!
//if(lux < cx.minimumValue) return cx.minimumValue; if(lux > cx.maximumValue) return cx.maximumValue;
return lux;
} else {
return vdev.metrics.level;
@@ -580,6 +778,43 @@ ZWayServerAccessory.prototype = {
});
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){
@@ -591,14 +826,29 @@ ZWayServerAccessory.prototype = {
success = false;
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++){
var cx = service.optionalCharacteristics[i];
var vdev = this.getVDevForCharacteristic(cx);
var vdev = this.getVDevForCharacteristic(cx, vdev);
if(!vdev) continue;
cx = this.configureCharacteristic(cx, vdev);
if(cx) service.addCharacteristic(cx);
//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);
}
catch (ex) {
debug('Adding Characteristic "' + cx.displayName + '" failed with message "' + ex.message + '". This may be expected.');
}
}
return success;
}
@@ -606,17 +856,30 @@ ZWayServerAccessory.prototype = {
getServices: function() {
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();
informationService
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me")
.setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)")
.setCharacteristic(Characteristic.SerialNumber, "VDev-" + this.devDesc.devices[this.devDesc.primary].h) //FIXME: Is this valid?);
.setCharacteristic(Characteristic.SerialNumber, accId);
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.devDesc.types["battery.Battery"]){
@@ -655,7 +918,7 @@ ZWayServerAccessory.prototype = {
extraCxs = []; // to wipe out any already setup cxs.
break;
}
this.configureCharacteristic(cx, vdev2);
this.configureCharacteristic(cx, vdev2, service);
extraCxs.push(cx);
}
for(var j = 0; j < extraCxs.length; j++)