diff --git a/accessories/knxlamp.js b/accessories/knxlamp.js new file mode 100644 index 0000000..337904c --- /dev/null +++ b/accessories/knxlamp.js @@ -0,0 +1,180 @@ +/* + * 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"); + + + +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 + +} + + +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]; + } +}; diff --git a/platforms/KNX.js b/platforms/KNX.js new file mode 100644 index 0000000..0b4f765 --- /dev/null +++ b/platforms/KNX.js @@ -0,0 +1,206 @@ +/** Sample platform outline + * based on Sonos platform + */ +'use strict'; +var types = require("HAP-NodeJS/accessories/types.js"); +//var hardware = require('myHardwareSupport'); //require any additional hardware packages +var Connection = require('eibd').connection; + +function KNXPlatform(log, config){ + this.log = log; + this.config = config; +// this.property1 = config.property1; +// this.property2 = config.property2; + + + // initiate connection to bus for listening ==> done with first shim + +}; + +MyPlatform.prototype = { + accessories: function(callback) { + this.log("Fetching myPlatform devices."); + var that = this; + + + // iterate through all devices the platform my offer + // for each device, create an accessory + + // read accessories from file !!!!! + var foundAccessories = this.config.accessories; + + + //create array of accessories + var myAccessories = []; + + for (var int = 0; int < foundAccessories.length; int++) { + + // instantiate and push to array + if (foundAccessories[i].accessory-type === "knxlamp") { + + myAccessories.push(new require('../accessories/knxlamp.js').accessory(this.log,foundAccessories[i])); + } else { + // do something else + this.log("unkown accessory type found") + } //etc. + + }; + // if done, return the array to callback function + 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 + + +/** + * The buscallbacks module is to expose a simple function to listen on the bus and register callbacks for value changes + * of registered addresses. + * + * Usage: +* You can start the monitoring process at any time + startMonitor({host: name-ip, port: port-num }); + +* You can add addresses to the subscriptions using + +registerGA(groupAddress, callback) + +* groupAddress has to be an groupAddress in common knx notation string '1/2/3' +* the callback has to be a +* var f = function(value) { handle value update;} +* so you can do a +* registerGA('1/2/3', function(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 +* +* You can also use arrays of addresses if your callback is supposed to listen to many addresses: + +registerGA(groupAddresses[], callback) + +* as in +* registerGA(['1/2/3','1/0/0'], function(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 +* +* +* callbacks can have a signature of +* function(value, src, dest, type) but do not have to support these parameters (order matters) +* src = physical address such as '1.1.20' +* dest = groupAddress hit (you subscribed to that address, remember?), as '1/2/3' +* type = Data point type, as 'DPT1' +* +* +*/ + + + +// array of registered addresses and their callbacks +var subscriptions = []; +// check variable to avoid running two listeners +var running; + +function groupsocketlisten(opts, callback) { + var conn = Connection(); + conn.socketRemote(opts, function() { + conn.openGroupSocket(0, callback); + }); +} + + +var registerSingleGA = function registerSingleGA (groupAddress, callback) { + subscriptions.push({address: groupAddress, callback: callback }); +} + +/* + * public busMonitor.startMonitor() + * starts listening for telegrams on KNX bus + * + */ +var startMonitor = function startMonitor(opts) { // using { host: name-ip, port: port-num } options object + if (!running) { + running = true; + } else { + return null; + } + + groupsocketlisten(opts, function(parser) { + //console.log("knxfunctions.read: in callback parser"); + parser.on('write', function(src, dest, type, val){ + // search the registered group addresses + for (var i = 0; i < subscriptions.length; i++) { + // iterate through all registered addresses + if (subscriptions[i].address === dest) { + // found one, notify + //console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']'); + subscriptions[i].callback(val, src, dest, type); + } + } + }); + + parser.on('response', function(src, dest, type, val) { + // search the registered group addresses + for (var i = 0; i < subscriptions.length; i++) { + // iterate through all registered addresses + if (subscriptions[i].address === dest) { + // found one, notify + //console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']'); + subscriptions[i].callback(val, src, dest, type); + } + } + + }); + + //dont care about reads here +// parser.on('read', function(src, dest) { +// console.log('Read from '+src+' to '+dest); +// }); + //console.log("knxfunctions.read: in callback parser at end"); + }); // groupsocketlisten parser +}; //startMonitor + + +/* + * public registerGA(groupAdresses[], callback(value)) + * parameters + * callback: function(value, src, dest, type) called when a value is sent on the bus + * groupAddresses: (Array of) string(s) for group addresses + * + * + * + */ +var registerGA = function (groupAddresses, callback) { + // check if the groupAddresses is an array + if (groupAddresses.constructor.toString().indexOf("Array") > -1) { + // handle multiple addresses + for (var i = 0; i < groupAddresses.length; i++) { + registerSingleGA (groupAddresses[i], callback); + } + } else { + // it's only one + registerSingleGA (groupAddresses, callback); + } +}; + + + +module.exports.platform = myPlatform; +module.exports.registerGA = registerGA; +module.exports.startMonitor = startMonitor; \ No newline at end of file