mirror of
https://github.com/mtan93/homebridge.git
synced 2026-04-03 14:23:11 +01:00
Merge remote-tracking branch 'nfarina/master' into zway-rgb
Conflicts: platforms/ZWayServer.js
This commit is contained in:
@@ -52,13 +52,18 @@ You'll also need some patience, as Siri can be very strict about sentence struct
|
|||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. First, clone this repo:
|
OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up.
|
||||||
|
|
||||||
|
**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed.
|
||||||
|
|
||||||
|
First, clone this repo:
|
||||||
|
|
||||||
$ git clone https://github.com/nfarina/homebridge.git
|
$ git clone https://github.com/nfarina/homebridge.git
|
||||||
$ cd homebridge
|
$ cd homebridge
|
||||||
$ npm install
|
$ npm install
|
||||||
|
|
||||||
**Node**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load.
|
**Note**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load.
|
||||||
|
|
||||||
|
|
||||||
Now you should be able to run the homebridge server:
|
Now you should be able to run the homebridge server:
|
||||||
|
|
||||||
|
|||||||
58
accessories/GenericRS232Device.js
Normal file
58
accessories/GenericRS232Device.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
var Service = require("HAP-NodeJS").Service;
|
||||||
|
var Characteristic = require("HAP-NodeJS").Characteristic;
|
||||||
|
var SerialPort = require("serialport").SerialPort;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
accessory: GenericRS232DeviceAccessory
|
||||||
|
}
|
||||||
|
|
||||||
|
function GenericRS232DeviceAccessory(log, config) {
|
||||||
|
this.log = log;
|
||||||
|
this.id = config["id"];
|
||||||
|
this.name = config["name"];
|
||||||
|
this.model_name = config["model_name"];
|
||||||
|
this.manufacturer = config["manufacturer"];
|
||||||
|
this.on_command = config["on_command"];
|
||||||
|
this.off_command = config["off_command"];
|
||||||
|
this.device = config["device"];
|
||||||
|
this.baudrate = config["baudrate"];
|
||||||
|
}
|
||||||
|
|
||||||
|
GenericRS232DeviceAccessory.prototype = {
|
||||||
|
setPowerState: function(powerOn, callback) {
|
||||||
|
var that = this;
|
||||||
|
var command = powerOn ? that.on_command : that.off_command;
|
||||||
|
var serialPort = new SerialPort(that.device, { baudrate: that.baudrate }, false);
|
||||||
|
serialPort.open(function (error) {
|
||||||
|
if (error) {
|
||||||
|
callback(new Error('Can not communicate with ' + that.name + " (" + error + ")"))
|
||||||
|
} else {
|
||||||
|
serialPort.write(command, function(err, results) {
|
||||||
|
if (error) {
|
||||||
|
callback(new Error('Can not send power command to ' + that.name + " (" + err + ")"))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getServices: function() {
|
||||||
|
var switchService = new Service.Switch(this.name);
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, this.manufacturer)
|
||||||
|
.setCharacteristic(Characteristic.Model, this.model_name)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, this.id);
|
||||||
|
|
||||||
|
switchService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('set', this.setPowerState.bind(this));
|
||||||
|
|
||||||
|
return [informationService, switchService];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.accessory = GenericRS232DeviceAccessory;
|
||||||
@@ -140,9 +140,7 @@ WeMoAccessory.prototype.getServices = function() {
|
|||||||
|
|
||||||
garageDoorService
|
garageDoorService
|
||||||
.getCharacteristic(Characteristic.TargetDoorState)
|
.getCharacteristic(Characteristic.TargetDoorState)
|
||||||
.on('set', this.setTargetDoorState.bind(this))
|
.on('set', this.setTargetDoorState.bind(this));
|
||||||
.supportsEventNotification = false;
|
|
||||||
|
|
||||||
|
|
||||||
return [garageDoorService];
|
return [garageDoorService];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
/*
|
/**
|
||||||
* This is a KNX universal accessory shim.
|
* This is a KNX universal accessory shim.
|
||||||
|
* This is NOT the version for dynamic installation
|
||||||
*
|
*
|
||||||
|
New 2015-09-16: Welcome iOS9.0
|
||||||
|
new features include:
|
||||||
|
- services:
|
||||||
|
- Window
|
||||||
|
- WindowCovering
|
||||||
|
- ContactSensor
|
||||||
|
New 2015-09-18:
|
||||||
|
- Services Switch and Outlet
|
||||||
|
- Code cleanup
|
||||||
|
New 2015-09-19:
|
||||||
|
- GarageDoorOpener Service
|
||||||
|
- MotionSensor Service
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
var Service = require("HAP-NodeJS").Service;
|
var Service = require("HAP-NodeJS").Service;
|
||||||
@@ -23,7 +36,7 @@ function KNXDevice(log, config) {
|
|||||||
if (config.knxd_ip){
|
if (config.knxd_ip){
|
||||||
this.knxd_ip = config.knxd_ip;
|
this.knxd_ip = config.knxd_ip;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("MISSING KNXD IP");
|
throw new Error("KNX configuration fault: MISSING KNXD IP");
|
||||||
}
|
}
|
||||||
if (config.knxd_port){
|
if (config.knxd_port){
|
||||||
this.knxd_port = config.knxd_port;
|
this.knxd_port = config.knxd_port;
|
||||||
@@ -87,7 +100,7 @@ KNXDevice.prototype = {
|
|||||||
this.log("[ERROR] knxwrite:sendAPDU: " + err);
|
this.log("[ERROR] knxwrite:sendAPDU: " + err);
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
// this.log("knx data sent");
|
this.log("knx data sent: Value "+value+ " for GA "+groupAddress);
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@@ -95,7 +108,6 @@ KNXDevice.prototype = {
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
// issues an all purpose read request on the knx bus
|
// issues an all purpose read request on the knx bus
|
||||||
// DOES NOT WAIT for an answer. Please register the address with a callback using registerGA() function
|
// DOES NOT WAIT for an answer. Please register the address with a callback using registerGA() function
|
||||||
knxread: function(groupAddress){
|
knxread: function(groupAddress){
|
||||||
@@ -125,7 +137,6 @@ KNXDevice.prototype = {
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
// issuing multiple read requests at once
|
// issuing multiple read requests at once
|
||||||
knxreadarray: function (groupAddresses) {
|
knxreadarray: function (groupAddresses) {
|
||||||
if (groupAddresses.constructor.toString().indexOf("Array") > -1) {
|
if (groupAddresses.constructor.toString().indexOf("Array") > -1) {
|
||||||
@@ -141,26 +152,14 @@ KNXDevice.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// special types
|
/** Registering routines
|
||||||
knxwrite_percent: function(callback, groupAddress, value) {
|
*
|
||||||
var numericValue = 0;
|
*/
|
||||||
if (value && value>=0 && value <= 100) {
|
|
||||||
numericValue = 255*value/100; // convert 1..100 to 1..255 for KNX bus
|
|
||||||
} else {
|
|
||||||
this.log("[ERROR] Percentage value ot of bounds ");
|
|
||||||
numericValue = 0;
|
|
||||||
}
|
|
||||||
this.knxwrite(callback, groupAddress,'DPT5',numericValue);
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// need to spit registers into types
|
|
||||||
|
|
||||||
// boolean: get 0 or 1 from the bus, write boolean
|
// boolean: get 0 or 1 from the bus, write boolean
|
||||||
knxregister_bool: function(addresses, characteristic) {
|
knxregister_bool: function(addresses, characteristic) {
|
||||||
this.log("knx registering BOOLEAN " + addresses);
|
this.log("knx registering BOOLEAN " + addresses);
|
||||||
knxd_registerGA(addresses, function(val, src, dest, type){
|
knxd_registerGA(addresses, function(val, src, dest, type){
|
||||||
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type + " for " + characteristic.displayName);
|
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName);
|
||||||
// iterate(characteristic);
|
// iterate(characteristic);
|
||||||
characteristic.setValue(val ? 1 : 0, undefined, 'fromKNXBus');
|
characteristic.setValue(val ? 1 : 0, undefined, 'fromKNXBus');
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@@ -168,7 +167,7 @@ KNXDevice.prototype = {
|
|||||||
knxregister_boolReverse: function(addresses, characteristic) {
|
knxregister_boolReverse: function(addresses, characteristic) {
|
||||||
this.log("knx registering BOOLEAN " + addresses);
|
this.log("knx registering BOOLEAN " + addresses);
|
||||||
knxd_registerGA(addresses, function(val, src, dest, type){
|
knxd_registerGA(addresses, function(val, src, dest, type){
|
||||||
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type + " for " + characteristic.displayName);
|
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName);
|
||||||
// iterate(characteristic);
|
// iterate(characteristic);
|
||||||
characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus');
|
characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus');
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@@ -177,7 +176,7 @@ KNXDevice.prototype = {
|
|||||||
knxregister_percent: function(addresses, characteristic) {
|
knxregister_percent: function(addresses, characteristic) {
|
||||||
this.log("knx registering PERCENT " + addresses);
|
this.log("knx registering PERCENT " + addresses);
|
||||||
knxd_registerGA(addresses, function(val, src, dest, type){
|
knxd_registerGA(addresses, function(val, src, dest, type){
|
||||||
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
|
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
|
||||||
if (type !== "DPT5") {
|
if (type !== "DPT5") {
|
||||||
this.log("[ERROR] Received value cannot be a percentage value");
|
this.log("[ERROR] Received value cannot be a percentage value");
|
||||||
} else {
|
} else {
|
||||||
@@ -194,27 +193,35 @@ KNXDevice.prototype = {
|
|||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
// float
|
// float
|
||||||
knxregister_float: function(addresses, characteristic) {
|
knxregister_float: function(addresses, characteristic) {
|
||||||
this.log("knx registering FLOAT " + addresses);
|
this.log("knx registering FLOAT " + addresses);
|
||||||
knxd_registerGA(addresses, function(val, src, dest, type){
|
knxd_registerGA(addresses, function(val, src, dest, type){
|
||||||
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
|
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
|
||||||
var hk_value = Math.round(val*10)/10;
|
var hk_value = Math.round(val*10)/10;
|
||||||
if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) {
|
if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) {
|
||||||
characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decoimal for HomeKit
|
characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit
|
||||||
} else {
|
} else {
|
||||||
this.log("Value %s out of bounds %s...%s ",hk_value, characteristic.minimumValue, characteristic.maximumValue);
|
this.log("Value %s out of bounds %s...%s ",hk_value, characteristic.minimumValue, characteristic.maximumValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
//integer
|
||||||
// what about HVAC heating cooling types?
|
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');
|
||||||
|
} else {
|
||||||
|
this.log("Value %s out of bounds %s...%s ",hk_value, (characteristic.minimumValue || 0), (characteristic.maximumValue || 255));
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
knxregister_HVAC: function(addresses, characteristic) {
|
knxregister_HVAC: function(addresses, characteristic) {
|
||||||
this.log("knx registering HVAC " + addresses);
|
this.log("knx registering HVAC " + addresses);
|
||||||
knxd_registerGA(addresses, function(val, src, dest, type){
|
knxd_registerGA(addresses, function(val, src, dest, type){
|
||||||
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
|
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
|
||||||
var HAPvalue = 0;
|
var HAPvalue = 0;
|
||||||
switch (val){
|
switch (val){
|
||||||
case 0:
|
case 0:
|
||||||
@@ -238,7 +245,7 @@ KNXDevice.prototype = {
|
|||||||
characteristic.setValue(HAPvalue, undefined, 'fromKNXBus');
|
characteristic.setValue(HAPvalue, undefined, 'fromKNXBus');
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
// to do! KNX: DPT 20.102 = One Byte like DPT5
|
/** KNX HVAC (heating, ventilation, and air conditioning) types do not really match to homekit types:
|
||||||
// 0 = Auto
|
// 0 = Auto
|
||||||
// 1 = Comfort
|
// 1 = Comfort
|
||||||
// 2 = Standby
|
// 2 = Standby
|
||||||
@@ -250,25 +257,25 @@ KNXDevice.prototype = {
|
|||||||
// Characteristic.TargetHeatingCoolingState.HEAT = 1;
|
// Characteristic.TargetHeatingCoolingState.HEAT = 1;
|
||||||
// Characteristic.TargetHeatingCoolingState.COOL = 2;
|
// Characteristic.TargetHeatingCoolingState.COOL = 2;
|
||||||
// Characteristic.TargetHeatingCoolingState.AUTO = 3;
|
// Characteristic.TargetHeatingCoolingState.AUTO = 3;
|
||||||
|
AUTO (3) is not allowed as return type from devices!
|
||||||
|
*/
|
||||||
// undefined, has to match!
|
// undefined, has to match!
|
||||||
knxregister: function(addresses, characteristic) {
|
knxregister: function(addresses, characteristic) {
|
||||||
this.log("knx registering " + addresses);
|
this.log("knx registering " + addresses);
|
||||||
knxd_registerGA(addresses, function(val, src, dest, type){
|
knxd_registerGA(addresses, function(val, src, dest, type){
|
||||||
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
|
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
|
||||||
characteristic.setValue(val, undefined, 'fromKNXBus');
|
characteristic.setValue(val, undefined, 'fromKNXBus');
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/** set methods used for creating callbacks
|
||||||
* set methods used for creating callbacks, such as
|
* such as
|
||||||
* var Characteristic = myService.addCharacteristic(new Characteristic.Brightness())
|
* var Characteristic = myService.addCharacteristic(new Characteristic.Brightness())
|
||||||
* .on('set', function(value, callback, context) {
|
* .on('set', function(value, callback, context) {
|
||||||
* this.setPercentage(value, callback, context, this.config[index].Set)
|
* this.setPercentage(value, callback, context, this.config[index].Set)
|
||||||
* }.bind(this));
|
* }.bind(this));
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setBooleanState: function(value, callback, context, gaddress) {
|
setBooleanState: function(value, callback, context, gaddress) {
|
||||||
if (context === 'fromKNXBus') {
|
if (context === 'fromKNXBus') {
|
||||||
this.log(gaddress + " event ping pong, exit!");
|
this.log(gaddress + " event ping pong, exit!");
|
||||||
@@ -301,7 +308,6 @@ KNXDevice.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setPercentage: function(value, callback, context, gaddress) {
|
setPercentage: function(value, callback, context, gaddress) {
|
||||||
if (context === 'fromKNXBus') {
|
if (context === 'fromKNXBus') {
|
||||||
this.log("event ping pong, exit!");
|
this.log("event ping pong, exit!");
|
||||||
@@ -317,7 +323,21 @@ KNXDevice.prototype = {
|
|||||||
this.knxwrite(callback, gaddress,'DPT5',numericValue);
|
this.knxwrite(callback, gaddress,'DPT5',numericValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setInt: function(value, callback, context, gaddress) {
|
||||||
|
if (context === 'fromKNXBus') {
|
||||||
|
this.log("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
|
||||||
|
}
|
||||||
|
this.log("Setting "+gaddress+" int to %s (%s)", value, numericValue);
|
||||||
|
this.knxwrite(callback, gaddress,'DPT5',numericValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
setFloat: function(value, callback, context, gaddress) {
|
setFloat: function(value, callback, context, gaddress) {
|
||||||
if (context === 'fromKNXBus') {
|
if (context === 'fromKNXBus') {
|
||||||
this.log(gaddress + " event ping pong, exit!");
|
this.log(gaddress + " event ping pong, exit!");
|
||||||
@@ -333,7 +353,6 @@ KNXDevice.prototype = {
|
|||||||
this.knxwrite(callback, gaddress,'DPT9',numericValue);
|
this.knxwrite(callback, gaddress,'DPT9',numericValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setHVACState: function(value, callback, context, gaddress) {
|
setHVACState: function(value, callback, context, gaddress) {
|
||||||
if (context === 'fromKNXBus') {
|
if (context === 'fromKNXBus') {
|
||||||
this.log(gaddress + " event ping pong, exit!");
|
this.log(gaddress + " event ping pong, exit!");
|
||||||
@@ -364,21 +383,16 @@ KNXDevice.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
/** identify dummy
|
||||||
|
*
|
||||||
|
*/
|
||||||
identify: function(callback) {
|
identify: function(callback) {
|
||||||
this.log("Identify requested!");
|
this.log("Identify requested!");
|
||||||
callback(); // success
|
callback(); // success
|
||||||
},
|
},
|
||||||
|
/** bindCharacteristic
|
||||||
|
* initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK)
|
||||||
/*
|
*/
|
||||||
* function getXXXXXXXService(config)
|
|
||||||
*
|
|
||||||
* returns a configured service object to the caller (accessory/device)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
bindCharacteristic: function(myService, characteristicType, valueType, config) {
|
bindCharacteristic: function(myService, characteristicType, valueType, config) {
|
||||||
var myCharacteristic = myService.getCharacteristic(characteristicType);
|
var myCharacteristic = myService.getCharacteristic(characteristicType);
|
||||||
if (myCharacteristic === undefined) {
|
if (myCharacteristic === undefined) {
|
||||||
@@ -408,14 +422,20 @@ KNXDevice.prototype = {
|
|||||||
this.setFloat(value, callback, context, config.Set);
|
this.setFloat(value, callback, context, config.Set);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
break;
|
break;
|
||||||
|
case "Int":
|
||||||
|
myCharacteristic.on('set', function(value, callback, context) {
|
||||||
|
this.setInt(value, callback, context, config.Set);
|
||||||
|
}.bind(this));
|
||||||
|
break;
|
||||||
case "HVAC":
|
case "HVAC":
|
||||||
myCharacteristic.on('set', function(value, callback, context) {
|
myCharacteristic.on('set', function(value, callback, context) {
|
||||||
this.setHVACState(value, callback, context, config.Set);
|
this.setHVACState(value, callback, context, config.Set);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
break;
|
break;
|
||||||
default:
|
default: {
|
||||||
this.log("[ERROR] unknown type passed");
|
this.log("[ERROR] unknown type passed");
|
||||||
throw new Error("[ERROR] unknown type passed");
|
throw new Error("[ERROR] unknown type passed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ([config.Set].concat(config.Listen || []).length>0) {
|
if ([config.Set].concat(config.Listen || []).length>0) {
|
||||||
@@ -434,6 +454,9 @@ KNXDevice.prototype = {
|
|||||||
case "Float":
|
case "Float":
|
||||||
this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic);
|
this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic);
|
||||||
break;
|
break;
|
||||||
|
case "Int":
|
||||||
|
this.knxregister_int([config.Set].concat(config.Listen || []), myCharacteristic);
|
||||||
|
break;
|
||||||
case "HVAC":
|
case "HVAC":
|
||||||
this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic);
|
this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic);
|
||||||
break;
|
break;
|
||||||
@@ -446,7 +469,118 @@ KNXDevice.prototype = {
|
|||||||
}
|
}
|
||||||
return myCharacteristic; // for chaining or whatsoever
|
return myCharacteristic; // for chaining or whatsoever
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* function getXXXXXXXService(config)
|
||||||
|
* returns a configured service object to the caller (accessory/device)
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* pass a configuration array parsed from config.json
|
||||||
|
* specifically for this service
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getContactSenserService: function(config) {
|
||||||
|
// Characteristic.ContactSensorState.CONTACT_DETECTED = 0;
|
||||||
|
// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1;
|
||||||
|
|
||||||
|
// some sanity checks
|
||||||
|
if (config.type !== "ContactSensor") {
|
||||||
|
this.log("[ERROR] ContactSensor Service for non 'ContactSensor' service called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
this.log("[ERROR] ContactSensor Service without 'name' property called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var myService = new Service.ContactSensor(config.name,config.name);
|
||||||
|
if (config.ContactSensorState) {
|
||||||
|
this.log("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);
|
||||||
|
}
|
||||||
|
//optionals
|
||||||
|
if (config.StatusActive) {
|
||||||
|
this.log("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");
|
||||||
|
myService.addCharacteristic(Characteristic.StatusFault);
|
||||||
|
this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault);
|
||||||
|
}
|
||||||
|
if (config.StatusTampered) {
|
||||||
|
this.log("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");
|
||||||
|
myService.addCharacteristic(Characteristic.StatusLowBattery);
|
||||||
|
this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery);
|
||||||
|
}
|
||||||
|
return myService;
|
||||||
|
},
|
||||||
|
getGarageDoorOpenerService: function(config) {
|
||||||
|
// // Required Characteristics
|
||||||
|
// this.addCharacteristic(Characteristic.CurrentDoorState);
|
||||||
|
// this.addCharacteristic(Characteristic.TargetDoorState);
|
||||||
|
// this.addCharacteristic(Characteristic.ObstructionDetected);
|
||||||
|
// Characteristic.CurrentDoorState.OPEN = 0;
|
||||||
|
// Characteristic.CurrentDoorState.CLOSED = 1;
|
||||||
|
// Characteristic.CurrentDoorState.OPENING = 2;
|
||||||
|
// Characteristic.CurrentDoorState.CLOSING = 3;
|
||||||
|
// Characteristic.CurrentDoorState.STOPPED = 4;
|
||||||
|
// //
|
||||||
|
// // Optional Characteristics
|
||||||
|
// this.addOptionalCharacteristic(Characteristic.LockCurrentState);
|
||||||
|
// this.addOptionalCharacteristic(Characteristic.LockTargetState);
|
||||||
|
// The value property of LockCurrentState must be one of the following:
|
||||||
|
// Characteristic.LockCurrentState.UNSECURED = 0;
|
||||||
|
// Characteristic.LockCurrentState.SECURED = 1;
|
||||||
|
// Characteristic.LockCurrentState.JAMMED = 2;
|
||||||
|
// Characteristic.LockCurrentState.UNKNOWN = 3;
|
||||||
|
|
||||||
|
// some sanity checks
|
||||||
|
if (config.type !== "GarageDoorOpener") {
|
||||||
|
this.log("[ERROR] GarageDoorOpener Service for non 'GarageDoorOpener' service called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
this.log("[ERROR] GarageDoorOpener Service without 'name' property called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var myService = new Service.GarageDoorOpener(config.name,config.name);
|
||||||
|
if (config.CurrentDoorState) {
|
||||||
|
this.log("GarageDoorOpener CurrentDoorState characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState);
|
||||||
|
}
|
||||||
|
if (config.TargetDoorState) {
|
||||||
|
this.log("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.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected);
|
||||||
|
}
|
||||||
|
//optionals
|
||||||
|
if (config.LockCurrentState) {
|
||||||
|
this.log("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");
|
||||||
|
myService.addCharacteristic(Characteristic.LockTargetState);
|
||||||
|
this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState);
|
||||||
|
}
|
||||||
|
return myService;
|
||||||
|
},
|
||||||
getLightbulbService: function(config) {
|
getLightbulbService: function(config) {
|
||||||
// some sanity checks
|
// some sanity checks
|
||||||
//this.config = config;
|
//this.config = config;
|
||||||
@@ -475,13 +609,32 @@ KNXDevice.prototype = {
|
|||||||
//iterate(myService);
|
//iterate(myService);
|
||||||
return myService;
|
return myService;
|
||||||
},
|
},
|
||||||
|
getLightSensorService: function(config) {
|
||||||
|
|
||||||
getLockMechanismService: function(config) {
|
|
||||||
// some sanity checks
|
// some sanity checks
|
||||||
//this.config = config;
|
if (config.type !== "LightSensor") {
|
||||||
|
this.log("[ERROR] LightSensor Service for non 'LightSensor' service called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
this.log("[ERROR] LightSensor Service without 'name' property called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
var myService = new Service.LightSensor(config.name,config.name);
|
||||||
|
// CurrentTemperature)
|
||||||
|
if (config.CurrentAmbientLightLevel) {
|
||||||
|
this.log("LightSensor CurrentAmbientLightLevel characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.CurrentAmbientLightLevel, "Float", config.CurrentAmbientLightLevel);
|
||||||
|
}
|
||||||
|
return myService;
|
||||||
|
},
|
||||||
|
getLockMechanismService: function(config) {
|
||||||
|
|
||||||
|
/** //this.config = config;
|
||||||
// Characteristic.LockCurrentState.UNSECURED = 0;
|
// Characteristic.LockCurrentState.UNSECURED = 0;
|
||||||
// Characteristic.LockCurrentState.SECURED = 1;
|
// Characteristic.LockCurrentState.SECURED = 1;
|
||||||
|
*/
|
||||||
|
// some sanity checks
|
||||||
if (config.type !== "LockMechanism") {
|
if (config.type !== "LockMechanism") {
|
||||||
this.log("[ERROR] LockMechanism Service for non 'LockMechanism' service called");
|
this.log("[ERROR] LockMechanism Service for non 'LockMechanism' service called");
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -490,6 +643,7 @@ KNXDevice.prototype = {
|
|||||||
this.log("[ERROR] LockMechanism Service without 'name' property called");
|
this.log("[ERROR] LockMechanism Service without 'name' property called");
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
var myService = new Service.LockMechanism(config.name,config.name);
|
var myService = new Service.LockMechanism(config.name,config.name);
|
||||||
// LockCurrentState
|
// LockCurrentState
|
||||||
if (config.LockCurrentState) {
|
if (config.LockCurrentState) {
|
||||||
@@ -513,28 +667,103 @@ KNXDevice.prototype = {
|
|||||||
//iterate(myService);
|
//iterate(myService);
|
||||||
return myService;
|
return myService;
|
||||||
},
|
},
|
||||||
|
getMotionSensorService: function(config) {
|
||||||
|
// Characteristic.ContactSensorState.CONTACT_DETECTED = 0;
|
||||||
getThermostatService: function(config) {
|
// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1;
|
||||||
|
|
||||||
|
|
||||||
// // Required Characteristics
|
|
||||||
// this.addCharacteristic(Characteristic.CurrentHeatingCoolingState);
|
|
||||||
// this.addCharacteristic(Characteristic.TargetHeatingCoolingState);
|
|
||||||
// this.addCharacteristic(Characteristic.CurrentTemperature); //check
|
|
||||||
// this.addCharacteristic(Characteristic.TargetTemperature); //
|
|
||||||
// this.addCharacteristic(Characteristic.TemperatureDisplayUnits);
|
|
||||||
//
|
|
||||||
// // Optional Characteristics
|
|
||||||
// this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity);
|
|
||||||
// this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity);
|
|
||||||
// this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature);
|
|
||||||
// this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature);
|
|
||||||
|
|
||||||
|
|
||||||
// some sanity checks
|
// some sanity checks
|
||||||
|
if (config.type !== "MotionSensor") {
|
||||||
|
this.log("[ERROR] MotionSensor Service for non 'MotionSensor' service called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
this.log("[ERROR] MotionSensor Service without 'name' property called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var myService = new Service.MotionSensor(config.name,config.name);
|
||||||
|
if (config.MotionDetected) {
|
||||||
|
this.log("MotionSensor MotionDetected characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected);
|
||||||
|
}
|
||||||
|
//optionals
|
||||||
|
if (config.StatusActive) {
|
||||||
|
this.log("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");
|
||||||
|
myService.addCharacteristic(Characteristic.StatusFault);
|
||||||
|
this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault);
|
||||||
|
}
|
||||||
|
if (config.StatusTampered) {
|
||||||
|
this.log("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");
|
||||||
|
myService.addCharacteristic(Characteristic.StatusLowBattery);
|
||||||
|
this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery);
|
||||||
|
}
|
||||||
|
return myService;
|
||||||
|
},
|
||||||
|
getOutletService: function(config) {
|
||||||
|
/**
|
||||||
|
* this.addCharacteristic(Characteristic.On);
|
||||||
|
* this.addCharacteristic(Characteristic.OutletInUse);
|
||||||
|
*/
|
||||||
|
// some sanity checks
|
||||||
|
if (config.type !== "Outlet") {
|
||||||
|
this.log("[ERROR] Outlet Service for non 'Outlet' service called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
this.log("[ERROR] Outlet Service without 'name' property called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
var myService = new Service.Outlet(config.name,config.name);
|
||||||
|
// On (and Off)
|
||||||
|
if (config.On) {
|
||||||
|
this.log("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.bindCharacteristic(myService, Characteristic.OutletInUse, "Bool", config.OutletInUse);
|
||||||
|
}
|
||||||
|
return myService;
|
||||||
|
},
|
||||||
|
getSwitchService: function(config) {
|
||||||
|
// some sanity checks
|
||||||
|
if (config.type !== "Switch") {
|
||||||
|
this.log("[ERROR] Switch Service for non 'Switch' service called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
this.log("[ERROR] Switch Service without 'name' property called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
var myService = new Service.Switch(config.name,config.name);
|
||||||
|
// On (and Off)
|
||||||
|
if (config.On) {
|
||||||
|
this.log("Switch on/off characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On);
|
||||||
|
} // On characteristic
|
||||||
|
|
||||||
|
return myService;
|
||||||
|
},
|
||||||
|
getThermostatService: function(config) {
|
||||||
|
/**
|
||||||
|
// Optional Characteristics
|
||||||
|
this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// some sanity checks
|
||||||
if (config.type !== "Thermostat") {
|
if (config.type !== "Thermostat") {
|
||||||
this.log("[ERROR] Thermostat Service for non 'Thermostat' service called");
|
this.log("[ERROR] Thermostat Service for non 'Thermostat' service called");
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -543,6 +772,7 @@ KNXDevice.prototype = {
|
|||||||
this.log("[ERROR] Thermostat Service without 'name' property called");
|
this.log("[ERROR] Thermostat Service without 'name' property called");
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
var myService = new Service.Thermostat(config.name,config.name);
|
var myService = new Service.Thermostat(config.name,config.name);
|
||||||
// CurrentTemperature)
|
// CurrentTemperature)
|
||||||
if (config.CurrentTemperature) {
|
if (config.CurrentTemperature) {
|
||||||
@@ -557,22 +787,21 @@ KNXDevice.prototype = {
|
|||||||
myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C
|
myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C
|
||||||
this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature);
|
this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature);
|
||||||
}
|
}
|
||||||
// HVAC missing yet
|
// HVAC
|
||||||
if (config.CurrentHeatingCoolingState) {
|
if (config.CurrentHeatingCoolingState) {
|
||||||
this.log("Thermostat CurrentHeatingCoolingState characteristic enabled");
|
this.log("Thermostat CurrentHeatingCoolingState characteristic enabled");
|
||||||
this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState);
|
this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState);
|
||||||
}
|
}
|
||||||
|
// HVAC
|
||||||
|
if (config.TargetHeatingCoolingState) {
|
||||||
|
this.log("Thermostat TargetHeatingCoolingState characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.TargetHeatingCoolingState, "HVAC", config.TargetHeatingCoolingState);
|
||||||
|
}
|
||||||
return myService;
|
return myService;
|
||||||
},
|
},
|
||||||
|
|
||||||
// temperature sensor type (iOS9 assumed)
|
|
||||||
getTemperatureSensorService: function(config) {
|
getTemperatureSensorService: function(config) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// some sanity checks
|
// some sanity checks
|
||||||
|
|
||||||
|
|
||||||
if (config.type !== "TemperatureSensor") {
|
if (config.type !== "TemperatureSensor") {
|
||||||
this.log("[ERROR] TemperatureSensor Service for non 'TemperatureSensor' service called");
|
this.log("[ERROR] TemperatureSensor Service for non 'TemperatureSensor' service called");
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -584,16 +813,91 @@ KNXDevice.prototype = {
|
|||||||
var myService = new Service.TemperatureSensor(config.name,config.name);
|
var myService = new Service.TemperatureSensor(config.name,config.name);
|
||||||
// CurrentTemperature)
|
// CurrentTemperature)
|
||||||
if (config.CurrentTemperature) {
|
if (config.CurrentTemperature) {
|
||||||
this.log("Thermostat CurrentTemperature characteristic enabled");
|
this.log("TemperatureSensor CurrentTemperature characteristic enabled");
|
||||||
this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature);
|
this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature);
|
||||||
}
|
}
|
||||||
return myService;
|
return myService;
|
||||||
},
|
},
|
||||||
|
getWindowService: function(config) {
|
||||||
|
/**
|
||||||
|
Optional Characteristics
|
||||||
|
this.addOptionalCharacteristic(Characteristic.HoldPosition);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.ObstructionDetected);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.Name);
|
||||||
|
|
||||||
|
PositionState values: The KNX blind actuators I have return only MOVING=1 and STOPPED=0
|
||||||
|
Characteristic.PositionState.DECREASING = 0;
|
||||||
|
Characteristic.PositionState.INCREASING = 1;
|
||||||
|
Characteristic.PositionState.STOPPED = 2;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// some sanity checks
|
||||||
|
|
||||||
|
|
||||||
|
if (config.type !== "Window") {
|
||||||
|
this.log("[ERROR] Window Service for non 'Window' service called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
this.log("[ERROR] Window Service without 'name' property called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
var myService = new Service.Window(config.name,config.name);
|
||||||
|
|
||||||
|
if (config.CurrentPosition) {
|
||||||
|
this.log("Window CurrentPosition characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition);
|
||||||
|
}
|
||||||
|
if (config.TargetPosition) {
|
||||||
|
this.log("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);
|
||||||
|
}
|
||||||
|
return myService;
|
||||||
|
},
|
||||||
|
getWindowCoveringService: function(config) {
|
||||||
|
/**
|
||||||
|
// Optional Characteristics
|
||||||
|
this.addOptionalCharacteristic(Characteristic.HoldPosition);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle);
|
||||||
|
this.addOptionalCharacteristic(Characteristic.ObstructionDetected);
|
||||||
|
*/
|
||||||
|
// some sanity checks
|
||||||
|
if (config.type !== "WindowCovering") {
|
||||||
|
this.log("[ERROR] WindowCovering Service for non 'WindowCovering' service called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
this.log("[ERROR] WindowCovering Service without 'name' property called");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var myService = new Service.WindowCovering(config.name,config.name);
|
||||||
|
if (config.CurrentPosition) {
|
||||||
|
this.log("WindowCovering CurrentPosition characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition);
|
||||||
|
}
|
||||||
|
if (config.TargetPosition) {
|
||||||
|
this.log("WindowCovering TargetPosition characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition);
|
||||||
|
}
|
||||||
|
if (config.PositionState) {
|
||||||
|
this.log("WindowCovering PositionState characteristic enabled");
|
||||||
|
this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState);
|
||||||
|
}
|
||||||
|
return myService;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/* assemble the device ***************************************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
|
/* assemble the device ***************************************************************************************************/
|
||||||
getServices: function() {
|
getServices: function() {
|
||||||
|
|
||||||
// you can OPTIONALLY create an information service if you wish to override
|
// you can OPTIONALLY create an information service if you wish to override
|
||||||
@@ -606,12 +910,12 @@ KNXDevice.prototype = {
|
|||||||
informationService
|
informationService
|
||||||
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
|
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
|
||||||
.setCharacteristic(Characteristic.Model, "KNX Universal Device")
|
.setCharacteristic(Characteristic.Model, "KNX Universal Device")
|
||||||
.setCharacteristic(Characteristic.SerialNumber, "Version 1.1");
|
.setCharacteristic(Characteristic.SerialNumber, "Version 1.1.2");
|
||||||
|
|
||||||
accessoryServices.push(informationService);
|
accessoryServices.push(informationService);
|
||||||
|
|
||||||
iterate(this.config);
|
//iterate(this.config);
|
||||||
// throw new Error("STOP");
|
|
||||||
if (!this.config.services){
|
if (!this.config.services){
|
||||||
this.log("No services found in accessory?!")
|
this.log("No services found in accessory?!")
|
||||||
}
|
}
|
||||||
@@ -625,21 +929,43 @@ KNXDevice.prototype = {
|
|||||||
this.log("[ERROR] must specify 'type' and 'name' properties for each service in config.json. KNX platform section fault ");
|
this.log("[ERROR] must specify 'type' and 'name' properties for each service in config.json. KNX platform section fault ");
|
||||||
throw new Error("Must specify 'type' and 'name' properties for each service in config.json");
|
throw new Error("Must specify 'type' and 'name' properties for each service in config.json");
|
||||||
}
|
}
|
||||||
|
this.log("Preparing Service: " + int + " of type "+configService.type)
|
||||||
switch (configService.type) {
|
switch (configService.type) {
|
||||||
|
case "ContactSensor":
|
||||||
|
accessoryServices.push(this.getContactSenserService(configService));
|
||||||
|
break;
|
||||||
|
case "GarageDoorOpener":
|
||||||
|
accessoryServices.push(this.getGarageDoorOpenerService(configService));
|
||||||
|
break;
|
||||||
case "Lightbulb":
|
case "Lightbulb":
|
||||||
accessoryServices.push(this.getLightbulbService(configService));
|
accessoryServices.push(this.getLightbulbService(configService));
|
||||||
break;
|
break;
|
||||||
|
case "LightSensor":
|
||||||
|
accessoryServices.push(this.getLightSensorService(configService));
|
||||||
|
break;
|
||||||
case "LockMechanism":
|
case "LockMechanism":
|
||||||
accessoryServices.push(this.getLockMechanismService(configService));
|
accessoryServices.push(this.getLockMechanismService(configService));
|
||||||
break;
|
break;
|
||||||
|
case "MotionSensor":
|
||||||
|
accessoryServices.push(this.getMotionSensorService(configService));
|
||||||
|
break;
|
||||||
|
case "Switch":
|
||||||
|
accessoryServices.push(this.getSwitchService(configService));
|
||||||
|
break;
|
||||||
case "TemperatureSensor":
|
case "TemperatureSensor":
|
||||||
accessoryServices.push(this.getTemperatureSensorService(configService));
|
accessoryServices.push(this.getTemperatureSensorService(configService));
|
||||||
break;
|
break;
|
||||||
case "Thermostat":
|
case "Thermostat":
|
||||||
accessoryServices.push(this.getThermostatService(configService));
|
accessoryServices.push(this.getThermostatService(configService));
|
||||||
break;
|
break;
|
||||||
|
case "Window":
|
||||||
|
accessoryServices.push(this.getWindowService(configService));
|
||||||
|
break;
|
||||||
|
case "WindowCovering":
|
||||||
|
accessoryServices.push(this.getWindowCoveringService(configService));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
this.log("[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 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 ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
app.js
11
app.js
@@ -191,6 +191,16 @@ function createAccessory(accessoryInstance, displayName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the setup code in a scannable format.
|
||||||
|
function printPin(pin) {
|
||||||
|
console.log("Scan this code with your HomeKit App on your iOS device:");
|
||||||
|
console.log("\x1b[30;47m%s\x1b[0m", " ");
|
||||||
|
console.log("\x1b[30;47m%s\x1b[0m", " ┌────────────┐ ");
|
||||||
|
console.log("\x1b[30;47m%s\x1b[0m", " │ " + pin + " │ ");
|
||||||
|
console.log("\x1b[30;47m%s\x1b[0m", " └────────────┘ ");
|
||||||
|
console.log("\x1b[30;47m%s\x1b[0m", " ");
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a logging function that prepends messages with the given name in [brackets].
|
// Returns a logging function that prepends messages with the given name in [brackets].
|
||||||
function createLog(name) {
|
function createLog(name) {
|
||||||
return function(message) {
|
return function(message) {
|
||||||
@@ -201,6 +211,7 @@ function createLog(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function publish() {
|
function publish() {
|
||||||
|
printPin(bridgeConfig.pin);
|
||||||
bridge.publish({
|
bridge.publish({
|
||||||
username: bridgeConfig.username || "CC:22:3D:E3:CE:30",
|
username: bridgeConfig.username || "CC:22:3D:E3:CE:30",
|
||||||
port: bridgeConfig.port || 51826,
|
port: bridgeConfig.port || 51826,
|
||||||
|
|||||||
@@ -94,7 +94,8 @@
|
|||||||
"platform": "HomeAssistant",
|
"platform": "HomeAssistant",
|
||||||
"name": "HomeAssistant",
|
"name": "HomeAssistant",
|
||||||
"host": "http://192.168.1.10:8123",
|
"host": "http://192.168.1.10:8123",
|
||||||
"password": "XXXXX"
|
"password": "XXXXX",
|
||||||
|
"supported_types": ["light", "switch", "media_player", "scene"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -204,7 +205,18 @@
|
|||||||
"window_seconds": 5,
|
"window_seconds": 5,
|
||||||
"sensor_type": "m",
|
"sensor_type": "m",
|
||||||
"inverse": false
|
"inverse": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessory": "GenericRS232Device",
|
||||||
|
"name": "Projector",
|
||||||
|
"description": "Make sure you set a 'Siri-Name' for your iOS-Device (example: 'Home Cinema') otherwise it might not work.",
|
||||||
|
"id": "TYDYMU044UVNP",
|
||||||
|
"baudrate": 9600,
|
||||||
|
"device": "/dev/tty.usbserial",
|
||||||
|
"manufacturer": "Acer",
|
||||||
|
"model_name": "H6510BD",
|
||||||
|
"on_command": "* 0 IR 001\r",
|
||||||
|
"off_command": "* 0 IR 002\r"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"color": "0.10.x",
|
"color": "0.10.x",
|
||||||
"eibd": "^0.3.1",
|
"eibd": "^0.3.1",
|
||||||
"elkington": "kevinohara80/elkington",
|
"elkington": "kevinohara80/elkington",
|
||||||
"hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#98ef550c8d6fd961741673d4b695a74dd0126eba",
|
"hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#4650e771f356a220868d873d16564a6be6603ff7",
|
||||||
"harmonyhubjs-client": "^1.1.4",
|
"harmonyhubjs-client": "^1.1.4",
|
||||||
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
|
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
|
||||||
"lifx-api": "^1.0.1",
|
"lifx-api": "^1.0.1",
|
||||||
|
|||||||
253
platforms/FibaroHC2.js
Normal file
253
platforms/FibaroHC2.js
Normal 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;
|
||||||
@@ -7,7 +7,27 @@
|
|||||||
// URL: http://home-assistant.io
|
// URL: http://home-assistant.io
|
||||||
// GitHub: https://github.com/balloob/home-assistant
|
// GitHub: https://github.com/balloob/home-assistant
|
||||||
//
|
//
|
||||||
// HA accessories supported: Lights, Switches, Media Players.
|
// HA accessories supported: Lights, Switches, Media Players, Scenes.
|
||||||
|
//
|
||||||
|
// Optional Devices - Edit the supported_types key in the config to pick which
|
||||||
|
// of the 4 types you would like to expose to HomeKit from
|
||||||
|
// Home Assistant. light, switch, media_player, scene.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Scene Support
|
||||||
|
//
|
||||||
|
// You can optionally import your Home Assistant scenes. These will appear to
|
||||||
|
// HomeKit as switches. You can simply say "turn on party time". In some cases
|
||||||
|
// scenes names are already rerved in HomeKit...like "Good Morning" and
|
||||||
|
// "Good Night". You will be able to just say "Good Morning" or "Good Night" to
|
||||||
|
// have these triggered.
|
||||||
|
//
|
||||||
|
// You might want to play with the wording to figure out what ends up working well
|
||||||
|
// for your scene names. It's also important to not populate any actual HomeKit
|
||||||
|
// scenes with the same names, as Siri will pick these instead of your Home
|
||||||
|
// Assistant scenes.
|
||||||
|
//
|
||||||
|
//
|
||||||
//
|
//
|
||||||
// Media Player Support
|
// Media Player Support
|
||||||
//
|
//
|
||||||
@@ -25,6 +45,8 @@
|
|||||||
// will need to use the same language you use to set the brighness of a light.
|
// will need to use the same language you use to set the brighness of a light.
|
||||||
// You can play around with language to see what fits best.
|
// You can play around with language to see what fits best.
|
||||||
//
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
// Examples
|
// Examples
|
||||||
//
|
//
|
||||||
// Dim the Kitchen Speaker to 40% - sets volume to 40%
|
// Dim the Kitchen Speaker to 40% - sets volume to 40%
|
||||||
@@ -37,7 +59,8 @@
|
|||||||
// "platform": "HomeAssistant",
|
// "platform": "HomeAssistant",
|
||||||
// "name": "HomeAssistant",
|
// "name": "HomeAssistant",
|
||||||
// "host": "http://192.168.1.50:8123",
|
// "host": "http://192.168.1.50:8123",
|
||||||
// "password": "xxx"
|
// "password": "xxx",
|
||||||
|
// "supported_types": ["light", "switch", "media_player", "scene"]
|
||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
//
|
//
|
||||||
@@ -56,6 +79,7 @@ function HomeAssistantPlatform(log, config){
|
|||||||
// auth info
|
// auth info
|
||||||
this.host = config["host"];
|
this.host = config["host"];
|
||||||
this.password = config["password"];
|
this.password = config["password"];
|
||||||
|
this.supportedTypes = config["supported_types"];
|
||||||
|
|
||||||
this.log = log;
|
this.log = log;
|
||||||
}
|
}
|
||||||
@@ -121,22 +145,26 @@ HomeAssistantPlatform.prototype = {
|
|||||||
|
|
||||||
var that = this;
|
var that = this;
|
||||||
var foundAccessories = [];
|
var foundAccessories = [];
|
||||||
var lightsRE = /^light\./i
|
|
||||||
var switchRE = /^switch\./i
|
|
||||||
var mediaPlayerRE = /^media_player\./i
|
|
||||||
|
|
||||||
|
|
||||||
this._request('GET', '/states', {}, function(error, response, data){
|
this._request('GET', '/states', {}, function(error, response, data){
|
||||||
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
for (var i = 0; i < data.length; i++) {
|
||||||
entity = data[i]
|
entity = data[i]
|
||||||
|
entity_type = entity.entity_id.split('.')[0]
|
||||||
|
|
||||||
|
if (that.supportedTypes.indexOf(entity_type) == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var accessory = null
|
var accessory = null
|
||||||
|
|
||||||
if (entity.entity_id.match(lightsRE)) {
|
if (entity_type == 'light') {
|
||||||
accessory = new HomeAssistantLight(that.log, entity, that)
|
accessory = new HomeAssistantLight(that.log, entity, that)
|
||||||
}else if (entity.entity_id.match(switchRE)){
|
}else if (entity_type == 'switch'){
|
||||||
accessory = new HomeAssistantSwitch(that.log, entity, that)
|
accessory = new HomeAssistantSwitch(that.log, entity, that)
|
||||||
}else if (entity.entity_id.match(mediaPlayerRE) && entity.attributes && entity.attributes.supported_media_commands){
|
}else if (entity_type == 'scene'){
|
||||||
|
accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene')
|
||||||
|
}else if (entity_type == 'media_player' && entity.attributes && entity.attributes.supported_media_commands){
|
||||||
accessory = new HomeAssistantMediaPlayer(that.log, entity, that)
|
accessory = new HomeAssistantMediaPlayer(that.log, entity, that)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +268,12 @@ HomeAssistantLight.prototype = {
|
|||||||
},
|
},
|
||||||
getServices: function() {
|
getServices: function() {
|
||||||
var lightbulbService = new Service.Lightbulb();
|
var lightbulbService = new Service.Lightbulb();
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "Home Assistant")
|
||||||
|
.setCharacteristic(Characteristic.Model, "Light")
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, "xxx");
|
||||||
|
|
||||||
lightbulbService
|
lightbulbService
|
||||||
.getCharacteristic(Characteristic.On)
|
.getCharacteristic(Characteristic.On)
|
||||||
@@ -251,7 +285,7 @@ HomeAssistantLight.prototype = {
|
|||||||
.on('get', this.getBrightness.bind(this))
|
.on('get', this.getBrightness.bind(this))
|
||||||
.on('set', this.setBrightness.bind(this));
|
.on('set', this.setBrightness.bind(this));
|
||||||
|
|
||||||
return [lightbulbService];
|
return [informationService, lightbulbService];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -375,6 +409,12 @@ HomeAssistantMediaPlayer.prototype = {
|
|||||||
},
|
},
|
||||||
getServices: function() {
|
getServices: function() {
|
||||||
var lightbulbService = new Service.Lightbulb();
|
var lightbulbService = new Service.Lightbulb();
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
|
||||||
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "Home Assistant")
|
||||||
|
.setCharacteristic(Characteristic.Model, "Media Player")
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, "xxx");
|
||||||
|
|
||||||
lightbulbService
|
lightbulbService
|
||||||
.getCharacteristic(Characteristic.On)
|
.getCharacteristic(Characteristic.On)
|
||||||
@@ -389,15 +429,15 @@ HomeAssistantMediaPlayer.prototype = {
|
|||||||
.on('set', this.setVolume.bind(this));
|
.on('set', this.setVolume.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [lightbulbService];
|
return [informationService, lightbulbService];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function HomeAssistantSwitch(log, data, client) {
|
function HomeAssistantSwitch(log, data, client, type) {
|
||||||
// device info
|
// device info
|
||||||
this.domain = "switch"
|
this.domain = type || "switch"
|
||||||
this.data = data
|
this.data = data
|
||||||
this.entity_id = data.entity_id
|
this.entity_id = data.entity_id
|
||||||
if (data.attributes && data.attributes.friendly_name) {
|
if (data.attributes && data.attributes.friendly_name) {
|
||||||
@@ -454,13 +494,35 @@ HomeAssistantSwitch.prototype = {
|
|||||||
},
|
},
|
||||||
getServices: function() {
|
getServices: function() {
|
||||||
var switchService = new Service.Switch();
|
var switchService = new Service.Switch();
|
||||||
|
var informationService = new Service.AccessoryInformation();
|
||||||
|
var model;
|
||||||
|
|
||||||
switchService
|
switch (this.domain) {
|
||||||
.getCharacteristic(Characteristic.On)
|
case "scene":
|
||||||
.on('get', this.getPowerState.bind(this))
|
model = "Scene"
|
||||||
.on('set', this.setPowerState.bind(this));
|
break;
|
||||||
|
default:
|
||||||
|
model = "Switch"
|
||||||
|
}
|
||||||
|
|
||||||
return [switchService];
|
informationService
|
||||||
|
.setCharacteristic(Characteristic.Manufacturer, "Home Assistant")
|
||||||
|
.setCharacteristic(Characteristic.Model, model)
|
||||||
|
.setCharacteristic(Characteristic.SerialNumber, "xxx");
|
||||||
|
|
||||||
|
if (this.domain == 'switch') {
|
||||||
|
switchService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('get', this.getPowerState.bind(this))
|
||||||
|
.on('set', this.setPowerState.bind(this));
|
||||||
|
|
||||||
|
}else{
|
||||||
|
switchService
|
||||||
|
.getCharacteristic(Characteristic.On)
|
||||||
|
.on('set', this.setPowerState.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [informationService, switchService];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
},
|
},
|
||||||
"description": "This is an example configuration file for KNX platform shim",
|
"description": "This is an example configuration file for KNX platform shim",
|
||||||
"hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration",
|
"hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration",
|
||||||
|
"hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!",
|
||||||
|
"hint3": "For valid services and their characteristics have a look at the knxdevice.md file in folder accessories!",
|
||||||
"platforms": [
|
"platforms": [
|
||||||
{
|
{
|
||||||
"platform": "KNX",
|
"platform": "KNX",
|
||||||
@@ -16,7 +18,7 @@
|
|||||||
"accessories": [
|
"accessories": [
|
||||||
{
|
{
|
||||||
"accessory_type": "knxdevice",
|
"accessory_type": "knxdevice",
|
||||||
"description": "Only generic type knxdevice is supported, all previous knx type have been merged into that.",
|
"description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.",
|
||||||
"name": "Living Room North Lamp",
|
"name": "Living Room North Lamp",
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
@@ -72,7 +74,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"accessory_type": "knxdevice",
|
"accessory_type": "knxdevice",
|
||||||
"description":"sample device with multiple services. Multiple services of different types are widely supported",
|
"description": "sample device with multiple services. Multiple services of different types are widely supported",
|
||||||
"name": "Office",
|
"name": "Office",
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
@@ -100,16 +102,47 @@
|
|||||||
"type": "WindowCovering",
|
"type": "WindowCovering",
|
||||||
"description": "iOS9 Window covering (blinds etc) type, still WIP",
|
"description": "iOS9 Window covering (blinds etc) type, still WIP",
|
||||||
"name": "Blinds",
|
"name": "Blinds",
|
||||||
"Target": {
|
"TargetPosition": {
|
||||||
"Set": "address",
|
"Set": "1/2/3",
|
||||||
"Listen": "adresses"
|
"Listen": "1/2/4"
|
||||||
},
|
},
|
||||||
"Current": {
|
"CurrentPosition": {
|
||||||
"Set": "address",
|
"Set": "1/3/1",
|
||||||
"Listen": "adresses"
|
"Listen": "1/3/2"
|
||||||
},
|
},
|
||||||
"PositionState": {
|
"PositionState": {
|
||||||
"Listen": "adresses"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
170
platforms/KNX.md
Normal file
170
platforms/KNX.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Syntax of the config.json
|
||||||
|
In the platforms section, you can insert a KNX type platform.
|
||||||
|
You need to configure all devices directly in the config.json.
|
||||||
|
````json
|
||||||
|
"platforms": [
|
||||||
|
{
|
||||||
|
"platform": "KNX",
|
||||||
|
"name": "KNX",
|
||||||
|
"knxd_ip": "192.168.178.205",
|
||||||
|
"knxd_port": 6720,
|
||||||
|
"accessories": [
|
||||||
|
{
|
||||||
|
"accessory_type": "knxdevice",
|
||||||
|
"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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
````
|
||||||
|
In the accessories section (the array within the brackets [ ]) you can insert as many objects as you like in the following form
|
||||||
|
````json
|
||||||
|
{
|
||||||
|
"accessory_type": "knxdevice",
|
||||||
|
"name": "Here goes your display name, this will be shown in HomeKit apps",
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
````
|
||||||
|
You have to add services in the following syntax:
|
||||||
|
````json
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"CHARACTERISTIC1": {
|
||||||
|
"Set": "1/1/6",
|
||||||
|
"Listen": [
|
||||||
|
"1/1/63"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CHARACTERISTIC2": {
|
||||||
|
"Set": "1/1/62",
|
||||||
|
"Listen": [
|
||||||
|
"1/1/64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
````
|
||||||
|
`CHARACTERISTICx` are properties that are dependent on the service type, so they are listed below.
|
||||||
|
|
||||||
|
Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group address, to which changes are sent if the service supports changing values. Changes on the bus are listened to, too.
|
||||||
|
`"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
|
||||||
|
|
||||||
|
## ContactSensor
|
||||||
|
- ContactSensorState: DPT 1.002, 0 as contact **OR**
|
||||||
|
- ContactSensorStateContact1: DPT 1.002, 1 as contact
|
||||||
|
|
||||||
|
- StatusActive: DPT 1.011, 1 as true
|
||||||
|
- StatusFault: DPT 1.011, 1 as true
|
||||||
|
- StatusTampered: DPT 1.011, 1 as true
|
||||||
|
- StatusLowBattery: DPT 1.011, 1 as true
|
||||||
|
|
||||||
|
## GarageDoorOpener
|
||||||
|
- CurrentDoorState: DPT5 integer value in range 0..4
|
||||||
|
// Characteristic.CurrentDoorState.OPEN = 0;
|
||||||
|
// Characteristic.CurrentDoorState.CLOSED = 1;
|
||||||
|
// Characteristic.CurrentDoorState.OPENING = 2;
|
||||||
|
// Characteristic.CurrentDoorState.CLOSING = 3;
|
||||||
|
// Characteristic.CurrentDoorState.STOPPED = 4;
|
||||||
|
|
||||||
|
- TargetDoorState: DPT5 integer value in range 0..1
|
||||||
|
// Characteristic.TargetDoorState.OPEN = 0;
|
||||||
|
// Characteristic.TargetDoorState.CLOSED = 1;
|
||||||
|
|
||||||
|
- ObstructionDetected: DPT1, 1 as true
|
||||||
|
|
||||||
|
- LockCurrentState: DPT5 integer value in range 0..3
|
||||||
|
// Characteristic.LockCurrentState.UNSECURED = 0;
|
||||||
|
// Characteristic.LockCurrentState.SECURED = 1;
|
||||||
|
// Characteristic.LockCurrentState.JAMMED = 2;
|
||||||
|
// Characteristic.LockCurrentState.UNKNOWN = 3;
|
||||||
|
|
||||||
|
- LockTargetState: DPT5 integer value in range 0..1
|
||||||
|
// Characteristic.LockTargetState.UNSECURED = 0;
|
||||||
|
// Characteristic.LockTargetState.SECURED = 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Lightbulb
|
||||||
|
- On: DPT 1.001, 1 as on, 0 as off
|
||||||
|
- Brightness: DPT5.001 percentage, 100% (=255) the brightest
|
||||||
|
|
||||||
|
## LightSensor
|
||||||
|
- 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
|
||||||
|
|
||||||
|
*ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3*
|
||||||
|
|
||||||
|
## MotionSensor
|
||||||
|
- MotionDetected: DPT 1.002, 1 as motion detected
|
||||||
|
|
||||||
|
- StatusActive: DPT 1.011, 1 as true
|
||||||
|
- StatusFault: DPT 1.011, 1 as true
|
||||||
|
- StatusTampered: DPT 1.011, 1 as true
|
||||||
|
- StatusLowBattery: DPT 1.011, 1 as true
|
||||||
|
|
||||||
|
## Outlet
|
||||||
|
- On: DPT 1.001, 1 as on, 0 as off
|
||||||
|
- OutletInUse: DPT 1.011, 1 as on, 0 as off
|
||||||
|
|
||||||
|
## Switch
|
||||||
|
- On: DPT 1.001, 1 as on, 0 as off
|
||||||
|
|
||||||
|
## TemperatureSensor
|
||||||
|
- 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
|
||||||
|
- CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only]
|
||||||
|
- TargetHeatingCoolingState: DPT20.102 HVAC, as above
|
||||||
|
|
||||||
|
## Window
|
||||||
|
- CurrentPosition: DPT5.001 percentage
|
||||||
|
- TargetPosition: DPT5.001 percentage
|
||||||
|
- PositionState: DPT5.005 value [listen only: 0 Increasing, 1 Decreasing, 2 Stopped]
|
||||||
|
|
||||||
|
## WindowCovering
|
||||||
|
- CurrentPosition: DPT5 percentage
|
||||||
|
- TargetPosition: DPT5 percentage
|
||||||
|
- PositionState: DPT5 value [listen only]
|
||||||
|
|
||||||
|
### not yet supported
|
||||||
|
- HoldPosition
|
||||||
|
- TargetHorizontalTiltAngle
|
||||||
|
- TargetVerticalTiltAngle
|
||||||
|
- CurrentHorizontalTiltAngle
|
||||||
|
- CurrentVerticalTiltAngle
|
||||||
|
- ObstructionDetected
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# DISCLAIMER
|
||||||
|
**This is work in progress!**
|
||||||
|
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
var types = require("HAP-NodeJS/accessories/types.js");
|
var types = require("HAP-NodeJS/accessories/types.js");
|
||||||
|
var inherits = require('util').inherits;
|
||||||
|
var debug = require('debug')('YamahaAVR');
|
||||||
|
var Service = require("HAP-NodeJS").Service;
|
||||||
|
var Characteristic = require("HAP-NodeJS").Characteristic;
|
||||||
var Yamaha = require('yamaha-nodejs');
|
var Yamaha = require('yamaha-nodejs');
|
||||||
|
var Q = require('q');
|
||||||
var mdns = require('mdns');
|
var mdns = require('mdns');
|
||||||
//workaround for raspberry pi
|
//workaround for raspberry pi
|
||||||
var sequence = [
|
var sequence = [
|
||||||
@@ -12,10 +17,53 @@ function YamahaAVRPlatform(log, config){
|
|||||||
this.log = log;
|
this.log = log;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.playVolume = config["play_volume"];
|
this.playVolume = config["play_volume"];
|
||||||
|
this.minVolume = config["min_volume"] || -50.0;
|
||||||
|
this.maxVolume = config["max_volume"] || -20.0;
|
||||||
|
this.gapVolume = this.maxVolume - this.minVolume;
|
||||||
this.setMainInputTo = config["setMainInputTo"];
|
this.setMainInputTo = config["setMainInputTo"];
|
||||||
|
this.expectedDevices = config["expected_devices"] || 100;
|
||||||
|
this.discoveryTimeout = config["discovery_timeout"] || 30;
|
||||||
this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence});
|
this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom Characteristics and service...
|
||||||
|
|
||||||
|
YamahaAVRPlatform.AudioVolume = function() {
|
||||||
|
Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377');
|
||||||
|
this.format = 'uint8';
|
||||||
|
this.unit = 'percentage';
|
||||||
|
this.maximumValue = 100;
|
||||||
|
this.minimumValue = 0;
|
||||||
|
this.stepValue = 1;
|
||||||
|
this.readable = true;
|
||||||
|
this.writable = true;
|
||||||
|
this.supportsEventNotification = true;
|
||||||
|
this.value = this.getDefaultValue();
|
||||||
|
};
|
||||||
|
inherits(YamahaAVRPlatform.AudioVolume, Characteristic);
|
||||||
|
|
||||||
|
YamahaAVRPlatform.Muting = function() {
|
||||||
|
Characteristic.call(this, 'Muting', '00001002-0000-1000-8000-135D67EC4377');
|
||||||
|
this.format = 'bool';
|
||||||
|
this.readable = true;
|
||||||
|
this.writable = true;
|
||||||
|
this.supportsEventNotification = true;
|
||||||
|
this.value = this.getDefaultValue();
|
||||||
|
};
|
||||||
|
inherits(YamahaAVRPlatform.Muting, Characteristic);
|
||||||
|
|
||||||
|
YamahaAVRPlatform.AudioDeviceService = function(displayName, subtype) {
|
||||||
|
Service.call(this, displayName, '00000001-0000-1000-8000-135D67EC4377', subtype);
|
||||||
|
|
||||||
|
// Required Characteristics
|
||||||
|
this.addCharacteristic(YamahaAVRPlatform.AudioVolume);
|
||||||
|
|
||||||
|
// Optional Characteristics
|
||||||
|
this.addOptionalCharacteristic(YamahaAVRPlatform.Muting);
|
||||||
|
};
|
||||||
|
inherits(YamahaAVRPlatform.AudioDeviceService, Service);
|
||||||
|
|
||||||
|
|
||||||
YamahaAVRPlatform.prototype = {
|
YamahaAVRPlatform.prototype = {
|
||||||
accessories: function(callback) {
|
accessories: function(callback) {
|
||||||
this.log("Getting Yamaha AVR devices.");
|
this.log("Getting Yamaha AVR devices.");
|
||||||
@@ -24,6 +72,8 @@ YamahaAVRPlatform.prototype = {
|
|||||||
var browser = this.browser;
|
var browser = this.browser;
|
||||||
browser.stop();
|
browser.stop();
|
||||||
browser.removeAllListeners('serviceUp'); // cleanup listeners
|
browser.removeAllListeners('serviceUp'); // cleanup listeners
|
||||||
|
var accessories = [];
|
||||||
|
var timer, timeElapsed = 0, checkCyclePeriod = 5000;
|
||||||
|
|
||||||
browser.on('serviceUp', function(service){
|
browser.on('serviceUp', function(service){
|
||||||
var name = service.name;
|
var name = service.name;
|
||||||
@@ -36,12 +86,36 @@ YamahaAVRPlatform.prototype = {
|
|||||||
var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0];
|
var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0];
|
||||||
that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\"");
|
that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\"");
|
||||||
var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig);
|
var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig);
|
||||||
callback([accessory]);
|
accessories.push(accessory);
|
||||||
|
if(accessories.length >= this.expectedDevices)
|
||||||
|
timeoutFunction(); // We're done, call the timeout function now.
|
||||||
|
//callback([accessory]);
|
||||||
}, function(err){
|
}, function(err){
|
||||||
return;
|
return;
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
browser.start();
|
browser.start();
|
||||||
|
|
||||||
|
// The callback can only be called once...so we'll have to find as many as we can
|
||||||
|
// in a fixed time and then call them in.
|
||||||
|
var timeoutFunction = function(){
|
||||||
|
if(accessories.length >= that.expectedDevices){
|
||||||
|
clearTimeout(timer);
|
||||||
|
} else {
|
||||||
|
timeElapsed += checkCyclePeriod;
|
||||||
|
if(timeElapsed > that.discoveryTimeout * 1000){
|
||||||
|
that.log("Waited " + that.discoveryTimeout + " seconds, stopping discovery.");
|
||||||
|
} else {
|
||||||
|
timer = setTimeout(timeoutFunction, checkCyclePeriod);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
browser.stop();
|
||||||
|
browser.removeAllListeners('serviceUp');
|
||||||
|
that.log("Discovery finished, found " + accessories.length + " Yamaha AVR devices.");
|
||||||
|
callback(accessories);
|
||||||
|
};
|
||||||
|
timer = setTimeout(timeoutFunction, checkCyclePeriod);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,6 +130,9 @@ function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) {
|
|||||||
this.serviceName = mdnsService.name + " Speakers";
|
this.serviceName = mdnsService.name + " Speakers";
|
||||||
this.setMainInputTo = config["setMainInputTo"];
|
this.setMainInputTo = config["setMainInputTo"];
|
||||||
this.playVolume = this.config["play_volume"];
|
this.playVolume = this.config["play_volume"];
|
||||||
|
this.minVolume = config["min_volume"] || -50.0;
|
||||||
|
this.maxVolume = config["max_volume"] || -20.0;
|
||||||
|
this.gapVolume = this.maxVolume - this.minVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
YamahaAVRAccessory.prototype = {
|
YamahaAVRAccessory.prototype = {
|
||||||
@@ -66,104 +143,74 @@ YamahaAVRAccessory.prototype = {
|
|||||||
|
|
||||||
if (playing) {
|
if (playing) {
|
||||||
|
|
||||||
yamaha.powerOn().then(function(){
|
return yamaha.powerOn().then(function(){
|
||||||
if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10);
|
if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10);
|
||||||
else return { then: function(f, r){ f(); } };
|
else return Q();
|
||||||
}).then(function(){
|
}).then(function(){
|
||||||
if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo);
|
if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo);
|
||||||
else return { then: function(f, r){ f(); } };
|
else return Q();
|
||||||
}).then(function(){
|
}).then(function(){
|
||||||
if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver(
|
if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver(
|
||||||
'<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>'
|
'<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>'
|
||||||
);
|
);
|
||||||
else return { then: function(f, r){ f(); } };
|
else return Q();
|
||||||
//else return Promise.fulfilled(undefined);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
yamaha.powerOff();
|
return yamaha.powerOff();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getServices: function() {
|
getServices: function() {
|
||||||
var that = this;
|
var that = this;
|
||||||
return [{
|
var informationService = new Service.AccessoryInformation();
|
||||||
sType: types.ACCESSORY_INFORMATION_STYPE,
|
var yamaha = this.yamaha;
|
||||||
characteristics: [{
|
|
||||||
cType: types.NAME_CTYPE,
|
informationService
|
||||||
onUpdate: null,
|
.setCharacteristic(Characteristic.Name, this.name)
|
||||||
perms: ["pr"],
|
.setCharacteristic(Characteristic.Manufacturer, "Yamaha")
|
||||||
format: "string",
|
.setCharacteristic(Characteristic.Model, this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0])
|
||||||
initialValue: this.name,
|
.setCharacteristic(Characteristic.SerialNumber, this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]);
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
var switchService = new Service.Switch("Power State");
|
||||||
manfDescription: "Name of the accessory",
|
switchService.getCharacteristic(Characteristic.On)
|
||||||
designedMaxLength: 255
|
.on('get', function(callback, context){
|
||||||
},{
|
yamaha.isOn().then(function(result){
|
||||||
cType: types.MANUFACTURER_CTYPE,
|
callback(false, result);
|
||||||
onUpdate: null,
|
}.bind(this));
|
||||||
perms: ["pr"],
|
}.bind(this))
|
||||||
format: "string",
|
.on('set', function(powerOn, callback){
|
||||||
initialValue: "Yamaha",
|
this.setPlaying(powerOn).then(function(){
|
||||||
supportEvents: false,
|
callback(false, powerOn);
|
||||||
supportBonjour: false,
|
}, function(error){
|
||||||
manfDescription: "Manufacturer",
|
callback(error, !powerOn); //TODO: Actually determine and send real new status.
|
||||||
designedMaxLength: 255
|
});
|
||||||
},{
|
}.bind(this));
|
||||||
cType: types.MODEL_CTYPE,
|
|
||||||
onUpdate: null,
|
var audioDeviceService = new YamahaAVRPlatform.AudioDeviceService("Audio Functions");
|
||||||
perms: ["pr"],
|
audioDeviceService.getCharacteristic(YamahaAVRPlatform.AudioVolume)
|
||||||
format: "string",
|
.on('get', function(callback, context){
|
||||||
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0],
|
yamaha.getBasicInfo().done(function(basicInfo){
|
||||||
supportEvents: false,
|
var v = basicInfo.getVolume()/10.0;
|
||||||
supportBonjour: false,
|
var p = 100 * ((v - that.minVolume) / that.gapVolume);
|
||||||
manfDescription: "Model",
|
p = p < 0 ? 0 : p > 100 ? 100 : Math.round(p);
|
||||||
designedMaxLength: 255
|
debug("Got volume percent of " + p + "%");
|
||||||
},{
|
callback(false, p);
|
||||||
cType: types.SERIAL_NUMBER_CTYPE,
|
});
|
||||||
onUpdate: null,
|
})
|
||||||
perms: ["pr"],
|
.on('set', function(p, callback){
|
||||||
format: "string",
|
var v = ((p / 100) * that.gapVolume) + that.minVolume;
|
||||||
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0],
|
v = Math.round(v*10.0);
|
||||||
supportEvents: false,
|
debug("Setting volume to " + v);
|
||||||
supportBonjour: false,
|
yamaha.setVolumeTo(v).then(function(){
|
||||||
manfDescription: "SN",
|
callback(false, p);
|
||||||
designedMaxLength: 255
|
});
|
||||||
},{
|
})
|
||||||
cType: types.IDENTIFY_CTYPE,
|
.getValue(null, null); // force an asynchronous get
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pw"],
|
|
||||||
format: "bool",
|
return [informationService, switchService, audioDeviceService];
|
||||||
initialValue: false,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Identify Accessory",
|
|
||||||
designedMaxLength: 1
|
|
||||||
}]
|
|
||||||
},{
|
|
||||||
sType: types.SWITCH_STYPE,
|
|
||||||
characteristics: [{
|
|
||||||
cType: types.NAME_CTYPE,
|
|
||||||
onUpdate: null,
|
|
||||||
perms: ["pr"],
|
|
||||||
format: "string",
|
|
||||||
initialValue: this.serviceName,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Name of service",
|
|
||||||
designedMaxLength: 255
|
|
||||||
},{
|
|
||||||
cType: types.POWER_STATE_CTYPE,
|
|
||||||
onUpdate: function(value) { that.setPlaying(value); },
|
|
||||||
perms: ["pw","pr","ev"],
|
|
||||||
format: "bool",
|
|
||||||
initialValue: false,
|
|
||||||
supportEvents: false,
|
|
||||||
supportBonjour: false,
|
|
||||||
manfDescription: "Change the playback state of the Yamaha AV Receiver",
|
|
||||||
designedMaxLength: 1
|
|
||||||
}]
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -90,11 +90,11 @@ ZWayServerPlatform.prototype = {
|
|||||||
//TODO: Unify this with getVDevServices, so there's only one place with mapping between service and vDev type.
|
//TODO: Unify this with getVDevServices, so there's only one place with mapping between service and vDev type.
|
||||||
//Note: Order matters!
|
//Note: Order matters!
|
||||||
var primaryDeviceClasses = [
|
var primaryDeviceClasses = [
|
||||||
"switchBinary",
|
|
||||||
"thermostat",
|
"thermostat",
|
||||||
"sensorBinary.Door/Window",
|
"switchMultilevel",
|
||||||
|
"switchBinary",
|
||||||
|
"sensorBinary.Door/Window"
|
||||||
"sensorMultilevel.Temperature",
|
"sensorMultilevel.Temperature",
|
||||||
"switchMultilevel"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var that = this;
|
var that = this;
|
||||||
@@ -255,21 +255,21 @@ ZWayServerAccessory.prototype = {
|
|||||||
var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev);
|
var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev);
|
||||||
var services = [], service;
|
var services = [], service;
|
||||||
switch (typeKey) {
|
switch (typeKey) {
|
||||||
|
case "thermostat":
|
||||||
|
services.push(new Service.Thermostat(vdev.metrics.title));
|
||||||
|
break;
|
||||||
case "switchBinary":
|
case "switchBinary":
|
||||||
services.push(new Service.Switch(vdev.metrics.title, vdev.id));
|
services.push(new Service.Switch(vdev.metrics.title));
|
||||||
break;
|
break;
|
||||||
case "switchMultilevel":
|
case "switchMultilevel":
|
||||||
services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id));
|
services.push(new Service.Lightbulb(vdev.metrics.title));
|
||||||
break;
|
break;
|
||||||
case "thermostat":
|
case "sensorBinary.Door/Window":
|
||||||
services.push(new Service.Thermostat(vdev.metrics.title, vdev.id));
|
services.push(new Service.GarageDoorOpener(vdev.metrics.title));
|
||||||
break;
|
break;
|
||||||
case "sensorMultilevel.Temperature":
|
case "sensorMultilevel.Temperature":
|
||||||
services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id));
|
services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id));
|
||||||
break;
|
break;
|
||||||
case "sensorBinary.Door/Window":
|
|
||||||
services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id));
|
|
||||||
break;
|
|
||||||
case "battery.Battery":
|
case "battery.Battery":
|
||||||
services.push(new Service.BatteryService(vdev.metrics.title, vdev.id));
|
services.push(new Service.BatteryService(vdev.metrics.title, vdev.id));
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user