Merge remote-tracking branch 'nfarina/master' into zway-rgb

Conflicts:
	platforms/ZWayServer.js
This commit is contained in:
S'pht'Kr
2015-09-29 06:58:06 +02:00
13 changed files with 1201 additions and 226 deletions

View File

@@ -52,13 +52,18 @@ You'll also need some patience, as Siri can be very strict about sentence struct
# Getting Started
OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. First, clone this repo:
OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up.
**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed.
First, clone this repo:
$ git clone https://github.com/nfarina/homebridge.git
$ cd homebridge
$ npm install
**Node**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load.
**Note**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load.
Now you should be able to run the homebridge server:

View 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;

View File

@@ -140,9 +140,7 @@ WeMoAccessory.prototype.getServices = function() {
garageDoorService
.getCharacteristic(Characteristic.TargetDoorState)
.on('set', this.setTargetDoorState.bind(this))
.supportsEventNotification = false;
.on('set', this.setTargetDoorState.bind(this));
return [garageDoorService];
}

View File

@@ -1,6 +1,19 @@
/*
/**
* 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;
@@ -23,7 +36,7 @@ function KNXDevice(log, config) {
if (config.knxd_ip){
this.knxd_ip = config.knxd_ip;
} else {
throw new Error("MISSING KNXD IP");
throw new Error("KNX configuration fault: MISSING KNXD IP");
}
if (config.knxd_port){
this.knxd_port = config.knxd_port;
@@ -87,7 +100,7 @@ KNXDevice.prototype = {
this.log("[ERROR] knxwrite:sendAPDU: " + err);
callback(err);
} else {
// this.log("knx data sent");
this.log("knx data sent: Value "+value+ " for GA "+groupAddress);
callback();
}
}.bind(this));
@@ -95,7 +108,6 @@ KNXDevice.prototype = {
}.bind(this));
}.bind(this));
},
// 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
knxread: function(groupAddress){
@@ -125,7 +137,6 @@ KNXDevice.prototype = {
}.bind(this));
}.bind(this));
},
// issuing multiple read requests at once
knxreadarray: function (groupAddresses) {
if (groupAddresses.constructor.toString().indexOf("Array") > -1) {
@@ -141,26 +152,14 @@ KNXDevice.prototype = {
}
},
// special types
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
/** Registering routines
*
*/
// boolean: get 0 or 1 from the bus, write boolean
knxregister_bool: function(addresses, characteristic) {
this.log("knx registering BOOLEAN " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type + " for " + characteristic.displayName);
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName);
// iterate(characteristic);
characteristic.setValue(val ? 1 : 0, undefined, 'fromKNXBus');
}.bind(this));
@@ -168,7 +167,7 @@ KNXDevice.prototype = {
knxregister_boolReverse: function(addresses, characteristic) {
this.log("knx registering BOOLEAN " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type + " for " + characteristic.displayName);
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName);
// iterate(characteristic);
characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus');
}.bind(this));
@@ -177,7 +176,7 @@ KNXDevice.prototype = {
knxregister_percent: function(addresses, characteristic) {
this.log("knx registering PERCENT " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
if (type !== "DPT5") {
this.log("[ERROR] Received value cannot be a percentage value");
} else {
@@ -194,27 +193,35 @@ KNXDevice.prototype = {
}
}.bind(this));
},
// float
knxregister_float: function(addresses, characteristic) {
this.log("knx registering FLOAT " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
var hk_value = Math.round(val*10)/10;
if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) {
characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decoimal for HomeKit
characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit
} else {
this.log("Value %s out of bounds %s...%s ",hk_value, characteristic.minimumValue, characteristic.maximumValue);
}
}.bind(this));
},
// what about HVAC heating cooling types?
//integer
knxregister_int: function(addresses, characteristic) {
this.log("knx registering FLOAT " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
if (val>=(characteristic.minimumValue || 0) && val<=(characteristic.maximumValue || 255)) {
characteristic.setValue(val, undefined, 'fromKNXBus');
} 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) {
this.log("knx registering HVAC " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
var HAPvalue = 0;
switch (val){
case 0:
@@ -238,7 +245,7 @@ KNXDevice.prototype = {
characteristic.setValue(HAPvalue, undefined, 'fromKNXBus');
}.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
// 1 = Comfort
// 2 = Standby
@@ -250,25 +257,25 @@ KNXDevice.prototype = {
// Characteristic.TargetHeatingCoolingState.HEAT = 1;
// Characteristic.TargetHeatingCoolingState.COOL = 2;
// Characteristic.TargetHeatingCoolingState.AUTO = 3;
AUTO (3) is not allowed as return type from devices!
*/
// undefined, has to match!
knxregister: function(addresses, characteristic) {
this.log("knx registering " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName);
characteristic.setValue(val, undefined, 'fromKNXBus');
}.bind(this));
},
/*
* set methods used for creating callbacks, such as
* var Characteristic = myService.addCharacteristic(new Characteristic.Brightness())
* .on('set', function(value, callback, context) {
* this.setPercentage(value, callback, context, this.config[index].Set)
* }.bind(this));
*
*/
/** set methods used for creating callbacks
* such as
* var Characteristic = myService.addCharacteristic(new Characteristic.Brightness())
* .on('set', function(value, callback, context) {
* this.setPercentage(value, callback, context, this.config[index].Set)
* }.bind(this));
*
*/
setBooleanState: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
@@ -301,7 +308,6 @@ KNXDevice.prototype = {
}
},
setPercentage: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log("event ping pong, exit!");
@@ -317,7 +323,21 @@ KNXDevice.prototype = {
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) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
@@ -333,7 +353,6 @@ KNXDevice.prototype = {
this.knxwrite(callback, gaddress,'DPT9',numericValue);
}
},
setHVACState: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
@@ -364,21 +383,16 @@ KNXDevice.prototype = {
}
},
/** identify dummy
*
*/
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
/*
* function getXXXXXXXService(config)
*
* returns a configured service object to the caller (accessory/device)
*
*/
/** bindCharacteristic
* initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK)
*/
bindCharacteristic: function(myService, characteristicType, valueType, config) {
var myCharacteristic = myService.getCharacteristic(characteristicType);
if (myCharacteristic === undefined) {
@@ -408,14 +422,20 @@ KNXDevice.prototype = {
this.setFloat(value, callback, context, config.Set);
}.bind(this));
break;
case "Int":
myCharacteristic.on('set', function(value, callback, context) {
this.setInt(value, callback, context, config.Set);
}.bind(this));
break;
case "HVAC":
myCharacteristic.on('set', function(value, callback, context) {
this.setHVACState(value, callback, context, config.Set);
}.bind(this));
break;
default:
default: {
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) {
@@ -434,6 +454,9 @@ KNXDevice.prototype = {
case "Float":
this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic);
break;
case "Int":
this.knxregister_int([config.Set].concat(config.Listen || []), myCharacteristic);
break;
case "HVAC":
this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic);
break;
@@ -446,7 +469,118 @@ KNXDevice.prototype = {
}
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) {
// some sanity checks
//this.config = config;
@@ -475,13 +609,32 @@ KNXDevice.prototype = {
//iterate(myService);
return myService;
},
getLightSensorService: function(config) {
getLockMechanismService: function(config) {
// 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.SECURED = 1;
*/
// some sanity checks
if (config.type !== "LockMechanism") {
this.log("[ERROR] LockMechanism Service for non 'LockMechanism' service called");
return undefined;
@@ -490,6 +643,7 @@ KNXDevice.prototype = {
this.log("[ERROR] LockMechanism Service without 'name' property called");
return undefined;
}
var myService = new Service.LockMechanism(config.name,config.name);
// LockCurrentState
if (config.LockCurrentState) {
@@ -513,28 +667,103 @@ KNXDevice.prototype = {
//iterate(myService);
return myService;
},
getThermostatService: function(config) {
// // 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);
getMotionSensorService: function(config) {
// Characteristic.ContactSensorState.CONTACT_DETECTED = 0;
// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1;
// 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") {
this.log("[ERROR] Thermostat Service for non 'Thermostat' service called");
return undefined;
@@ -543,6 +772,7 @@ KNXDevice.prototype = {
this.log("[ERROR] Thermostat Service without 'name' property called");
return undefined;
}
var myService = new Service.Thermostat(config.name,config.name);
// CurrentTemperature)
if (config.CurrentTemperature) {
@@ -557,22 +787,21 @@ KNXDevice.prototype = {
myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C
this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature);
}
// HVAC missing yet
// HVAC
if (config.CurrentHeatingCoolingState) {
this.log("Thermostat CurrentHeatingCoolingState characteristic enabled");
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;
},
// temperature sensor type (iOS9 assumed)
getTemperatureSensorService: function(config) {
// some sanity checks
if (config.type !== "TemperatureSensor") {
this.log("[ERROR] TemperatureSensor Service for non 'TemperatureSensor' service called");
return undefined;
@@ -584,16 +813,91 @@ KNXDevice.prototype = {
var myService = new Service.TemperatureSensor(config.name,config.name);
// CurrentTemperature)
if (config.CurrentTemperature) {
this.log("Thermostat CurrentTemperature characteristic enabled");
this.log("TemperatureSensor CurrentTemperature characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature);
}
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() {
// you can OPTIONALLY create an information service if you wish to override
@@ -606,12 +910,12 @@ KNXDevice.prototype = {
informationService
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
.setCharacteristic(Characteristic.Model, "KNX Universal Device")
.setCharacteristic(Characteristic.SerialNumber, "Version 1.1");
.setCharacteristic(Characteristic.SerialNumber, "Version 1.1.2");
accessoryServices.push(informationService);
iterate(this.config);
// throw new Error("STOP");
//iterate(this.config);
if (!this.config.services){
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 ");
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) {
case "ContactSensor":
accessoryServices.push(this.getContactSenserService(configService));
break;
case "GarageDoorOpener":
accessoryServices.push(this.getGarageDoorOpenerService(configService));
break;
case "Lightbulb":
accessoryServices.push(this.getLightbulbService(configService));
break;
case "LightSensor":
accessoryServices.push(this.getLightSensorService(configService));
break;
case "LockMechanism":
accessoryServices.push(this.getLockMechanismService(configService));
break;
case "MotionSensor":
accessoryServices.push(this.getMotionSensorService(configService));
break;
case "Switch":
accessoryServices.push(this.getSwitchService(configService));
break;
case "TemperatureSensor":
accessoryServices.push(this.getTemperatureSensorService(configService));
break;
case "Thermostat":
accessoryServices.push(this.getThermostatService(configService));
break;
case "Window":
accessoryServices.push(this.getWindowService(configService));
break;
case "WindowCovering":
accessoryServices.push(this.getWindowCoveringService(configService));
break;
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 ");
}
}

11
app.js
View File

@@ -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].
function createLog(name) {
return function(message) {
@@ -201,6 +211,7 @@ function createLog(name) {
}
function publish() {
printPin(bridgeConfig.pin);
bridge.publish({
username: bridgeConfig.username || "CC:22:3D:E3:CE:30",
port: bridgeConfig.port || 51826,

View File

@@ -94,7 +94,8 @@
"platform": "HomeAssistant",
"name": "HomeAssistant",
"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,
"sensor_type": "m",
"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"
}
]
}

View File

@@ -17,7 +17,7 @@
"color": "0.10.x",
"eibd": "^0.3.1",
"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-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
"lifx-api": "^1.0.1",

253
platforms/FibaroHC2.js Normal file
View File

@@ -0,0 +1,253 @@
// Fibaro Home Center 2 Platform Shim for HomeBridge
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "FibaroHC2",
// "name": "FibaroHC2",
// "host": "PUT IP ADDRESS OF YOUR HC2 HERE",
// "username": "PUT USERNAME OF YOUR HC2 HERE",
// "password": "PUT PASSWORD OF YOUR HC2 HERE"
// }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
var types = require("HAP-NodeJS/accessories/types.js");
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var request = require("request");
function FibaroHC2Platform(log, config){
this.log = log;
this.host = config["host"];
this.username = config["username"];
this.password = config["password"];
this.auth = "Basic " + new Buffer(this.username + ":" + this.password).toString("base64");
this.url = "http://"+this.host+"/api/devices";
startPollingUpdate( this );
}
FibaroHC2Platform.prototype = {
accessories: function(callback) {
this.log("Fetching Fibaro Home Center devices...");
var that = this;
var foundAccessories = [];
request.get({
url: this.url,
headers : {
"Authorization" : this.auth
},
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json != undefined) {
json.map(function(s) {
that.log("Found: " + s.type);
if (s.visible == true) {
var accessory = null;
if (s.type == "com.fibaro.multilevelSwitch")
accessory = new FibaroAccessory(new Service.Lightbulb(s.name), [Characteristic.On, Characteristic.Brightness]);
else if (s.type == "com.fibaro.FGRM222" || s.type == "com.fibaro.FGR221")
accessory = new FibaroAccessory(new Service.WindowCovering(s.name), [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]);
else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch")
accessory = new FibaroAccessory(new Service.Switch(s.name), [Characteristic.On]);
else if (s.type == "com.fibaro.FGMS001" || s.type == "com.fibaro.motionSensor")
accessory = new FibaroAccessory(new Service.MotionSensor(s.name), [Characteristic.MotionDetected]);
else if (s.type == "com.fibaro.temperatureSensor")
accessory = new FibaroAccessory(new Service.TemperatureSensor(s.name), [Characteristic.CurrentTemperature]);
else if (s.type == "com.fibaro.doorSensor")
accessory = new FibaroAccessory(new Service.ContactSensor(s.name), [Characteristic.ContactSensorState]);
else if (s.type == "com.fibaro.lightSensor")
accessory = new FibaroAccessory(new Service.LightSensor(s.name), [Characteristic.CurrentAmbientLightLevel]);
else if (s.type == "com.fibaro.FGWP101")
accessory = new FibaroAccessory(new Service.Outlet(s.name), [Characteristic.On, Characteristic.OutletInUse]);
if (accessory != null) {
accessory.getServices = function() {
return that.getServices(accessory);
};
accessory.platform = that;
accessory.remoteAccessory = s;
accessory.id = s.id;
accessory.name = s.name;
accessory.model = s.type;
accessory.manufacturer = "Fibaro";
accessory.serialNumber = "<unknown>";
foundAccessories.push(accessory);
}
}
})
}
callback(foundAccessories);
} else {
that.log("There was a problem connecting with FibaroHC2.");
}
});
},
command: function(c,value, that) {
var url = "http://"+this.host+"/api/devices/"+that.id+"/action/"+c;
var body = value != undefined ? JSON.stringify({
"args": [ value ]
}) : null;
var method = "post";
request({
url: url,
body: body,
method: method,
headers: {
"Authorization" : this.auth
},
}, function(err, response) {
if (err) {
that.platform.log("There was a problem sending command " + c + " to" + that.name);
that.platform.log(url);
} else {
that.platform.log(that.name + " sent command " + c);
that.platform.log(url);
}
});
},
getAccessoryValue: function(callback, returnBoolean, homebridgeAccessory, powerValue) {
var url = "http://"+homebridgeAccessory.platform.host+"/api/devices/"+homebridgeAccessory.id+"/properties/";
if (powerValue)
url = url + "power";
else
url = url + "value";
request.get({
headers : {
"Authorization" : homebridgeAccessory.platform.auth
},
json: true,
url: url
}, function(err, response, json) {
homebridgeAccessory.platform.log(url);
if (!err && response.statusCode == 200) {
if (powerValue) {
callback(undefined, parseFloat(json.value) > 1.0 ? true : false);
} else if (returnBoolean)
callback(undefined, json.value == 0 ? 0 : 1);
else
callback(undefined, json.value);
} else {
homebridgeAccessory.platform.log("There was a problem getting value from" + homebridgeAccessory.id);
}
})
},
getInformationService: function(homebridgeAccessory) {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Name, homebridgeAccessory.name)
.setCharacteristic(Characteristic.Manufacturer, homebridgeAccessory.manufacturer)
.setCharacteristic(Characteristic.Model, homebridgeAccessory.model)
.setCharacteristic(Characteristic.SerialNumber, homebridgeAccessory.serialNumber);
return informationService;
},
bindCharacteristicEvents: function(characteristic, homebridgeAccessory) {
var onOff = characteristic.props.format == "bool" ? true : false;
var readOnly = true;
for (var i = 0; i < characteristic.props.perms.length; i++)
if (characteristic.props.perms[i] == "pw")
readOnly = false;
var powerValue = (characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") ? true : false;
subscribeUpdate(characteristic, homebridgeAccessory, onOff);
if (!readOnly) {
characteristic
.on('set', function(value, callback, context) {
if( context !== 'fromFibaro' ) {
if (onOff)
homebridgeAccessory.platform.command(value == 0 ? "turnOff": "turnOn", null, homebridgeAccessory);
else
homebridgeAccessory.platform.command("setValue", value, homebridgeAccessory);
}
callback();
}.bind(this) );
}
characteristic
.on('get', function(callback) {
homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory, powerValue);
}.bind(this) );
},
getServices: function(homebridgeAccessory) {
var informationService = homebridgeAccessory.platform.getInformationService(homebridgeAccessory);
for (var i=0; i < homebridgeAccessory.characteristics.length; i++) {
var characteristic = homebridgeAccessory.controlService.getCharacteristic(homebridgeAccessory.characteristics[i]);
if (characteristic == undefined)
characteristic = homebridgeAccessory.controlService.addCharacteristic(homebridgeAccessory.characteristics[i]);
homebridgeAccessory.platform.bindCharacteristicEvents(characteristic, homebridgeAccessory);
}
return [informationService, homebridgeAccessory.controlService];
}
}
function FibaroAccessory(controlService, characteristics) {
this.controlService = controlService;
this.characteristics = characteristics;
}
var lastPoll=0;
var pollingUpdateRunning = false;
function startPollingUpdate( platform )
{
if( pollingUpdateRunning )
return;
pollingUpdateRunning = true;
var updateUrl = "http://"+platform.host+"/api/refreshStates?last="+lastPoll;
request.get({
url: updateUrl,
headers : {
"Authorization" : platform.auth
},
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json != undefined) {
lastPoll = json.last;
if (json.changes != undefined) {
json.changes.map(function(s) {
if (s.value != undefined) {
var value=parseInt(s.value);
if (isNaN(value))
value=(s.value === "true");
for (i=0;i<updateSubscriptions.length; i++) {
var subscription = updateSubscriptions[i];
if (subscription.id == s.id) {
if (s.power != undefined && subscription.characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") {
subscription.characteristic.setValue(parseFloat(s.power) > 1.0 ? true : false, undefined, 'fromFibaro');
} else if ((subscription.onOff && typeof(value) == "boolean") || !subscription.onOff)
subscription.characteristic.setValue(value, undefined, 'fromFibaro');
else
subscription.characteristic.setValue(value == 0 ? false : true, undefined, 'fromFibaro');
}
}
}
})
}
}
} else {
platform.log("There was a problem connecting with FibaroHC2.");
}
pollingUpdateRunning = false;
setTimeout( function(){startPollingUpdate(platform)}, 2000 );
});
}
var updateSubscriptions = [];
function subscribeUpdate(characteristic, accessory, onOff)
{
// TODO: optimized management of updateSubscription data structure (no array with sequential access)
updateSubscriptions.push({ 'id': accessory.id, 'characteristic': characteristic, 'accessory': accessory, 'onOff': onOff });
}
module.exports.platform = FibaroHC2Platform;

View File

@@ -7,7 +7,27 @@
// URL: http://home-assistant.io
// 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
//
@@ -25,6 +45,8 @@
// 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.
//
//
//
// Examples
//
// Dim the Kitchen Speaker to 40% - sets volume to 40%
@@ -37,7 +59,8 @@
// "platform": "HomeAssistant",
// "name": "HomeAssistant",
// "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
this.host = config["host"];
this.password = config["password"];
this.supportedTypes = config["supported_types"];
this.log = log;
}
@@ -121,22 +145,26 @@ HomeAssistantPlatform.prototype = {
var that = this;
var foundAccessories = [];
var lightsRE = /^light\./i
var switchRE = /^switch\./i
var mediaPlayerRE = /^media_player\./i
this._request('GET', '/states', {}, function(error, response, data){
for (var i = 0; i < data.length; i++) {
entity = data[i]
entity_type = entity.entity_id.split('.')[0]
if (that.supportedTypes.indexOf(entity_type) == -1) {
continue;
}
var accessory = null
if (entity.entity_id.match(lightsRE)) {
if (entity_type == 'light') {
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)
}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)
}
@@ -240,6 +268,12 @@ HomeAssistantLight.prototype = {
},
getServices: function() {
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
.getCharacteristic(Characteristic.On)
@@ -251,7 +285,7 @@ HomeAssistantLight.prototype = {
.on('get', this.getBrightness.bind(this))
.on('set', this.setBrightness.bind(this));
return [lightbulbService];
return [informationService, lightbulbService];
}
}
@@ -375,6 +409,12 @@ HomeAssistantMediaPlayer.prototype = {
},
getServices: function() {
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
.getCharacteristic(Characteristic.On)
@@ -389,15 +429,15 @@ HomeAssistantMediaPlayer.prototype = {
.on('set', this.setVolume.bind(this));
}
return [lightbulbService];
return [informationService, lightbulbService];
}
}
function HomeAssistantSwitch(log, data, client) {
function HomeAssistantSwitch(log, data, client, type) {
// device info
this.domain = "switch"
this.domain = type || "switch"
this.data = data
this.entity_id = data.entity_id
if (data.attributes && data.attributes.friendly_name) {
@@ -454,13 +494,35 @@ HomeAssistantSwitch.prototype = {
},
getServices: function() {
var switchService = new Service.Switch();
var informationService = new Service.AccessoryInformation();
var model;
switchService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerState.bind(this))
.on('set', this.setPowerState.bind(this));
switch (this.domain) {
case "scene":
model = "Scene"
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];
}
}

View File

@@ -7,6 +7,8 @@
},
"description": "This is an example configuration file for KNX platform shim",
"hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration",
"hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!",
"hint3": "For valid services and their characteristics have a look at the knxdevice.md file in folder accessories!",
"platforms": [
{
"platform": "KNX",
@@ -16,7 +18,7 @@
"accessories": [
{
"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",
"services": [
{
@@ -72,7 +74,7 @@
},
{
"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",
"services": [
{
@@ -100,16 +102,47 @@
"type": "WindowCovering",
"description": "iOS9 Window covering (blinds etc) type, still WIP",
"name": "Blinds",
"Target": {
"Set": "address",
"Listen": "adresses"
"TargetPosition": {
"Set": "1/2/3",
"Listen": "1/2/4"
},
"Current": {
"Set": "address",
"Listen": "adresses"
"CurrentPosition": {
"Set": "1/3/1",
"Listen": "1/3/2"
},
"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
View 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!**

View File

@@ -1,5 +1,10 @@
var types = require("HAP-NodeJS/accessories/types.js");
var inherits = require('util').inherits;
var debug = require('debug')('YamahaAVR');
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Yamaha = require('yamaha-nodejs');
var Q = require('q');
var mdns = require('mdns');
//workaround for raspberry pi
var sequence = [
@@ -12,10 +17,53 @@ function YamahaAVRPlatform(log, config){
this.log = log;
this.config = config;
this.playVolume = config["play_volume"];
this.minVolume = config["min_volume"] || -50.0;
this.maxVolume = config["max_volume"] || -20.0;
this.gapVolume = this.maxVolume - this.minVolume;
this.setMainInputTo = config["setMainInputTo"];
this.expectedDevices = config["expected_devices"] || 100;
this.discoveryTimeout = config["discovery_timeout"] || 30;
this.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 = {
accessories: function(callback) {
this.log("Getting Yamaha AVR devices.");
@@ -24,6 +72,8 @@ YamahaAVRPlatform.prototype = {
var browser = this.browser;
browser.stop();
browser.removeAllListeners('serviceUp'); // cleanup listeners
var accessories = [];
var timer, timeElapsed = 0, checkCyclePeriod = 5000;
browser.on('serviceUp', function(service){
var name = service.name;
@@ -36,12 +86,36 @@ YamahaAVRPlatform.prototype = {
var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0];
that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\"");
var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig);
callback([accessory]);
accessories.push(accessory);
if(accessories.length >= this.expectedDevices)
timeoutFunction(); // We're done, call the timeout function now.
//callback([accessory]);
}, function(err){
return;
})
});
});
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.setMainInputTo = config["setMainInputTo"];
this.playVolume = this.config["play_volume"];
this.minVolume = config["min_volume"] || -50.0;
this.maxVolume = config["max_volume"] || -20.0;
this.gapVolume = this.maxVolume - this.minVolume;
}
YamahaAVRAccessory.prototype = {
@@ -66,104 +143,74 @@ YamahaAVRAccessory.prototype = {
if (playing) {
yamaha.powerOn().then(function(){
return yamaha.powerOn().then(function(){
if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10);
else return { then: function(f, r){ f(); } };
else return Q();
}).then(function(){
if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo);
else return { then: function(f, r){ f(); } };
else return Q();
}).then(function(){
if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver(
'<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>'
);
else return { then: function(f, r){ f(); } };
//else return Promise.fulfilled(undefined);
else return Q();
});
}
else {
yamaha.powerOff();
return yamaha.powerOff();
}
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Yamaha",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0],
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0],
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.serviceName,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPlaying(value); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the playback state of the Yamaha AV Receiver",
designedMaxLength: 1
}]
}];
var informationService = new Service.AccessoryInformation();
var yamaha = this.yamaha;
informationService
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.Manufacturer, "Yamaha")
.setCharacteristic(Characteristic.Model, this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0])
.setCharacteristic(Characteristic.SerialNumber, this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]);
var switchService = new Service.Switch("Power State");
switchService.getCharacteristic(Characteristic.On)
.on('get', function(callback, context){
yamaha.isOn().then(function(result){
callback(false, result);
}.bind(this));
}.bind(this))
.on('set', function(powerOn, callback){
this.setPlaying(powerOn).then(function(){
callback(false, powerOn);
}, function(error){
callback(error, !powerOn); //TODO: Actually determine and send real new status.
});
}.bind(this));
var audioDeviceService = new YamahaAVRPlatform.AudioDeviceService("Audio Functions");
audioDeviceService.getCharacteristic(YamahaAVRPlatform.AudioVolume)
.on('get', function(callback, context){
yamaha.getBasicInfo().done(function(basicInfo){
var v = basicInfo.getVolume()/10.0;
var p = 100 * ((v - that.minVolume) / that.gapVolume);
p = p < 0 ? 0 : p > 100 ? 100 : Math.round(p);
debug("Got volume percent of " + p + "%");
callback(false, p);
});
})
.on('set', function(p, callback){
var v = ((p / 100) * that.gapVolume) + that.minVolume;
v = Math.round(v*10.0);
debug("Setting volume to " + v);
yamaha.setVolumeTo(v).then(function(){
callback(false, p);
});
})
.getValue(null, null); // force an asynchronous get
return [informationService, switchService, audioDeviceService];
}
};

View File

@@ -90,11 +90,11 @@ ZWayServerPlatform.prototype = {
//TODO: Unify this with getVDevServices, so there's only one place with mapping between service and vDev type.
//Note: Order matters!
var primaryDeviceClasses = [
"switchBinary",
"thermostat",
"sensorBinary.Door/Window",
"switchMultilevel",
"switchBinary",
"sensorBinary.Door/Window"
"sensorMultilevel.Temperature",
"switchMultilevel"
];
var that = this;
@@ -255,21 +255,21 @@ ZWayServerAccessory.prototype = {
var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev);
var services = [], service;
switch (typeKey) {
case "thermostat":
services.push(new Service.Thermostat(vdev.metrics.title));
break;
case "switchBinary":
services.push(new Service.Switch(vdev.metrics.title, vdev.id));
services.push(new Service.Switch(vdev.metrics.title));
break;
case "switchMultilevel":
services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id));
services.push(new Service.Lightbulb(vdev.metrics.title));
break;
case "thermostat":
services.push(new Service.Thermostat(vdev.metrics.title, vdev.id));
case "sensorBinary.Door/Window":
services.push(new Service.GarageDoorOpener(vdev.metrics.title));
break;
case "sensorMultilevel.Temperature":
services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id));
break;
case "sensorBinary.Door/Window":
services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id));
break;
case "battery.Battery":
services.push(new Service.BatteryService(vdev.metrics.title, vdev.id));
break;