Working Version

Refactored version. KNX.js is used as platform, while knxdevice is
called for each accessory to add.
This commit is contained in:
Snowdd1
2015-09-05 19:08:28 +02:00
parent 116dd1b315
commit ea1c1f6fce
4 changed files with 699 additions and 503 deletions

562
accessories/knxdevice.js Normal file
View File

@@ -0,0 +1,562 @@
/*
* This is a KNX universal accessory shim.
*
*
*/
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var knxd = require("eibd");
var knxd_registerGA = require('../platforms/KNX.js').registerGA;
var knxd_startMonitor = require('../platforms/KNX.js').startMonitor;
function KNXDevice(log, config) {
this.log = log;
// everything in one object, do not copy individually
this.config = config;
log("Accessory constructor called");
if (config.name) {
this.name = config.name;
}
if (config.knxd_ip){
this.knxd_ip = config.knxd_ip;
} else {
throw new Error("MISSING KNXD IP");
}
if (config.knxd_port){
this.knxd_port = config.knxd_port;
} else {
throw new Error("MISSING KNXD PORT");
}
}
//debugging helper only
//inspects an object and prints its properties (also inherited properties)
var iterate = function nextIteration(myObject, path){
// this function iterates over all properties of an object and print them to the console
// when finding objects it goes one level deeper
var name;
if (!path){
console.log("---iterating--------------------")
}
for (name in myObject) {
if (typeof myObject[name] !== 'function') {
if (typeof myObject[name] !== 'object' ) {
console.log((path || "") + name + ': ' + myObject[name]);
} else {
nextIteration(myObject[name], path ? path + name + "." : name + ".");
}
} else {
console.log((path || "") + name + ': (function)' );
}
}
if (!path) {
console.log("================================");
}
};
module.exports = {
accessory: KNXDevice
};
KNXDevice.prototype = {
// all purpose / all types write function
knxwrite: function(callback, groupAddress, dpt, value) {
// this.log("DEBUG in knxwrite");
var knxdConnection = new knxd.Connection();
// this.log("DEBUG in knxwrite: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port);
knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() {
var dest = knxd.str2addr(groupAddress);
// this.log("DEBUG got dest="+dest);
knxdConnection.openTGroup(dest, 1, function(err) {
if (err) {
this.log("[ERROR] knxwrite:openTGroup: " + err);
callback(err);
} else {
// this.log("DEBUG opened TGroup ");
var msg = knxd.createMessage('write', dpt, parseFloat(value));
knxdConnection.sendAPDU(msg, function(err) {
if (err) {
this.log("[ERROR] knxwrite:sendAPDU: " + err);
callback(err);
} else {
// this.log("knx data sent");
callback();
}
}.bind(this));
}
}.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){
// this.log("DEBUG in knxread");
if (!groupAddress) {
return null;
}
var knxdConnection = new knxd.Connection();
// this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port);
knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() {
var dest = knxd.str2addr(groupAddress);
// this.log("DEBUG got dest="+dest);
knxdConnection.openTGroup(dest, 1, function(err) {
if (err) {
this.log("[ERROR] knxread:openTGroup: " + err);
} else {
// this.log("DEBUG knxread: opened TGroup ");
var msg = knxd.createMessage('read', 'DPT1', 0);
knxdConnection.sendAPDU(msg, function(err) {
if (err) {
this.log("[ERROR] knxread:sendAPDU: " + err);
} else {
this.log("knx request sent for "+groupAddress);
}
}.bind(this));
}
}.bind(this));
}.bind(this));
},
// issuing multiple read requests at once
knxreadarray: function (groupAddresses) {
if (groupAddresses.constructor.toString().indexOf("Array") > -1) {
// handle multiple addresses
for (var i = 0; i < groupAddresses.length; i++) {
if (groupAddresses[i]) { // do not bind empty addresses
this.knxread (groupAddresses[i]);
}
}
} else {
// it's only one
this.knxread (groupAddresses);
}
},
// 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
// 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);
// iterate(characteristic);
characteristic.setValue(val ? 1 : 0, undefined, 'fromKNXBus');
}.bind(this));
},
// percentage: get 0..255 from the bus, write 0..100 to characteristic
knxregister_percent: function(addresses, characteristic) {
this.log("knx registering PERCENT " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
if (type !== "DPT5") {
this.log("[ERROR] Received value cannot be a percentage value");
} else {
characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus');
}
}.bind(this));
},
// float
knxregister_float: function(addresses, characteristic) {
this.log("knx registering FLOAT " + addresses);
knxd_registerGA(addresses, function(val, src, dest, type){
this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type"+type+ " for " + characteristic.displayName);
characteristic.setValue(val, undefined, 'fromKNXBus');
}.bind(this));
},
// what about HVAC heating cooling types?
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);
var HAPvalue = 0;
switch (val){
case 0:
HAPvalue = 3;
break;
case 1:
HAPvalue = 3;
break;
case 2:
HAPvalue = 3;
break;
case 3:
HAPvalue = 3;
break;
case 4:
HAPvalue = 0;
break;
default:
HAPvalue = 0;
}
characteristic.setValue(HAPvalue, undefined, 'fromKNXBus');
}.bind(this));
},
// to do! KNX: DPT 20.102 = One Byte like DPT5
// 0 = Auto
// 1 = Comfort
// 2 = Standby
// 3 = Night
// 4 = Freezing/Heat Protection
// 5 255 = not allowed”
// The value property of TargetHeatingCoolingState must be one of the following:
// Characteristic.TargetHeatingCoolingState.OFF = 0;
// Characteristic.TargetHeatingCoolingState.HEAT = 1;
// Characteristic.TargetHeatingCoolingState.COOL = 2;
// Characteristic.TargetHeatingCoolingState.AUTO = 3;
// 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);
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));
*
*/
setBooleanState: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
if (callback) {
callback();
}
} else {
var numericValue = 0;
if (value) {
numericValue = 1; // need 0 or 1, not true or something
}
this.log("Setting "+gaddress+" Boolean to %s", numericValue);
this.knxwrite(callback, gaddress,'DPT1',numericValue);
}
},
setPercentage: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log("event ping pong, exit!");
if (callback) {
callback();
}
} else {
var numericValue = 0;
if (value) {
numericValue = Math.round(255*value/100); // convert 1..100 to 1..255 for KNX bus
}
this.log("Setting "+gaddress+" percentage to %s (%s)", value, numericValue);
this.knxwrite(callback, gaddress,'DPT5',numericValue);
}
},
setFloat: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
if (callback) {
callback();
}
} else {
var numericValue = 0;
if (value) {
numericValue = value; // need 0 or 1, not true or something
}
this.log("Setting "+gaddress+" Float to %s", numericValue);
this.knxwrite(callback, gaddress,'DPT9',numericValue);
}
},
setHVACState: function(value, callback, context, gaddress) {
if (context === 'fromKNXBus') {
this.log(gaddress + " event ping pong, exit!");
if (callback) {
callback();
}
} else {
var numericValue = 0;
switch (value){
case 0:
KNXvalue = 4;
break;
case 1:
KNXvalue = 1;
break;
case 2:
KNXvalue = 1;
break;
case 3:
KNXvalue = 1;
break;
default:
KNXvalue = 1;
}
this.log("Setting "+gaddress+" HVAC to %s", KNXvalue);
this.knxwrite(callback, gaddress,'DPT5',KNXvalue);
}
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
/*
* function getXXXXXXXService(config)
*
* returns a configured service object to the caller (accessory/device)
*
* the config section is supposed to look like that for the complete device
* "devices": [
{
"accessory_type": "knxdevice",
"name": "Living Room North Lamp",
"services": [
{
"type": "Light",
"name": "Living Room North Lamp",
"On": {
"Set": "1/1/6",
"Listen": [
"1/1/63"
]
},
"Brightness": {
"Set": "1/1/62",
"Listen": [
"1/1/64"
]
}
}
]
},
* for each service it is supposed to have a
* type
* name
* and service specific objects
*/
bindCharacteristic: function(myService, characteristicType, valueType, config) {
var myCharacteristic = myService.getCharacteristic(characteristicType);
if (myCharacteristic === undefined) {
throw new Error("unknown characteristics cannot be bound");
}
if (config.Set) {
// can write
switch (valueType) {
case "Bool":
myCharacteristic.on('set', function(value, callback, context) {
// this.log("ITERATE DEBUG");
// iterate(config);
this.setBooleanState(value, callback, context, config.Set);
}.bind(this));
break;
case "Percent":
myCharacteristic.on('set', function(value, callback, context) {
this.setPercentage(value, callback, context, config.Set);
}.bind(this));
break;
case "Float":
myCharacteristic.on('set', function(value, callback, context) {
this.setFloat(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:
this.log("[ERROR] unknown type passed");
throw new Error("[ERROR] unknown type passed");
}
}
if ([config.Set].concat(config.Listen || []).length>0) {
this.log("Binding LISTEN");
// can read
switch (valueType) {
case "Bool":
this.knxregister_bool([config.Set].concat(config.Listen || []), myCharacteristic);
break;
case "Percent":
this.knxregister_percent([config.Set].concat(config.Listen || []), myCharacteristic);
break;
case "Float":
this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic);
break;
case "HVAC":
this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic);
break;
default:
this.log("[ERROR] unknown type passed");
throw new Error("[ERROR] unknown type passed");
}
this.log("Issuing read requests on the KNX bus...");
this.knxreadarray([config.Set].concat(config.Listen || []));
}
return myCharacteristic; // for chaining or whatsoever
},
getLightbulbService: function(config) {
// some sanity checks
//this.config = config;
if (config.type !== "Lightbulb") {
this.log("[ERROR] Lightbulb Service for non 'Lightbulb' service called");
return undefined;
}
if (!config.name) {
this.log("[ERROR] Lightbulb Service without 'name' property called");
return undefined;
}
var myService = new Service.Lightbulb(config.name,config.name);
// On (and Off)
if (config.On) {
this.log("Lightbulb on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On);
} // On characteristic
// Brightness if available
if (config.Brightness) {
this.log("Lightbulb Brightness characteristic enabled");
myService.addCharacteristic(Characteristic.Brightness); // it's an optional
this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.Brightness);
}
// Hue and Saturation could be added here if available in KNX lamps
//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);
// some sanity checks
if (config.type !== "Thermostat") {
this.log("[ERROR] Thermostat Service for non 'Thermostat' service called");
return undefined;
}
if (!config.name) {
this.log("[ERROR] Thermostat Service without 'name' property called");
return undefined;
}
var myService = new Service.Thermostat(config.name,config.name);
// CurrentTemperature)
if (config.CurrentTemperature) {
this.log("Thermostat CurrentTemperature characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature);
}
// TargetTemperature if available
if (config.TargetTemperature) {
this.log("Thermostat TargetTemperature characteristic enabled");
this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature);
}
// HVAC missing yet
if (config.CurrentHeatingCoolingState) {
this.log("Thermostat CurrentHeatingCoolingState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState);
}
return myService;
},
/* assemble the device ***************************************************************************************************/
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var accessoryServices = [];
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
.setCharacteristic(Characteristic.Model, "KNX Universal Device")
.setCharacteristic(Characteristic.SerialNumber, "Version 1.1");
accessoryServices.push(informationService);
iterate(this.config);
// throw new Error("STOP");
if (!this.config.services){
this.log("No services found in accessory?!")
}
var currServices = this.config.services;
this.log("Preparing Services: " + currServices.length)
// go through the config thing and look for services
for (var int = 0; int < currServices.length; int++) {
var configService = currServices[int];
// services need to have type and name properties
if (!configService.type && !configService.name) {
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");
}
switch (configService.type) {
case "Lightbulb":
accessoryServices.push(this.getLightbulbService(configService));
break;
case "Thermostat":
accessoryServices.push(this.getThermostatService(configService));
break;
default:
this.log("[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 ");
}
}
// start listening for events on the bus (if not started yet - will prevent itself)
knxd_startMonitor({ host: this.knxd_ip, port: this.knxd_port });
return accessoryServices;
}
};

View File

@@ -1,186 +0,0 @@
/*
* This is a demo KNX thermostat accessory shim.
* It can
*
*/
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var knxd = require("eibd");
var knxd_registerGA = require('../platforms/KNX.js').registerGA;
var knxd_startMonitor = require('../platforms/KNX.js').startMonitor;
function KNXlampAccessory(log, config) {
this.log = log;
// knx information on object
this.group_address = config.group_address;
this.listen_addresses = config.listen_addresses; // supposed to be undefined, an array of strings, or single string
this.can_dim = config.can_dim; //supposed to be true or false
this.brightness_group_address = config.brightness_group_address;
this.brightness_listen_addresses = config.brightness_listen_addresses;
this.knxd_ip = config.knxd_ip ; // eg 127.0.0.1 if running on localhost
this.knxd_port = config.knxd_port || 6720; // eg 6720 default knxd port
if (config.name) {
this.name = config.name;
}
log("Accessory constructor called");
}
module.exports = {
accessory: KNXlampAccessory
};
KNXlampAccessory.prototype = {
knxwrite: function(callback, groupAddress, dpt, value) {
// this.log("DEBUG in knxwrite");
var knxdConnection = new knxd.Connection();
// this.log("DEBUG in knxwrite: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port);
knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() {
var dest = knxd.str2addr(groupAddress);
// this.log("DEBUG got dest="+dest);
knxdConnection.openTGroup(dest, 1, function(err) {
if (err) {
this.log("[ERROR] knxwrite:openTGroup: " + err);
callback(err);
} else {
// this.log("DEBUG opened TGroup ");
var msg = knxd.createMessage('write', dpt, parseFloat(value));
knxdConnection.sendAPDU(msg, function(err) {
if (err) {
this.log("[ERROR] knxwrite:sendAPDU: " + err);
callback(err);
} else {
// this.log("knx data sent");
callback();
}
}.bind(this));
}
}.bind(this));
}.bind(this));
},
// issues a 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){
// this.log("DEBUG in knxread");
var knxdConnection = new knxd.Connection();
// this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port);
knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() {
var dest = knxd.str2addr(groupAddress);
// this.log("DEBUG got dest="+dest);
knxdConnection.openTGroup(dest, 1, function(err) {
if (err) {
this.log("[ERROR] knxread:openTGroup: " + err);
} else {
// this.log("DEBUG knxread: opened TGroup ");
var msg = knxd.createMessage('read', 'DPT1', 0);
knxdConnection.sendAPDU(msg, function(err) {
if (err) {
this.log("[ERROR] knxread:sendAPDU: " + err);
} else {
this.log("knx request sent");
}
}.bind(this));
}
}.bind(this));
}.bind(this));
},
knxregister: function(addresses, characteristic) {
console.log("knx registering " + addresses);
knxd_registerGA(addresses, function(value){
// parameters do not match
this.log("Getting value from bus:"+value);
characteristic.setValue(value, undefined, 'fromKNXBus');
}.bind(this));
},
setPowerState: function(value, callback, context) {
if (context === 'fromKNXBus') {
this.log("event ping pong, exit!");
if (callback) {
callback();
}
} else {
console.log("Setting power to %s", value);
var numericValue = 0;
if (value) {
numericValue = 1; // need 0 or 1, not true or something
}
this.knxwrite(callback, this.group_address,'DPT1',numericValue);
}
},
setBrightness: function(value, callback, context) {
if (context === 'fromKNXBus') {
this.log("event ping pong, exit!");
if (callback) {
callback();
}
} else {
this.log("Setting brightness to %s", value);
var numericValue = 0;
if (value) {
numericValue = 255*value/100; // convert 1..100 to 1..255 for KNX bus
}
this.knxwrite(callback, this.brightness_group_address,'DPT5',numericValue);
}
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
.setCharacteristic(Characteristic.Model, "KNX Light Switch with or without dimmer")
.setCharacteristic(Characteristic.SerialNumber, "Version 1");
var lightbulbService = new Service.Lightbulb();
var onCharacteristic = lightbulbService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this));
onCharacteristic.supportsEventNotification=true;
// register with value update service
this.addresses = [this.group_address];
this.log("DEBUG1 this.addresses = "+this.addresses);
this.log("DEBUG2 this.listen_addresses = "+this.listen_addresses);
this.addresses = this.addresses.concat(this.listen_addresses || []); // do not join anything if empty (do not add undefined)
this.log("DEBUG3 this.addresses = "+this.addresses);
this.knxregister(this.addresses, onCharacteristic);
this.knxread(this.group_address); // issue a read request on the bus, maybe the device answers to that!
if (this.can_dim) {
var brightnessCharacteristic = lightbulbService
.addCharacteristic(new Characteristic.Brightness())
.on('set', this.setBrightness.bind(this));
// register with value update service
this.brightness_addresses = [this.brightness_group_address];
this.brightness_addresses.concat(this.brightness_listen_addresses || []); // do not join anything if empty (do not add undefined)
this.knxregister(this.brightness_addresses, brightnessCharacteristic);
this.knxread(this.brightness_group_address); // issue a read request on the bus, maybe the device answers to that!
}
knxd_startMonitor({ host: this.knxd_ip, port: this.knxd_port });
return [informationService, lightbulbService];
}
};

View File

@@ -1,178 +0,0 @@
/*
* This is a demo KNX lamp accessory shim.
* It can switch a light on and off, and optionally set a brightness if configured to do so
*
*/
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var knxd = require("eibd");
var knxd_registerGA = require('../platforms/KNX.js').registerGA;
var knxd_startMonitor = require('../platforms/KNX.js').startMonitor;
function KNXthermoAccessory(log, config) {
this.log = log;
this.config=config;
// knx information on object
this.curr_temp_address = config.curr_temp_address;
this.curr_temp_listen_addresses = config.curr_temp_listen_addresses; // supposed to be undefined, an array of strings, or single string
this.target_temp_address = config.target_temp_address;
this.knxd_ip = config.knxd_ip ; // eg 127.0.0.1 if running on localhost
this.knxd_port = config.knxd_port || 6720; // eg 6720 default knxd port
if (config.name) {
this.name = config.name;
}
log("Accessory constructor called");
}
module.exports = {
accessory: KNXthermoAccessory
};
KNXthermoAccessory.prototype = {
knxwrite: function(callback, groupAddress, dpt, value) {
// this.log("DEBUG in knxwrite");
var knxdConnection = new knxd.Connection();
// this.log("DEBUG in knxwrite: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port);
knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() {
var dest = knxd.str2addr(groupAddress);
// this.log("DEBUG got dest="+dest);
knxdConnection.openTGroup(dest, 1, function(err) {
if (err) {
this.log("[ERROR] knxwrite:openTGroup: " + err);
callback(err);
} else {
// this.log("DEBUG opened TGroup ");
var msg = knxd.createMessage('write', dpt, parseFloat(value));
knxdConnection.sendAPDU(msg, function(err) {
if (err) {
this.log("[ERROR] knxwrite:sendAPDU: " + err);
callback(err);
} else {
this.log("knx data sent");
callback();
}
}.bind(this));
}
}.bind(this));
}.bind(this));
},
// issues a 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){
// this.log("DEBUG in knxread");
var knxdConnection = new knxd.Connection();
// this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port);
knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() {
var dest = knxd.str2addr(groupAddress);
// this.log("DEBUG got dest="+dest);
knxdConnection.openTGroup(dest, 1, function(err) {
if (err) {
this.log("[ERROR] knxread:openTGroup: " + err);
} else {
// this.log("DEBUG knxread: opened TGroup ");
var msg = knxd.createMessage('read', 'DPT1', 0);
knxdConnection.sendAPDU(msg, function(err) {
if (err) {
this.log("[ERROR] knxread:sendAPDU: " + err);
} else {
this.log("knx request sent");
}
}.bind(this));
}
}.bind(this));
}.bind(this));
},
knxregister: function(addresses, characteristic) {
console.log("knx registering " + addresses);
knxd_registerGA(addresses, function(value){
// parameters do not match
this.log("Getting value from bus:"+value);
characteristic.setValue(value, undefined, 'fromKNXBus');
}.bind(this));
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
.setCharacteristic(Characteristic.Model, "KNX Thermostat")
.setCharacteristic(Characteristic.SerialNumber, "Version 1");
var myService = new Service.Thermostat();
//
// // 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);
// this.addOptionalCharacteristic(Characteristic.Name);
var CurrentTemperatureCharacteristic = myService
.getCharacteristic(Characteristic.CurrentTemperature)
// .on('set', this.setPowerState.bind(this));
CurrentTemperatureCharacteristic.supportsEventNotification=true;
// register with value update service
this.addresses1 = [this.curr_temp_address];
this.addresses1 = this.addresses1.concat(this.curr_temp_listen_addresses || []); // do not join anything if empty (do not add undefined)
this.knxregister(this.addresses1, CurrentTemperatureCharacteristic);
this.knxread(this.curr_temp_address); // issue a read request on the bus, maybe the device answers to that!
var TargetTemperatureCharacteristic = myService
.getCharacteristic(Characteristic.TargetTemperature)
.on('set', function(value, callback, context) {
if (context === 'fromKNXBus') {
this.log("event ping pong, exit!");
if (callback) {
callback();
}
} else {
console.log("Setting temperature to %s", value);
var numericValue = 0.0;
if (value) {
numericValue = 0+value; // need to be numeric
}
this.knxwrite(callback, this.target_temp_address,'DPT9',numericValue);
}
}.bind(this));
TargetTemperatureCharacteristic.supportsEventNotification=true;
// register with value update service
this.addresses2 = [this.target_temp_address];
this.addresses2 = this.addresses2.concat(this.target_temp_listen_addresses || []); // do not join anything if empty (do not add undefined)
this.knxregister(this.addresses2, TargetTemperatureCharacteristic);
this.knxread(this.target_temp_address); // issue a read request on the bus, maybe the device answers to that!
knxd_startMonitor({ host: this.knxd_ip, port: this.knxd_port });
return [informationService, myService];
}
};

View File

@@ -7,83 +7,75 @@ var types = require("HAP-NodeJS/accessories/types.js");
var knxd = require('eibd'); var knxd = require('eibd');
function KNXPlatform(log, config){ function KNXPlatform(log, config){
this.log = log; this.log = log;
this.config = config; this.config = config;
// this.property1 = config.property1; // this.property1 = config.property1;
// this.property2 = config.property2; // this.property2 = config.property2;
// initiate connection to bus for listening ==> done with first shim // initiate connection to bus for listening ==> done with first shim
}; };
KNXPlatform.prototype = { KNXPlatform.prototype = {
accessories: function(callback) { accessories: function(callback) {
this.log("Fetching KNX devices."); this.log("Fetching KNX devices.");
var that = this; var that = this;
// iterate through all devices the platform my offer // iterate through all devices the platform my offer
// for each device, create an accessory // for each device, create an accessory
// read accessories from file !!!!! // read accessories from file !!!!!
var foundAccessories = this.config.accessories; var foundAccessories = this.config.accessories;
//create array of accessories //create array of accessories
var myAccessories = []; var myAccessories = [];
for (var int = 0; int < foundAccessories.length; int++) { for (var int = 0; int < foundAccessories.length; int++) {
this.log("parsing acc " + int + " of " + foundAccessories.length); this.log("parsing acc " + int + " of " + foundAccessories.length);
// instantiate and push to array // instantiate and push to array
switch (foundAccessories[int].accessory_type) { switch (foundAccessories[int].accessory_type) {
case "knxlamp": case "knxdevice":
this.log("push new lamp with "+foundAccessories[int].name); this.log("push new universal device "+foundAccessories[int].name);
foundAccessories[int].knxd_ip = this.config.knxd_ip; // push knxd connection setting to each device from platform
foundAccessories[int].knxd_port = this.config.knxd_port; foundAccessories[int].knxd_ip = this.config.knxd_ip;
var accConstructor = require('./../accessories/knxlamp.js'); foundAccessories[int].knxd_port = this.config.knxd_port;
var acc = new accConstructor.accessory(this.log,foundAccessories[int]); var accConstructor = require('./../accessories/knxdevice.js');
this.log("created "+acc.name+" accessory"); var acc = new accConstructor.accessory(this.log,foundAccessories[int]);
myAccessories.push(acc); this.log("created "+acc.name+" universal accessory");
break; myAccessories.push(acc);
case "knxthermostat": break;
this.log("push new thermostat with "+foundAccessories[int].name); case "knxlamp":
foundAccessories[int].knxd_ip = this.config.knxd_ip; this.log("push new lamp with "+foundAccessories[int].name );
foundAccessories[int].knxd_port = this.config.knxd_port; foundAccessories[int].knxd_ip = this.config.knxd_ip;
var accConstructor = require('./../accessories/knxthermostat.js'); foundAccessories[int].knxd_port = this.config.knxd_port;
var acc = new accConstructor.accessory(this.log,foundAccessories[int]); var accConstructor = require('./../accessories/knxlamp.js');
this.log("created "+acc.name+" accessory"); var acc = new accConstructor.accessory(this.log,foundAccessories[int]);
myAccessories.push(acc); this.log("created "+acc.name+" accessory");
break; myAccessories.push(acc);
default: break;
// do something else case "knxthermostat":
this.log("unkown accessory type found") this.log("push new thermostat with "+foundAccessories[int].name);
} foundAccessories[int].knxd_ip = this.config.knxd_ip;
foundAccessories[int].knxd_port = this.config.knxd_port;
}; var accConstructor = require('./../accessories/knxthermostat.js');
// if done, return the array to callback function var acc = new accConstructor.accessory(this.log,foundAccessories[int]);
this.log("returning "+myAccessories.length+" accessories"); this.log("created "+acc.name+" accessory");
callback(myAccessories); myAccessories.push(acc);
} break;
default:
// do something else
this.log("unkown accessory type found")
}
};
// if done, return the array to callback function
this.log("returning "+myAccessories.length+" accessories");
callback(myAccessories);
}
}; };
// the signature of the constructor has to be adopted to the accessory you need in your platform! These are the first lines from the sonos platform
function myAccessoryType1(log, config, device, description /* add or remove parms as you need*/ ) {
this.log = log;
this.config = config;
this.device = device;
this.description = description;
// more initialization if required
}
myAccessoryType1.prototype = {
// see https shim wiki page for details. Accessory definition is discussed there.
}
// more
/** /**
@@ -91,47 +83,47 @@ myAccessoryType1.prototype = {
* of registered addresses. * of registered addresses.
* *
* Usage: * Usage:
* You can start the monitoring process at any time * You can start the monitoring process at any time
startMonitor({host: name-ip, port: port-num }); startMonitor({host: name-ip, port: port-num });
* You can add addresses to the subscriptions using * You can add addresses to the subscriptions using
registerGA(groupAddress, callback) registerGA(groupAddress, callback)
* groupAddress has to be an groupAddress in common knx notation string '1/2/3' * groupAddress has to be an groupAddress in common knx notation string '1/2/3'
* the callback has to be a * the callback has to be a
* var f = function(value) { handle value update;} * var f = function(value) { handle value update;}
* so you can do a * so you can do a
* registerGA('1/2/3', function(value){ * registerGA('1/2/3', function(value){
* console.log('1/2/3 got a hit with '+value); * console.log('1/2/3 got a hit with '+value);
* }); * });
* but of course it is meant to be used programmatically, not literally, otherwise it has no advantage * but of course it is meant to be used programmatically, not literally, otherwise it has no advantage
* *
* You can also use arrays of addresses if your callback is supposed to listen to many addresses: * You can also use arrays of addresses if your callback is supposed to listen to many addresses:
registerGA(groupAddresses[], callback) registerGA(groupAddresses[], callback)
* as in * as in
* registerGA(['1/2/3','1/0/0'], function(value){ * registerGA(['1/2/3','1/0/0'], function(value){
* console.log('1/2/3 or 1/0/0 got a hit with '+value); * console.log('1/2/3 or 1/0/0 got a hit with '+value);
* }); * });
* if you are having central addresses like "all lights off" or additional response objects * if you are having central addresses like "all lights off" or additional response objects
* *
* *
* callbacks can have a signature of * callbacks can have a signature of
* function(value, src, dest, type) but do not have to support these parameters (order matters) * function(value, src, dest, type) but do not have to support these parameters (order matters)
* src = physical address such as '1.1.20' * src = physical address such as '1.1.20'
* dest = groupAddress hit (you subscribed to that address, remember?), as '1/2/3' * dest = groupAddress hit (you subscribed to that address, remember?), as '1/2/3'
* type = Data point type, as 'DPT1' * type = Data point type, as 'DPT1'
* *
* *
*/ */
// array of registered addresses and their callbacks //array of registered addresses and their callbacks
var subscriptions = []; var subscriptions = [];
// check variable to avoid running two listeners //check variable to avoid running two listeners
var running; var running;
function groupsocketlisten(opts, callback) { function groupsocketlisten(opts, callback) {
@@ -145,7 +137,7 @@ function groupsocketlisten(opts, callback) {
var registerSingleGA = function registerSingleGA (groupAddress, callback) { var registerSingleGA = function registerSingleGA (groupAddress, callback) {
subscriptions.push({address: groupAddress, callback: callback }); subscriptions.push({address: groupAddress, callback: callback });
} }
/* /*
* public busMonitor.startMonitor() * public busMonitor.startMonitor()
* starts listening for telegrams on KNX bus * starts listening for telegrams on KNX bus
@@ -155,42 +147,45 @@ var startMonitor = function startMonitor(opts) { // using { host: name-ip, port
if (!running) { if (!running) {
running = true; running = true;
} else { } else {
console.log("<< knxd socket listener already running >>");
return null; return null;
} }
console.log(">>> knxd groupsocketlisten starting <<<");
groupsocketlisten(opts, function(parser) { groupsocketlisten(opts, function(parser) {
//console.log("knxfunctions.read: in callback parser"); //console.log("knxfunctions.read: in callback parser");
parser.on('write', function(src, dest, type, val){ parser.on('write', function(src, dest, type, val){
// search the registered group addresses // search the registered group addresses
for (var i = 0; i < subscriptions.length; i++) { //console.log('recv: Write from '+src+' to '+dest+': '+val+' ['+type+'], listeners:' + subscriptions.length);
// iterate through all registered addresses for (var i = 0; i < subscriptions.length; i++) {
if (subscriptions[i].address === dest) { // iterate through all registered addresses
// found one, notify if (subscriptions[i].address === dest) {
//console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']'); // found one, notify
subscriptions[i].callback(val, src, dest, type); console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']');
} subscriptions[i].callback(val, src, dest, type);
} }
}); }
});
parser.on('response', function(src, dest, type, val) { parser.on('response', function(src, dest, type, val) {
// search the registered group addresses // search the registered group addresses
for (var i = 0; i < subscriptions.length; i++) { // console.log('recv: resp from '+src+' to '+dest+': '+val+' ['+type+']');
// iterate through all registered addresses for (var i = 0; i < subscriptions.length; i++) {
if (subscriptions[i].address === dest) { // iterate through all registered addresses
// found one, notify if (subscriptions[i].address === dest) {
//console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']'); // found one, notify
subscriptions[i].callback(val, src, dest, type); // console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']');
} subscriptions[i].callback(val, src, dest, type);
} }
}
});
});
//dont care about reads here
// parser.on('read', function(src, dest) { //dont care about reads here
// console.log('Read from '+src+' to '+dest); // parser.on('read', function(src, dest) {
// }); // console.log('Read from '+src+' to '+dest);
// });
//console.log("knxfunctions.read: in callback parser at end"); //console.log("knxfunctions.read: in callback parser at end");
}); // groupsocketlisten parser }); // groupsocketlisten parser
}; //startMonitor }; //startMonitor
@@ -208,12 +203,15 @@ var registerGA = function (groupAddresses, callback) {
if (groupAddresses.constructor.toString().indexOf("Array") > -1) { if (groupAddresses.constructor.toString().indexOf("Array") > -1) {
// handle multiple addresses // handle multiple addresses
for (var i = 0; i < groupAddresses.length; i++) { for (var i = 0; i < groupAddresses.length; i++) {
registerSingleGA (groupAddresses[i], callback); if (groupAddresses[i]) { // do not bind empty addresses
registerSingleGA (groupAddresses[i], callback);
}
} }
} else { } else {
// it's only one // it's only one
registerSingleGA (groupAddresses, callback); registerSingleGA (groupAddresses, callback);
} }
// console.log("listeners now: " + subscriptions.length);
}; };