├── .travis.yml ├── .gitignore ├── AUTHORS ├── index.js ├── .github └── workflows │ └── nodejs.yml ├── LICENSE ├── lib ├── creator.js ├── tools.js ├── parser.js ├── encoder.js ├── decoder.js └── connection.js ├── package.json ├── bin ├── groupread ├── groupswrite ├── groupwrite ├── groupsocketlisten └── groupsend ├── .jshintrc ├── README.md └── test ├── support └── server.js ├── creator.test.js ├── connection.test.js ├── decoder.test.js └── encoder.test.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "13" 4 | - "12" 5 | - "10" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .project 3 | .DS_Store 4 | .nyc_output/ 5 | .tern-project 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Andree Klattenhoff 2 | Dries Verbrugge 3 | Andreas Monitzer 4 | Raoul 5 | Marco Piraccini 6 | stasee 7 | Motaz Abuthiab 8 | Markus Keil 9 | farmio 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Connection = require('./lib/connection'); 2 | var Parser = require('./lib/parser'); 3 | var tools = require('./lib/tools'); 4 | var createMessage = require('./lib/creator.js'); 5 | 6 | exports.Connection = Connection; 7 | exports.Parser = Parser; 8 | exports.createMessage = createMessage; 9 | exports.str2addr = tools.str2addr; 10 | exports.addr2str = tools.addr2str; 11 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [16.x, 17.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | npm install 23 | npm run build --if-present 24 | npm test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012-2015 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . 15 | -------------------------------------------------------------------------------- /lib/creator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Encoder = require('./encoder'); 4 | 5 | function createMessage(messageAction, DPTType, value) { 6 | DPTType = DPTType || ''; 7 | let data = Buffer.alloc(2); 8 | 9 | if(messageAction === 'read') { 10 | //Read message first byte 0 (from Buffer.alloc) 11 | } else { 12 | const payload = new Encoder().encode(DPTType, value); 13 | const dottedDPTType = DPTType + '.'; 14 | //first Byte 64 for response; first Byte 128 for write 15 | const firstByte = (messageAction === 'response') ? 64 : 128; 16 | 17 | if(dottedDPTType.indexOf('DPT1.') === 0 18 | || dottedDPTType.indexOf('DPT2.') === 0 19 | || dottedDPTType.indexOf('DPT3.') === 0) { 20 | //Small payload to OR with action 21 | data.writeUInt8(firstByte | payload.readUInt8(0) , 1); 22 | } else { 23 | //Big payload to append 24 | data.writeUInt8(firstByte,1); 25 | data = Buffer.concat([data, payload]); 26 | } 27 | } 28 | 29 | return Array.prototype.slice.call(data, 0); 30 | } 31 | 32 | module.exports = createMessage; 33 | -------------------------------------------------------------------------------- /lib/tools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * pack array to u8int-buffer 5 | */ 6 | module.exports.pack = function(data) { 7 | var buf = Buffer.alloc(data.length); 8 | 9 | for(var i = 0; i < data.length; i++) { 10 | buf.writeUInt8(data[i], i); 11 | } 12 | 13 | return buf; 14 | }; 15 | 16 | module.exports.str2addr = function(str) { 17 | var m = str.match(/(\d*)\/(\d*)\/(\d*)/); 18 | var a, b, c = 0; 19 | var result = -1; 20 | 21 | if(m && m.length > 0) { 22 | a = (m[1] & 0x01f) << 11; 23 | b = (m[2] & 0x07) << 8; 24 | c = m[3] & 0xff; 25 | result = a | b | c; 26 | } 27 | 28 | if(result > -1) { 29 | return result; 30 | } else { 31 | return new Error("Could not parse address"); 32 | } 33 | }; 34 | 35 | module.exports.addr2str = function(adr, ga) { 36 | var str = ''; 37 | if(ga === true) { 38 | var a = (adr>>11)&0x1f; // allow 16bit addresses using 5 bits for the first address part 39 | var b = (adr>>8)&0x7; 40 | var c = (adr & 0xff); 41 | str = a+'/'+b+'/'+c; 42 | } else { 43 | var a = (adr>>12)&0xf; 44 | var b = (adr>>8)&0xf; 45 | var c = adr&0xff; 46 | str = a+'.'+b+'.'+c; 47 | } 48 | 49 | return str; 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eibd", 3 | "version": "0.5.1", 4 | "description": "node eibd client", 5 | "main": "./index", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/andreek/node-eibd" 9 | }, 10 | "engines": { 11 | "node": ">=10.19.0" 12 | }, 13 | "bin": { 14 | "groupread": "./bin/groupread", 15 | "groupsocketlisten": "./bin/groupsocketlisten", 16 | "groupswrite": "./bin/groupswrite", 17 | "groupwrite": "./bin/groupwrite", 18 | "groupsend": "./bin/groupsend" 19 | }, 20 | "devDependencies": { 21 | "mocha": "^9.2.2" 22 | }, 23 | "author": "Andree Klattenhoff", 24 | "contributors": [ 25 | { 26 | "name": "Dries Verbrugge" 27 | }, 28 | { 29 | "name": "Andreas Monitzer" 30 | }, 31 | { 32 | "name": "Raoul" 33 | }, 34 | { 35 | "name": "Marco Piraccini" 36 | }, 37 | { 38 | "name": "stasee" 39 | } 40 | ], 41 | "scripts": { 42 | "test": "mocha --exit -R list ./test" 43 | }, 44 | "licenses": [ 45 | { 46 | "type": "GPL", 47 | "url": "http://github.com/andreek/node-eibd/LICENSE" 48 | } 49 | ], 50 | "keywords": [ 51 | "knx", 52 | "eibd", 53 | "house automation" 54 | ], 55 | "bugs": { 56 | "url": "http://github.com/andreek/node-eibd/issues" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bin/groupread: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var eibd = require('../'); 6 | /** 7 | * groupread 8 | */ 9 | function groupread(opts, gad, callback) { 10 | var conn = new eibd.Connection(); 11 | conn.socketRemote(opts, function(err) { 12 | 13 | if(err) { 14 | callback(err); 15 | return; 16 | } 17 | 18 | var address = eibd.str2addr(gad); 19 | conn.openTGroup(address, 0, function (err) { 20 | if(err) { 21 | callback(err); 22 | return; 23 | } 24 | var msg = eibd.createMessage('read'); 25 | conn.sendAPDU(msg, callback); 26 | }); 27 | }); 28 | } 29 | 30 | var host = process.argv[2]; 31 | var port = process.argv[3]; 32 | var gad = process.argv[4]; 33 | 34 | if(!host || !port) { 35 | console.log('Usage:'); 36 | console.log('groupread '); 37 | console.log('Sends read request to the knxd listening on : for group address '); 38 | console.log(''); 39 | console.log('groupread --socket '); 40 | console.log('Sends read request to the local knxd listening on unix socket for group address '); 41 | console.log(''); 42 | console.error('Parameter missing.'); 43 | } else if(!gad) { 44 | console.error('[ERROR] No gad given'); 45 | } else { 46 | if (host==='--socket') { 47 | var opts = {path:port}; //path is hiding behind port variable from args 48 | } else { 49 | opts = { 50 | host: host, 51 | port: port 52 | }; 53 | } 54 | groupread(opts, gad, function(err) { 55 | if(err) { 56 | console.error('[ERROR]'+err); 57 | } else { 58 | console.log('Read request sent.'); 59 | } 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /bin/groupswrite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var eibd = require('../'); 6 | /** 7 | * groupswrite 8 | * send a group write telegram to a group address (upto DPT3 values) 9 | */ 10 | function groupswrite(opts, gad, value, callback) { 11 | var conn = eibd.Connection(); 12 | var address = eibd.str2addr(gad); 13 | conn.socketRemote(opts, function(err) { 14 | if(err) { 15 | callback(err); 16 | return; 17 | } 18 | conn.openTGroup(address, 1, function (err) { 19 | if(err) { 20 | callback(err); 21 | return; 22 | } 23 | var msg = eibd.createMessage('write', 'DPT3', parseInt(value)); 24 | conn.sendAPDU(msg, callback); 25 | }); 26 | }); 27 | } 28 | var host = process.argv[2]; 29 | var port = process.argv[3]; 30 | var gad = process.argv[4]; 31 | var value = process.argv[5]; 32 | 33 | if(!host || !port) { 34 | console.log('Usage:'); 35 | console.log('groupswrite '); 36 | console.log('Sends a short value (DPT1..3) to the knxd listening on : for group address '); 37 | console.log(''); 38 | console.log('groupswrite --socket '); 39 | console.log('Sends a short value (DPT1..3) to the local knxd listening on unix socket for group address '); 40 | console.log(''); 41 | console.error('Parameter missing.'); 42 | } else if(!gad) { 43 | console.error('[ERROR] No gad given'); 44 | } else if(!value) { 45 | console.error('[ERROR] No value given'); 46 | } else { 47 | if (host==='--socket') { 48 | var opts = {path:port}; //path is hiding behind port variable from args 49 | } else { 50 | opts = { 51 | host: host, 52 | port: port 53 | }; 54 | } 55 | groupswrite(opts, gad, value, function(err) { 56 | if(err) { 57 | console.error('[ERROR]' + err); 58 | } else { 59 | console.log('Value written.'); 60 | } 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /bin/groupwrite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var eibd = require('../'); 6 | /** 7 | * groupwrite 8 | * send a group write telegram to a group address (for DPT5 values) 9 | */ 10 | function groupwrite(opts, gad, value, callback) { 11 | var conn = eibd.Connection(); 12 | var address = eibd.str2addr(gad); 13 | conn.socketRemote(opts, function(err) { 14 | if (err) { 15 | callback(err); 16 | return; 17 | } 18 | conn.openTGroup(address, 1, function (err) { 19 | if(err) { 20 | callback(err); 21 | return; 22 | } 23 | var msg = eibd.createMessage('write', 'DPT5', parseInt(value)); 24 | conn.sendAPDU(msg, callback); 25 | }); 26 | }); 27 | } 28 | 29 | var host = process.argv[2]; 30 | var port = process.argv[3]; 31 | var gad = process.argv[4]; 32 | var value = process.argv[5]; 33 | 34 | if(!host || !port) { 35 | console.log('Usage:'); 36 | console.log('groupwrite '); 37 | console.log('Sends a (DPT5) value to the knxd listening on : for group address '); 38 | console.log(''); 39 | console.log('groupwrite --socket '); 40 | console.log('Sends a (DPT5) value to the local knxd listening on unix socket for group address '); 41 | console.log(''); 42 | console.error('[ERROR] No hostname or port, nor UNIX socket.'); 43 | } else if(!gad) { 44 | console.error('[ERROR] No gad given'); 45 | } else if(!value) { 46 | console.error('[ERROR] No value given'); 47 | } else { 48 | if (host==='--socket') { 49 | var opts = {path:port}; //path is hiding behind port variable from args 50 | } else { 51 | opts = { 52 | host: host, 53 | port: port 54 | }; 55 | } 56 | groupwrite(opts, gad, value, function(err) { 57 | if(err) { 58 | console.error('[ERROR]' + err); 59 | } else { 60 | console.log('Value written.'); 61 | } 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "immed": true, 7 | "es3": false, 8 | "forin": false, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "noempty": true, 14 | "nonew": true, 15 | "plusplus": false, 16 | "quotmark": false, 17 | "undef": true, 18 | "unused": true, 19 | "strict": true, 20 | "trailing": true, 21 | "asi": false, 22 | "boss": true, 23 | "debug": false, 24 | "eqnull": true, 25 | "esnext": true, 26 | "evil": true, 27 | "expr": true, 28 | "funcscope": true, 29 | "globalstrict": false, 30 | "iterator": true, 31 | "lastsemic": false, 32 | "laxbreak": true, 33 | "laxcomma": false, 34 | "loopfunc": false, 35 | "moz": false, 36 | "multistr": false, 37 | "proto": true, 38 | "scripturl": false, 39 | "smarttabs": false, 40 | "shadow": true, 41 | "sub": true, 42 | "supernew": true, 43 | "validthis": true, 44 | "browser": true, 45 | "couch": false, 46 | "devel": true, 47 | "dojo": false, 48 | "jquery": true, 49 | "mootools": false, 50 | "node": true, 51 | "nonstandard": false, 52 | "prototypejs": false, 53 | "rhino": true, 54 | "worker": false, 55 | "wsh": true, 56 | "yui": false, 57 | "-W038": true, 58 | "-W053": true, 59 | "-W086": true, 60 | "globals": { 61 | "define": false, 62 | "require": false, 63 | "requirejs": false, 64 | "expect": true, 65 | "sails": true, 66 | "describe": false, 67 | "xdescribe": false, 68 | "it": false, 69 | "xit": false, 70 | "before": false, 71 | "beforeEach": false, 72 | "after": false, 73 | "afterEach": false 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /bin/groupsocketlisten: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var eibd = require('../'); 6 | /** 7 | * groupsocketlisten 8 | */ 9 | function groupsocketlisten(opts, callback) { 10 | 11 | var conn = eibd.Connection(); 12 | 13 | conn.socketRemote(opts, function(err) { 14 | 15 | if(err) { 16 | callback(err); 17 | return; 18 | } 19 | 20 | conn.openGroupSocket(0, function(parser) { 21 | callback(undefined, parser); 22 | }); 23 | 24 | }); 25 | 26 | conn.on('close', function () { 27 | //restart... 28 | setTimeout(function () { groupsocketlisten(opts, callback); }, 100); 29 | }); 30 | } 31 | 32 | var host = process.argv[2]; 33 | var port = process.argv[3]; 34 | 35 | if(!host || !port) { 36 | console.log('Usage:'); 37 | console.log('groupsocketlisten '); 38 | console.log('Prints telegrams received by knxd listening on :'); 39 | console.log(''); 40 | console.log('groupwrite --socket '); 41 | console.log('Prints telegrams received by local knxd listening on UNIX socket '); 42 | console.log(''); 43 | console.error('[ERROR] No hostname or port, nor UNIX socket.'); 44 | } else { 45 | if (host==='--socket') { 46 | var opts = {path:port}; //path is hiding behind port variable from args 47 | } else { 48 | opts = { 49 | host: host, 50 | port: port 51 | }; 52 | } 53 | groupsocketlisten(opts, function(err, parser) { 54 | if(!err) { 55 | console.log('Listening... (Ctrl-c to abort)\n'); 56 | parser.on('write', function(src, dest, type, val){ 57 | console.log('Write from '+src+' to '+dest+': '+val+' ['+type+']'); 58 | }); 59 | parser.on('response', function(src, dest, type, val) { 60 | console.log('Response from '+src+' to '+dest+': '+val+' ['+type+']'); 61 | }); 62 | parser.on('read', function(src, dest) { 63 | console.log('Read from '+src+' to '+dest); 64 | }); 65 | 66 | } else { 67 | console.error('[ERROR] '+err); 68 | } 69 | 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eibd [![Build Status](https://secure.travis-ci.org/andreek/node-eibd.png?branch=master)](http://travis-ci.org/andreek/node-eibd) 2 | 3 | A Node.js client for eib/knx daemon. Implements all functions of eibd client library needed for groupswrite/groupwrite, groupread and groupsocketlisten. 4 | 5 | ## Install 6 | 7 | npm install eibd 8 | 9 | ## Test 10 | 11 | npm test 12 | 13 | ## Supported Datatypes 14 | 15 | * EIS 1 / DPT 1.xxx 16 | * EIS 2 / DPT 3.xxx 17 | * EIS 3 / DPT 10.xxx 18 | * EIS 4 / DPT 11.xxx 19 | * EIS 5 / DPT 9.xxx 20 | * EIS 6 / DPT 5.xxx 21 | * EIS 8 / DPT 2.xxx 22 | * EIS 9 / DPT 14.xxx 23 | * EIS 10.000 / DPT 7.xxx 24 | * EIS 10.001 / DPT 8.xxx 25 | * EIS 11 / DPT 12.xxx 26 | * EIS 11.001 / DPT 3.xxx 27 | * EIS 13 / DPT 4.xxx 28 | * EIS 14 / DPT 6.xxx 29 | * EIS 15 / DPT 16.xxx 30 | * DPT232 31 | 32 | ## CLI Usage 33 | 34 | View source code of cli tools as examples for usage. 35 | 36 | ### groupwrite 37 | 38 | ./bin/groupwrite host port x/x/x 0..255 39 | 40 | e.g. `./bin/groupwrite localhost 6270 1/2/3 100` 41 | 42 | ./bin/groupwrite --socket path x/x/x 0..255 43 | 44 | e.g. `./bin/groupwrite --socket /run/knx 1/2/3 100` 45 | 46 | ### groupswrite 47 | 48 | ./bin/groupswrite host port x/x/x 0..1 49 | 50 | e.g. `./bin/groupswrite localhost 6270 1/2/4 1` 51 | 52 | ./bin/groupswrite --socket path x/x/x 0..1 53 | 54 | e.g. `./bin/grouspwrite --socket /run/knx 1/2/4 1` 55 | 56 | ### groupread 57 | (issues a read request telegram to the bus, does not wait for an answer!) 58 | 59 | ./bin/groupread host port x/x/x 60 | 61 | e.g. `./bin/groupread localhost 6270 1/2/4` 62 | 63 | ./bin/groupread --socket path x/x/x 64 | 65 | e.g. `./bin/groupread --socket /run/knx 1/2/4` 66 | 67 | ### Listening for group telegrams 68 | 69 | ./bin/groupsocketlisten host port 70 | 71 | ./bin/groupsocketlisten --socket path 72 | 73 | ## Related projects 74 | * https://github.com/knxd/knxd 75 | * https://github.com/snowdd1/homebridge-knx 76 | * https://bitbucket.org/ekarak/node-red-contrib-knxjs 77 | 78 | ## eibd documentation 79 | 80 | * http://www.auto.tuwien.ac.at/~mkoegler/eib/sdkdoc-0.0.5.pdf 81 | -------------------------------------------------------------------------------- /test/support/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * simple test server 4 | */ 5 | var net = require('net'); 6 | 7 | function TestServer(port, callback) { 8 | 9 | this.groupSockets = []; 10 | 11 | var self = this; 12 | this.server = net.createServer(function(socket) { 13 | socket.on('data', function(data) { 14 | self.onData(socket, data); 15 | }); 16 | }); 17 | 18 | this.server.listen(port, callback); 19 | 20 | return this; 21 | } 22 | 23 | TestServer.prototype.end = function(callback) { 24 | 25 | if(this.server) { 26 | this.server.close(function() { 27 | if(callback) { 28 | callback(); 29 | } 30 | }); 31 | } else { 32 | if(callback) { 33 | callback(); 34 | } 35 | } 36 | }; 37 | 38 | TestServer.prototype.noticeSockets = function() { 39 | var buf = Buffer.from([0,8,0,27,0,0,2,1,0,0,0,8,0,27,0,0,0,1,0,0,0,9,0,27,11,8,11,4,0,40,0]); 40 | for(var i in this.groupSockets) { 41 | var socket = this.groupSockets[i]; 42 | socket.write(buf); 43 | } 44 | }; 45 | 46 | TestServer.prototype.onData = function(socket, buf) { 47 | 48 | var arr = []; 49 | for(var i = 0; i < buf.length; i++) { 50 | var val = buf.readUInt8(i); 51 | arr.push(val); 52 | } 53 | 54 | var len = arr[1]; 55 | 56 | if(len >= 4 && arr[2] === 0) { 57 | 58 | var req = arr[3]; 59 | var action = arr[5]; 60 | var dest = (arr[4]<<8)|arr[5]; 61 | 62 | switch(req) { 63 | case 38: 64 | this.groupSockets.push(socket); 65 | var self = this; 66 | socket.on('end', function() { 67 | var idx = self.groupSockets.indexOf(socket); 68 | self.groupSockets.splice(idx, 1); 69 | }); 70 | break; 71 | case 37: 72 | if(len === 4) { 73 | this.noticeSockets(req, dest, dest, action); 74 | } else if(len === 5) { 75 | this.noticeSockets(req, dest, dest, action, arr[6]); 76 | } 77 | break; 78 | case 34: 79 | 80 | var data = [0, 2, 0, 34]; 81 | var buf = Buffer.from(data); 82 | var self = this; 83 | socket.write(buf, function() { 84 | self.noticeSockets(req, dest, dest, 64, 255); 85 | }); 86 | break; 87 | default: 88 | break; 89 | } 90 | 91 | } 92 | 93 | }; 94 | 95 | module.exports = TestServer; 96 | -------------------------------------------------------------------------------- /test/creator.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'), 4 | createMessage = require('../lib/creator.js'); 5 | 6 | 7 | describe('Creator', function() { 8 | 9 | it('should create DPT1 messages', function() { 10 | assert.deepEqual(createMessage('write','DPT1',true), [0, 129]); 11 | assert.deepEqual(createMessage('write','DPT1',false), [0, 128]); 12 | 13 | assert.deepEqual(createMessage('write','DPT1',1), [0, 129]); 14 | assert.deepEqual(createMessage('write','DPT1',0), [0, 128]); 15 | 16 | assert.deepEqual(createMessage('read','DPT1', true), [0, 0]); 17 | assert.deepEqual(createMessage('read','DPT1'), [0, 0]); 18 | assert.deepEqual(createMessage('read'), [0, 0]); 19 | 20 | assert.deepEqual(createMessage('response','DPT1',true), [0, 65]); 21 | assert.deepEqual(createMessage('response','DPT1',false), [0, 64]); 22 | }); 23 | 24 | it('should create DPT2 messages', function() { 25 | assert.deepEqual(createMessage('write','DPT2',1), [0, 129]); 26 | assert.deepEqual(createMessage('write','DPT2',3), [0, 131]); 27 | 28 | assert.deepEqual(createMessage('read','DPT2', true), [0, 0]); 29 | assert.deepEqual(createMessage('read','DPT2'), [0, 0]); 30 | assert.deepEqual(createMessage('read'), [0, 0]); 31 | 32 | assert.deepEqual(createMessage('response','DPT2',1), [0, 65]); 33 | assert.deepEqual(createMessage('response','DPT2',3), [0, 67]); 34 | }); 35 | 36 | it('should create DPT3 messages', function() { 37 | assert.deepEqual(createMessage('write','DPT3',1), [0, 129]); 38 | assert.deepEqual(createMessage('write','DPT3',15), [0, 143]); 39 | 40 | assert.deepEqual(createMessage('read','DPT3', true), [0, 0]); 41 | assert.deepEqual(createMessage('read','DPT3'), [0, 0]); 42 | assert.deepEqual(createMessage('read'), [0, 0]); 43 | 44 | assert.deepEqual(createMessage('response','DPT3',1), [0, 65]); 45 | assert.deepEqual(createMessage('response','DPT3',15), [0, 79]); 46 | }); 47 | 48 | it('should create DPT5 messages', function() { 49 | assert.deepEqual(createMessage('write','DPT5',5), [0, 128, 5]); 50 | assert.deepEqual(createMessage('read','DPT5'), [0, 0]); 51 | assert.deepEqual(createMessage('response','DPT5',4), [0, 64, 4]); 52 | }); 53 | 54 | it('should create DPT7 messages', function() { 55 | assert.deepEqual(createMessage('write','DPT7',0), [0, 128, 0x00, 0x00]); 56 | assert.deepEqual(createMessage('write','DPT7.001',31247), [0, 128, 0x7A, 0x0F]); 57 | assert.deepEqual(createMessage('write','DPT7.600',2550), [0, 128, 0x09, 0xF6]); 58 | assert.deepEqual(createMessage('read','DPT7'), [0, 0]); 59 | assert.deepEqual(createMessage('response','DPT7',65535), [0, 64, 0xFF, 0xFF]); 60 | }); 61 | 62 | it('should create DPT9 messages', function() { 63 | assert.deepEqual(createMessage('write','DPT9',-5.08), [0, 128, 0x86, 0x4]); 64 | assert.deepEqual(createMessage('read','DPT9'), [0, 0]); 65 | assert.deepEqual(createMessage('response','DPT9',-5.08), [0, 64, 0x86, 0x4]); 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /bin/groupsend: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var eibd = require('../'); 6 | 7 | /** 8 | * groupsend 9 | * Send a KNX telegram with support for read/write/response messages and DPT1, DPT2, DPT3, DPT5, DPT9 data format 10 | * Contains functionality from groupread/groupwrite/groupswrite in one cmd line application 11 | * 12 | * Arguments: host port gad action dpt value 13 | * host = ipadress (Ex. 127.0.0.1 - localhost - host.com)5 14 | * port = portnumber (Ex. 6720) 15 | * gad = groupnumber (Ex. 1/2/34) 16 | * action = eibd action (read , write or response) 17 | * dpt = data point type for write/response (Ex. DPT1, DPT2, DPT3, DPT5, DPT9) 18 | * value = data value for write/response (Ex. true , 23 , 12.23) 19 | */ 20 | function send(opts, gad, action, dpt, value, callback) { 21 | var conn = new eibd.Connection(); 22 | conn.socketRemote(opts, function(err) { 23 | 24 | if(err) { 25 | callback(err); 26 | return; 27 | } 28 | 29 | var address = eibd.str2addr(gad); 30 | conn.openTGroup(address, 0, function (err) { 31 | if(err) { 32 | callback(err); 33 | return; 34 | } 35 | 36 | var msg = eibd.createMessage(action, dpt, parseFloat(value)); 37 | conn.sendAPDU(msg, callback); 38 | 39 | }); 40 | }); 41 | } 42 | 43 | var host = process.argv[2]; 44 | var port = process.argv[3]; 45 | var gad = process.argv[4]; 46 | var action = process.argv[5]; 47 | var dpt = process.argv[6]; 48 | var value = process.argv[7]; 49 | 50 | 51 | var argumentsError; 52 | if(!host || !port) { 53 | argumentsError = '[ERROR] No hostname and port or --socket and path'; 54 | } else if(!gad) { 55 | argumentsError = '[ERROR] No gad given'; 56 | } else if(!action || action !== 'response' && action !== 'read' && action !== 'write') { 57 | argumentsError = '[ERROR] Wrong action, should be read, write or response'; 58 | console.log(action); 59 | } else if(!dpt && action !== 'read') { 60 | argumentsError = '[ERROR] Response and write action require a DPT value'; 61 | } else if (action !== 'read' && dpt.indexOf('DPT1') !== 0 && dpt.indexOf('DPT5') !== 0 && dpt.indexOf('DPT9') !== 0) { 62 | argumentsError = '[ERROR] Wrong DPT, only DPT1, DPT5 and DPT9 are implemented'; 63 | } else if(!value && (action === 'response' || action === 'write')) { 64 | argumentsError = '[ERROR] Response and write actions require a value'; 65 | } 66 | 67 | if(argumentsError) { 68 | console.error('\n' + argumentsError + '\n\n'); 69 | console.log('Usage:') 70 | console.log(' groupsend gad action dpt value'); 71 | console.log(' groupsend --socket gad action dpt value \n\n'); 72 | console.log('host = ipadress (Ex. 127.0.0.1 - localhost - host.com)'); 73 | console.log('port = portnumber (Ex. 6720)'); 74 | console.log(' = alternative UNIX socket (Ex. /run/knx)') 75 | console.log('gad = groupnumber (Ex. 1/2/34)'); 76 | console.log('action = eibd action (read , write or response)'); 77 | console.log('dpt = data point type (Ex. DPT1, DPT2, DPT3, DPT5, DPT9)'); 78 | console.log('value = data value for write/response (Ex. true , 23 , 12.23)'); 79 | } else { 80 | if (host==='--socket') { 81 | var opts = {path:port}; //path is hiding behind port variable from args 82 | } else { 83 | opts = { 84 | host: host, 85 | port: port 86 | }; 87 | } 88 | send(opts, gad, action, dpt, value, function(err) { 89 | if(err) { 90 | console.error('[ERROR]'+err); 91 | } else { 92 | console.log('Action completed.'); 93 | } 94 | }); 95 | } 96 | -------------------------------------------------------------------------------- /test/connection.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'), 4 | eibd = require('../'); 5 | 6 | var port = 6721; 7 | var opts = { host: 'localhost', port: port }; 8 | var TestServer = new require('./support/server'); 9 | var server = null; 10 | 11 | describe('EIBConnection', function() { 12 | 13 | beforeEach(function(done) { 14 | server = new TestServer(port, function() { 15 | done(); 16 | }); 17 | }); 18 | 19 | afterEach(function(done) { 20 | if(server) { 21 | server.end(); 22 | } 23 | done(); 24 | }); 25 | 26 | describe('socketRemote', function() { 27 | it('should open a connection', function(done) { 28 | var conn = new eibd.Connection(); 29 | conn.socketRemote(opts, function() { 30 | assert.equal(true, true); 31 | done(); 32 | }); 33 | }), 34 | it('should catch error if server is not reachable', function(done) { 35 | server.end(); 36 | server = null; 37 | var conn = new eibd.Connection(); 38 | conn.socketRemote(opts, function(err) { 39 | assert.equal(err.code, 'ECONNREFUSED'); 40 | done(); 41 | }); 42 | }), 43 | it('should notice if host or port is not given', function(done) { 44 | var conn = new eibd.Connection(); 45 | try { 46 | conn.socketRemote(port); 47 | } catch(err) { 48 | assert.equal(true, err instanceof Error); 49 | done(); 50 | } 51 | }); 52 | }), 53 | describe('openTGroup', function() { 54 | it('should work without error', function(done) { 55 | var conn = new eibd.Connection(); 56 | conn.socketRemote(opts, function() { 57 | var dest = eibd.str2addr('0/1/0'); 58 | conn.openTGroup(dest, 0, function(err) { 59 | assert.equal(null, err); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | }), 65 | describe('sendAPDU', function() { 66 | it('should work without error', function(done) { 67 | var conn = new eibd.Connection(); 68 | conn.socketRemote(opts, function() { 69 | conn.sendAPDU([0,0],function(err) { 70 | assert.equal(undefined, err); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | }), 76 | describe('sendAPDU DPT9 4Byte', function() { 77 | it('should work without error', function(done) { 78 | var conn = new eibd.Connection(); 79 | conn.socketRemote(opts, function() { 80 | conn.sendAPDU([0,0,0,0],function(err) { 81 | assert.equal(undefined, err); 82 | done(); 83 | }); 84 | }); 85 | }); 86 | }), 87 | describe('sendRequest', function() { 88 | it('should work without error', function(done) { 89 | var conn = new eibd.Connection(); 90 | conn.socketRemote(opts, function() { 91 | conn.sendRequest([0,0],function(err) { 92 | assert.equal(undefined, err); 93 | done(); 94 | }); 95 | }); 96 | }); 97 | }), 98 | describe('openGroupSocket', function() { 99 | it('should get called', function(done) { 100 | var conn = new eibd.Connection(); 101 | conn.socketRemote(opts, function() { 102 | var i = 0; 103 | conn.openGroupSocket(0, function(parser) { 104 | parser.on('read', function() { 105 | i++; 106 | if(i === 2) { 107 | done(); 108 | } 109 | }); 110 | // send command for socket listen 111 | var groupswrite = new eibd.Connection(); 112 | groupswrite.socketRemote(opts, function() { 113 | var dest = eibd.str2addr('0/1/0'); 114 | groupswrite.openTGroup(dest, 1, function() { 115 | groupswrite.sendAPDU([0, 0xff&1]); 116 | }); 117 | }); 118 | }); 119 | }); 120 | 121 | }); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Readable = require('stream').Readable, 4 | Decoder = require('./decoder'), 5 | tools = require('./tools'); 6 | 7 | /** 8 | * Parser 9 | */ 10 | function Parser(socket, options) { 11 | 12 | if(!(this instanceof Parser)) { 13 | return new Parser(options); 14 | } 15 | 16 | Readable.call(this, options); 17 | 18 | this.decoder = new Decoder(); 19 | this._source = socket; 20 | this._inputBuffer = Buffer.alloc(0); 21 | 22 | var self = this; 23 | // hit source end 24 | this._source.on('end', function() { 25 | 26 | }); 27 | 28 | // get data 29 | this._source.on('data', function(data) { 30 | self.onData(data); 31 | }); 32 | 33 | } 34 | 35 | Parser.prototype = Object.create( 36 | Readable.prototype, { constructor: { value: Parser }}); 37 | 38 | /** 39 | * parse telegram 40 | */ 41 | Parser.prototype.parseTelegram = function(telegram) { 42 | var self = this; 43 | var len = telegram.readUInt8(1); 44 | 45 | // 4 + 5 src adr. 46 | var src = telegram.readUInt16BE(4); 47 | // 6 + 7 dest adr. 48 | var dest = telegram.readUInt16BE(6); 49 | 50 | // action 51 | var action = (telegram.readUInt8(8) & (3))*8+((telegram.readUInt8(9) & (192)) >> 6); //bytes 8 lowest 2 bits and byte 9 highest two bits are action (AND VALUE if VALUE HAS LESS THEN 7 bits !!!!) 52 | var event = ''; 53 | switch (action) { 54 | case 10: 55 | event = 'memory write'; 56 | break; 57 | case 2: 58 | event = 'write'; 59 | break; 60 | case 1: 61 | event = 'response'; 62 | break; 63 | case 0: 64 | event = 'read'; 65 | break; 66 | } 67 | 68 | if(action > 0) { 69 | 70 | // value 71 | var val = null; 72 | var valbuffer; 73 | if (len<=8) { 74 | val = telegram[telegram.length-1] & 63; // only 6 bits for data in the mixed action/data byte 75 | valbuffer = Buffer.from([val]); 76 | } else { //if(len > 8) 77 | val = telegram.slice(10, telegram.length); 78 | valbuffer = Buffer.from(val); 79 | } 80 | 81 | // emit raw telegram event, pass the addresses and the buffer, no DPT guessing, copy of data buffer 82 | self.emit('telegram', event, tools.addr2str(src, false), tools.addr2str(dest, true), valbuffer); 83 | 84 | self.decoder.decode(len, val, function(err, type, value) { 85 | 86 | // emit action event 87 | self.emit(event, tools.addr2str(src, false), tools.addr2str(dest, true), type, value); 88 | 89 | // emit dest address event 90 | self.emit(tools.addr2str(dest, true), event, tools.addr2str(src, false), tools.addr2str(dest, true), type, value); 91 | 92 | }); 93 | 94 | 95 | } else { 96 | 97 | // emit action event 98 | self.emit(event, tools.addr2str(src, false), tools.addr2str(dest, true)); 99 | 100 | // emit dest address event 101 | self.emit(tools.addr2str(dest, true), event, tools.addr2str(src, false), tools.addr2str(dest, true)); 102 | 103 | } 104 | 105 | }; 106 | 107 | /** 108 | * data received from socket 109 | */ 110 | Parser.prototype.onData = function(chunk) { 111 | 112 | // no data received 113 | if(chunk === null) { 114 | return ; 115 | } 116 | 117 | // store chunk 118 | this._inputBuffer = Buffer.concat([this._inputBuffer, chunk]); 119 | 120 | while (true) { 121 | // check if at least length header is here 122 | if (this._inputBuffer.length < 2) { 123 | return; 124 | } 125 | 126 | var packetlen = this._inputBuffer[1] + 2; 127 | if (packetlen > this._inputBuffer.length) { 128 | //not enough data 129 | return; 130 | } 131 | 132 | //what kind of packet have we got... 133 | if (packetlen === 4) { 134 | //confirm mag 135 | } else if (packetlen === 5) { 136 | //opengroupsocket 137 | } else if (packetlen >= 6) { 138 | // we have at least one complete package 139 | var telegram = this._inputBuffer.slice(0, packetlen); 140 | // emit event 141 | this.parseTelegram(telegram); 142 | } 143 | this._inputBuffer = this._inputBuffer.slice(packetlen); 144 | } 145 | }; 146 | 147 | module.exports = Parser; 148 | -------------------------------------------------------------------------------- /test/decoder.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'), 4 | Decoder = require('../lib/decoder.js'); 5 | 6 | var enc = null; 7 | 8 | describe('Decoder', function() { 9 | 10 | before(function() { 11 | enc = new Decoder(); 12 | }); 13 | describe('DPT1', function() { 14 | it('should decode DPT1 value 1', function() { 15 | var data = 1; 16 | enc.decode(8, data, function(err, type, value) { 17 | assert.equal(err, null); 18 | assert.equal(type, 'DPT1'); 19 | assert.equal(value, 1); 20 | }); 21 | }), 22 | it('should decode DPT1 value 0', function() { 23 | var data = 0; 24 | enc.decode(8, data, function(err, type, value) { 25 | assert.equal(err, null); 26 | assert.equal(type, 'DPT1'); 27 | assert.equal(value, 0); 28 | }); 29 | }); 30 | }); 31 | 32 | describe('DPT5', function() { 33 | it('should decode DPT5 value', function() { 34 | var buf = Buffer.alloc(1); 35 | buf.writeUInt8(150, 0); 36 | enc.decode(9, buf, function(err, type, value) { 37 | assert.equal(err, null); 38 | assert.equal(type, 'DPT5'); 39 | assert.equal(value, 150); 40 | }); 41 | }), 42 | it('should throw error if buffer wrong lenght', function() { 43 | var buf = Buffer.alloc(2); 44 | buf.writeUInt8(150, 0); 45 | buf.writeUInt8(151, 1); 46 | enc.decode(9, buf, function(err) { 47 | assert.equal(err.message, 'Invalid data len for DPT5'); 48 | }); 49 | }); 50 | }); 51 | 52 | describe('DPT9', function() { 53 | it('should decode DPT9 float value - exponent4', function() { 54 | var buf = Buffer.alloc(2); 55 | buf.writeUInt8(0xA3, 0); 56 | buf.writeUInt8(0xB5, 1); 57 | enc.decode(10, buf, function(err, type, value) { 58 | assert.equal(err, null); 59 | assert.equal(type, 'DPT9'); 60 | assert.equal(Math.round(value * 100) / 100, -175.84); 61 | }); 62 | }); 63 | 64 | it('should decode DPT9 float value - exponent4', function() { 65 | var buf = Buffer.alloc(2); 66 | buf.writeUInt8(0xA5, 0); 67 | buf.writeUInt8(0x8D, 1); 68 | enc.decode(10, buf, function(err, type, value) { 69 | assert.equal(err, null); 70 | assert.equal(type, 'DPT9'); 71 | assert.equal(Math.round(value * 100) / 100, -100.32); 72 | }); 73 | }); 74 | 75 | it('should decode DPT9 float value - exponent4', function() { 76 | var buf = Buffer.alloc(2); 77 | buf.writeUInt8(0xA3, 0); 78 | buf.writeUInt8(0x21, 1); 79 | enc.decode(10, buf, function(err, type, value) { 80 | assert.equal(err, null); 81 | assert.equal(type, 'DPT9'); 82 | assert.equal(Math.round(value * 100) / 100, -199.52); 83 | }); 84 | }); 85 | 86 | it('should decode DPT9 float value - exponent6', function() { 87 | var buf = Buffer.alloc(2); 88 | buf.writeUInt8(0xB6, 0); 89 | buf.writeUInt8(0xC7, 1); 90 | enc.decode(10, buf, function(err, type, value) { 91 | assert.equal(err, null); 92 | assert.equal(type, 'DPT9'); 93 | assert.equal(Math.round(value * 100) / 100, -200.32); 94 | }); 95 | }); 96 | 97 | it('should decode DPT9 float value - exponent2', function() { 98 | var buf = Buffer.alloc(2); 99 | buf.writeUInt8(0x97, 0); 100 | buf.writeUInt8(0x81, 1); 101 | enc.decode(10, buf, function(err, type, value) { 102 | assert.equal(err, null); 103 | assert.equal(type, 'DPT9'); 104 | assert.equal(Math.round(value * 100) / 100, -5.08); 105 | }); 106 | }); 107 | }); 108 | describe('UNKN', function() { 109 | it('should decode DPT14 float value', function() { 110 | var buf = Buffer.alloc(4); 111 | buf.writeUInt8(0x3e, 0); 112 | buf.writeUInt8(0x9a, 1); 113 | buf.writeUInt8(0x1c, 2); 114 | buf.writeUInt8(0xac, 3); 115 | enc.decode(12, buf, function(err, type, value) { 116 | assert.equal(err, null); 117 | assert.equal(type, 'UNKN'); 118 | var decoded = enc.decodeDPT14(value); 119 | assert.equal(Math.round(decoded * 1000) / 1000, 0.301); 120 | }); 121 | }); 122 | it('should decode DPT13 32bit integer value', function() { 123 | var buf = Buffer.alloc(4); 124 | buf.writeInt32BE(0x6eadbeef, 0); 125 | enc.decode(12, buf, function(err, type, value) { 126 | assert.equal(err, null); 127 | assert.equal(type, 'UNKN'); 128 | var decoded = enc.decodeDPT13(value); 129 | assert.equal(decoded, 0x6eadbeef); 130 | }); 131 | }); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /lib/encoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Implements encode methods for dpt types 5 | */ 6 | function Encoder() { 7 | } 8 | 9 | /** 10 | * encode dpt 1 values 11 | */ 12 | Encoder.prototype.encodeDPT1 = function(value) { 13 | var buffer = Buffer.alloc(1); 14 | buffer.writeUInt8(value & 0x1, 0); 15 | return buffer; 16 | }; 17 | 18 | /** 19 | * encode dpt 2 values 20 | */ 21 | Encoder.prototype.encodeDPT2 = function(value) { 22 | var buffer = Buffer.alloc(1); 23 | buffer.writeUInt8(value & 0x3, 0); 24 | return buffer; 25 | }; 26 | 27 | /** 28 | * encode dpt 3 values 29 | */ 30 | Encoder.prototype.encodeDPT3 = function(value) { 31 | var buffer = Buffer.alloc(1); 32 | buffer.writeUInt8(value & 0xF, 0); 33 | return buffer; 34 | }; 35 | 36 | /** 37 | * encode dpt 5 values 38 | */ 39 | Encoder.prototype.encodeDPT5 = function(value) { 40 | var buffer = Buffer.alloc(1); 41 | buffer.writeUInt8(value & 0xFF, 0); 42 | return buffer; 43 | }; 44 | 45 | /** 46 | * encode dpt 7 values 47 | */ 48 | Encoder.prototype.encodeDPT7 = function(value) { 49 | const buffer = Buffer.alloc(2); 50 | buffer.writeUInt16BE(value & 0xFFFF, 0); 51 | return buffer; 52 | }; 53 | 54 | /** 55 | * encode 9 values 56 | */ 57 | Encoder.prototype.encodeDPT9 = function(value) { 58 | 59 | var data = [0,0]; 60 | // Reverse of the formula: FloatValue = (0,01M)2^E 61 | var exp = Math.floor(Math.max(Math.log(Math.abs(value)*100)/Math.log(2)-10, 0)); 62 | var mant = value * 100 / (1 << exp); 63 | 64 | //Fill in sign bit 65 | if(value < 0) { 66 | data[0] |= 0x80; 67 | mant = (~(mant * -1) + 1) & 0x07ff; 68 | } 69 | 70 | //Fill in exp (4bit) 71 | data[0] |= (exp & 0x0F) << 3; 72 | 73 | //Fill in mant 74 | data[0] |= (mant >> 8) & 0x7; 75 | data[1] |= mant & 0xFF; 76 | 77 | var buffer = Buffer.alloc(2); 78 | buffer.writeUInt8(data[0], 0); 79 | buffer.writeUInt8(data[1], 1); 80 | return buffer; 81 | }; 82 | 83 | /** 84 | * Encode DTP-10 values. Values is an array [dayOfTheWeek, hour, minutes, seconds] 85 | * 86 | * TIME: 10.001 (PDT_TIME). 87 | * 88 | * For time: 89 | * 3 bits for day of week (0 = none; 1 = Monday ; ...; 7 = Sunday) 90 | * 5 bits for hour (00-23) 91 | * ---- byte 1 92 | * 2 bits ZERO as padding 93 | * 6 bits for minutes (00-59) 94 | * ---- byte 2 95 | * 2 bits ZERO as padding 96 | * 6 bits for seconds (00-59) 97 | * ---- byte 3 98 | * 3 octets: N3U5 - r2U6 - r2U6 99 | */ 100 | Encoder.prototype.encodeDPT10 = function(value) { 101 | var dayOfTheWeek = value[0]; 102 | var hour = value[1]; 103 | var minutes = value[2]; 104 | var seconds = value[3]; 105 | 106 | var data = [0,0,0]; 107 | 108 | // First byte 109 | data[0] |= dayOfTheWeek << 5; // convert the day (mask 3 bits) and shift to left fot 5 bits 110 | data[0] |= hour & 0x1F; // convert the hour (mask 5 bits) 111 | 112 | // Second byte 113 | data[1] |= minutes & 0xFF; // convert the hour (mask 6 bits) 114 | 115 | // Third byte 116 | data[2] |= seconds & 0xFF; // convert the hour (mask 6 bits) 117 | 118 | var buffer = Buffer.alloc(3); 119 | buffer.writeUInt8(data[0], 0); 120 | buffer.writeUInt8(data[1], 1); 121 | buffer.writeUInt8(data[2], 2); 122 | return buffer; 123 | }; 124 | 125 | /** 126 | * Encode DTP-11 values. Values is an array [day, month, year] 127 | * 128 | * 000 + Day [1..31] 129 | * 0000 + Month [1..12] 130 | * 0 + Year [0..99] 131 | * For Dates: 132 | */ 133 | Encoder.prototype.encodeDPT11 = function(value) { 134 | 135 | var day = value[0]; 136 | var month = value[1]; 137 | var year = value[2]; 138 | 139 | var data = [0,0,0]; 140 | 141 | // First byte 142 | data[0] |= day; // convert the day 143 | 144 | // Second byte 145 | data[1] |= month; 146 | 147 | // Third byte 148 | data[2] |= year; // convert the hour (mask 6 bits) 149 | 150 | var buffer = Buffer.alloc(3); 151 | buffer.writeUInt8(data[0], 0); 152 | buffer.writeUInt8(data[1], 1); 153 | buffer.writeUInt8(data[2], 2); 154 | return buffer; 155 | }; 156 | 157 | 158 | Encoder.prototype.encode = function(DPTType, value) { 159 | 160 | var dottedDPTType = DPTType + '.'; 161 | if(dottedDPTType.indexOf('DPT1.') === 0) { 162 | return this.encodeDPT1(value); 163 | } else if(dottedDPTType.indexOf('DPT2.') === 0) { 164 | return this.encodeDPT2(value); 165 | } else if(dottedDPTType.indexOf('DPT3.') === 0) { 166 | return this.encodeDPT3(value); 167 | } else if(dottedDPTType.indexOf('DPT5.') === 0) { 168 | return this.encodeDPT5(value); 169 | } else if(dottedDPTType.indexOf('DPT7.') === 0) { 170 | return this.encodeDPT7(value); 171 | } else if(dottedDPTType.indexOf('DPT9.') === 0) { 172 | return this.encodeDPT9(value); 173 | } else if(dottedDPTType.indexOf('DPT10.') === 0) { 174 | return this.encodeDPT10(value); 175 | } else if(dottedDPTType.indexOf('DPT11.') === 0) { 176 | return this.encodeDPT11(value); 177 | } 178 | return undefined; 179 | }; 180 | 181 | module.exports = Encoder; 182 | -------------------------------------------------------------------------------- /lib/decoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Implements decode methods for dpt types 5 | */ 6 | function Decoder() { 7 | } 8 | 9 | /** 10 | * decode eis 8 / dpt 2 values 11 | */ 12 | Decoder.prototype.decodeDPT2 = function(buffer) { 13 | var data = buffer.readUInt8(0) & 0x3; 14 | return data; 15 | }; 16 | 17 | /** 18 | * decode eis 2 / dpt 3 values 19 | */ 20 | Decoder.prototype.decodeDPT3 = function(buffer) { 21 | var data = buffer.readUInt8(0) & 0xf; 22 | return data; 23 | }; 24 | 25 | /** 26 | * decode eis 13 / dpt 4 values 27 | */ 28 | Decoder.prototype.decodeDPT4 = function(buffer) { 29 | var value = buffer.readUInt8(0); 30 | if(value <= 127) { 31 | value = buffer.toString('ascii', 0); 32 | } else { 33 | value = buffer.toString('utf8', 0); 34 | } 35 | return value; 36 | }; 37 | 38 | /** 39 | * decode eis 14 / dpt 5 values 40 | */ 41 | Decoder.prototype.decodeDPT5 = function(buffer) { 42 | var data = buffer.readUInt8(0); 43 | return data; 44 | }; 45 | 46 | /** 47 | * decode eis 14 / dpt 6 values 48 | */ 49 | Decoder.prototype.decodeDPT6 = function(buffer) { 50 | var data = buffer.readInt8(0); 51 | return data; 52 | }; 53 | 54 | /** 55 | * decode EIS 10 / dpt 7 values 56 | */ 57 | Decoder.prototype.decodeDPT7 = function(buffer) { 58 | var value = buffer.readUInt16BE(0); 59 | return value; 60 | }; 61 | 62 | /** 63 | * decode EIS 10.001 / dpt 8 values 64 | */ 65 | Decoder.prototype.decodeDPT8 = function(buffer) { 66 | var value = buffer.readInt16BE(0); 67 | return value; 68 | }; 69 | 70 | /** 71 | * Decode eis 5 / dpt 9 values. 72 | * From the specs: FloatValue = (0,01M)2^E 73 | * - E = 0,15 74 | * - M = -2048,2047 75 | */ 76 | Decoder.prototype.decodeDPT9 = function(buffer) { 77 | var value = buffer.readUInt16BE(0); 78 | 79 | var sign = (value & 0x8000) >> 15; 80 | var exp = (value & 0x7800) >> 11; 81 | var mant = (value & 0x07ff); 82 | 83 | if(sign !== 0) { 84 | mant = -(~(mant - 1) & 0x07ff); 85 | } 86 | return 0.01 * mant * Math.pow(2,exp); 87 | }; 88 | 89 | /** 90 | * decode eis 3 / dpt 10 values 91 | */ 92 | Decoder.prototype.decodeDPT10 = function(buffer) { 93 | 94 | var value = new Date(); 95 | 96 | var weekDay = (buffer[0] & 0xe0) >> 5; 97 | var hour = buffer[0] & 0x1f; 98 | var min = buffer[1] & 0x3f; 99 | var sec = buffer[2] & 0x3f; 100 | 101 | value.setHours(hour); 102 | value.setMinutes(min); 103 | value.setSecondes(sec); 104 | var currentDay = value.getDay(); 105 | if(currentDay !== weekDay) { 106 | if(currentDay > weekDay) { 107 | value.setDate(value.getDate() + (weekDay - currentDay)); 108 | } else { 109 | value.setDate(value.getDate() - (weekDay - currentDay)); 110 | } 111 | } 112 | return value; 113 | }; 114 | 115 | /** 116 | * decode eis 4 / dpt 11 values 117 | */ 118 | Decoder.prototype.decodeDPT11 = function(buffer) { 119 | 120 | var day = buffer[0] & 0x1f; 121 | var mon = buffer[1] & 0xf; 122 | var year = buffer[2] & 0x7f; 123 | 124 | if(year < 90) { 125 | year += 2000; 126 | } else { 127 | year += 1900; 128 | } 129 | 130 | var value = new Date(year, mon, day); 131 | 132 | return value; 133 | }; 134 | 135 | /** 136 | * decode eis 11 / dpt 12 values 137 | */ 138 | Decoder.prototype.decodeDPT12 = function(buffer) { 139 | 140 | var value = buffer.readUInt32BE(0); 141 | return value; 142 | 143 | }; 144 | 145 | /** 146 | * decode eis 11.001 / dpt 13 values 147 | */ 148 | Decoder.prototype.decodeDPT13 = function(buffer) { 149 | 150 | var value = buffer.readInt32BE(0); 151 | return value; 152 | 153 | }; 154 | 155 | /** 156 | * decode eis 9 / dpt 14 values 157 | */ 158 | Decoder.prototype.decodeDPT14 = function(buffer) { 159 | 160 | var value = buffer.readFloatBE(0); 161 | return value; 162 | 163 | }; 164 | 165 | Decoder.prototype.decodeDPT16 = function(buffer) { 166 | 167 | var value = ""; 168 | for(var i = 0; i < buffer.length; i++) { 169 | value += String.fromCharCode(buffer.readUInt8(i)); 170 | } 171 | return value; 172 | 173 | }; 174 | 175 | Decoder.prototype.decodeDPT232 = function(buffer) { 176 | 177 | var blue = buffer[0]; 178 | var green = buffer[1]; 179 | var red = buffer[2]; 180 | 181 | var value = [red, green, blue] 182 | return value; 183 | 184 | }; 185 | 186 | /** 187 | * decode value 188 | */ 189 | Decoder.prototype.decode = function(len, data, callback) { 190 | 191 | var err = null; 192 | var type = 'UNKN'; 193 | var value = null; 194 | 195 | // eis 1 / dpt 1.xxx 196 | if(len === 8) { 197 | type = 'DPT1'; 198 | value = data & 1; 199 | } 200 | 201 | // eis 6 / dpt 5.xxx 202 | // assumption 203 | if(len === 9){ 204 | type = 'DPT5'; 205 | if(data.length === 1) { 206 | value = this.decodeDPT5(data); 207 | } else { 208 | err = new Error('Invalid data len for DPT5'); 209 | } 210 | } 211 | 212 | // eis 5 / dpt 9.xxx 213 | // assumption 214 | if(len === 10) { 215 | type = 'DPT9'; 216 | if(data.length === 2) { 217 | value = this.decodeDPT9(data); 218 | } 219 | else { 220 | err = new Error('Invalid data len for DPT9'); 221 | } 222 | } 223 | 224 | //If still unkown take the raw Buffer 225 | if(type === 'UNKN') { 226 | value = data; 227 | } 228 | 229 | if(callback) { 230 | callback(err, type, value); 231 | } 232 | }; 233 | 234 | module.exports = Decoder; 235 | -------------------------------------------------------------------------------- /lib/connection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var parser = require('./parser'); 4 | var tools = require('./tools'); 5 | var net = require('net'); 6 | var events = require('events'); 7 | var util = require('util'); 8 | 9 | function Connection() { 10 | var self = this; 11 | events.EventEmitter.call(self); 12 | 13 | self.data = Buffer.from([]); 14 | self.parser = null; 15 | self.conn = null; 16 | 17 | } 18 | util.inherits(Connection, events.EventEmitter); 19 | 20 | /** 21 | * Opens a connection eibd over TCP/IP or UNIX local socket - establishes the connection to the given host/port or path. 22 | * Does not verify it is a valid eibd/knxd connection (yet) 23 | * 24 | * @param {Object} opts - either {host:ip, port:port} or {path:unixSocketPath} type object 25 | * @param {function} callback - Function(err) to be called upon completion 26 | */ 27 | Connection.prototype.socketRemote = function(opts, callback) { 28 | 29 | var self = this; 30 | 31 | if ((!opts.host || !opts.port) && !opts.path) { 32 | callback(new Error( 33 | 'please provide a host and a port as argument or supply a UNIX socket path!')); 34 | return; 35 | } 36 | 37 | self.host = opts.host; 38 | self.port = opts.port; 39 | self.path = opts.path; 40 | 41 | self.socket = net.connect(opts, callback); 42 | self.socket.on('error', callback); 43 | self.socket.on('error', self.onError); 44 | self.socket.on('close', function() { 45 | self.emit('close'); 46 | }); 47 | 48 | }; 49 | 50 | /** 51 | * error handler 52 | */ 53 | Connection.prototype.onError = function(err) { 54 | var self = this; 55 | self.end(); 56 | }; 57 | 58 | /** 59 | * close function - Ends the net.Socket 60 | * 61 | */ 62 | Connection.prototype.end = function() { 63 | var self = this; 64 | if (self.socket) { 65 | self.socket.end(); 66 | } 67 | 68 | if (self.parser) { 69 | self.parser = null; 70 | } 71 | }; 72 | 73 | /** 74 | * Opens a Group communication interface 75 | */ 76 | Connection.prototype.openGroupSocket = function(writeOnly, callback) { 77 | 78 | var self = this; 79 | 80 | var arr = new Array(5); 81 | arr[0] = 0; 82 | arr[1] = 38; 83 | arr[2] = 0; 84 | arr[3] = 0; 85 | 86 | if (writeOnly !== 0) { 87 | arr[4] = 0xff; 88 | } else { 89 | arr[4] = 0x00; 90 | } 91 | 92 | self.sendRequest(arr); 93 | 94 | self.parser = new parser(self.socket); 95 | 96 | if (callback) { 97 | callback(self.parser); 98 | } 99 | 100 | }; 101 | 102 | /** 103 | * Opens a connection of type T_Group 104 | * 105 | * @param {integer} dest - Numeric representation of the destination address (16 Bit UINT) 106 | * @param {Boolean} writeOnly - If true, connection is open for write only 107 | * @param {function} callback - Function(err) to be called upon completion 108 | */ 109 | Connection.prototype.openTGroup = function(dest, writeOnly, callback) { 110 | var self = this; 111 | 112 | // prepare dest 113 | var arr = new Array(5); 114 | arr[0] = 0; 115 | arr[1] = 34; 116 | arr[2] = (dest >> 8) & 0xff; 117 | arr[3] = dest & 0xff; 118 | 119 | if (writeOnly !== 0) { 120 | arr[4] = 0xff; 121 | } else { 122 | arr[4] = 0x00; 123 | } 124 | 125 | var buffer = null; 126 | 127 | var handler = function(data) { 128 | if (buffer) { 129 | buffer = Buffer.concat([ 130 | buffer, 131 | data ]); 132 | } else { 133 | buffer = data; 134 | } 135 | 136 | if (buffer.length < 2) { 137 | return; 138 | } 139 | if (buffer[0] << 8 | buffer[1] > buffer.length - 2) { 140 | return; 141 | } 142 | data = buffer; 143 | self.socket.removeListener('data', handler); 144 | 145 | if (data[0] << 8 | data[1] === 2) { 146 | if (data[2] << 8 | data[3] === 34) { 147 | callback(null); 148 | } else { 149 | callback(new Error('request invalid')); 150 | } 151 | } else { 152 | callback(new Error('invalid buffer length received')); 153 | } 154 | 155 | }; 156 | 157 | self.socket.on('data', handler); 158 | 159 | self.sendRequest(arr); 160 | 161 | }; 162 | 163 | /** 164 | * Sends an APDU (Application Program Data Unit) 165 | * 166 | * @param {array} data - The telegram data to be sent 167 | * @param {function} callback - function(error) to be called upon completion 168 | * @param {Boolean} doNotCloseConnection - If true, leaves the connection to knxd open for more telegrams 169 | */ 170 | Connection.prototype.sendAPDU = function(data, callback, doNotCloseConnection) { 171 | var self = this; 172 | var start = [ 173 | 0, 174 | 37 ]; 175 | var arr = start.concat(data); 176 | 177 | if (data.length === 3) { 178 | arr[4] = data[2]; 179 | } 180 | 181 | self.sendRequest(arr, function() { 182 | if (!doNotCloseConnection) { 183 | self.end(); 184 | } 185 | 186 | if (callback) { 187 | callback(); 188 | } 189 | }); 190 | 191 | }; 192 | 193 | /** 194 | * Sends TCP/IP request to eib-daemon 195 | */ 196 | Connection.prototype.sendRequest = function(input, callback) { 197 | 198 | var self = this; 199 | var data = new Array(input.length + 2); 200 | 201 | data[0] = (input.length >> 8) & 0xff; 202 | data[1] = input.length & 0xff; 203 | 204 | for (var i = 2; i < data.length; i++) { 205 | data[i] = input[i - 2]; 206 | } 207 | 208 | var buf = tools.pack(data); 209 | 210 | self.socket.write(buf, callback); 211 | 212 | }; 213 | 214 | function init() { 215 | var e = new Connection(); 216 | return e; 217 | } 218 | 219 | module.exports = init; 220 | -------------------------------------------------------------------------------- /test/encoder.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'), 4 | Encoder = require('../lib/encoder.js'); 5 | 6 | var enc = null; 7 | 8 | describe('Encoder', function() { 9 | 10 | before(function() { 11 | enc = new Encoder(); 12 | }); 13 | 14 | describe('DPT1 encode', function() { 15 | it('should encode DPT1 value', function() { 16 | var buffer = enc.encode('DPT1',0); 17 | assert.equal(buffer.readUInt8(0), 0); 18 | 19 | buffer = enc.encode('DPT1',1); 20 | assert.equal(buffer.readUInt8(0), 1); 21 | 22 | buffer = enc.encode('DPT1',5); 23 | assert.equal(buffer.readUInt8(0), 1); 24 | 25 | buffer = enc.encode('DPT1',6); 26 | assert.equal(buffer.readUInt8(0), 0); 27 | }); 28 | }); 29 | 30 | describe('DPT2 encode', function() { 31 | it('should encode DPT2 value', function() { 32 | var buffer = enc.encode('DPT2',0); 33 | assert.equal(buffer.readUInt8(0), 0); 34 | 35 | buffer = enc.encode('DPT2',3); 36 | assert.equal(buffer.readUInt8(0), 3); 37 | 38 | buffer = enc.encode('DPT2',4); 39 | assert.equal(buffer.readUInt8(0), 0); 40 | 41 | buffer = enc.encode('DPT2',5); 42 | assert.equal(buffer.readUInt8(0), 1); 43 | }); 44 | }); 45 | 46 | describe('DPT3 encode', function() { 47 | it('should encode DPT3 value', function() { 48 | var buffer = enc.encode('DPT3',0); 49 | assert.equal(buffer.readUInt8(0), 0); 50 | 51 | buffer = enc.encode('DPT3',15); 52 | assert.equal(buffer.readUInt8(0), 15); 53 | 54 | buffer = enc.encode('DPT3',16); 55 | assert.equal(buffer.readUInt8(0), 0); 56 | 57 | buffer = enc.encode('DPT3',17); 58 | assert.equal(buffer.readUInt8(0), 1); 59 | }); 60 | }); 61 | 62 | describe('DPT5 encode', function() { 63 | it('should encode DPT5 value', function() { 64 | var buffer = enc.encode('DPT5',40); 65 | assert.equal(buffer.readUInt8(0), 40); 66 | }); 67 | }); 68 | 69 | describe('DPT7 encode', function() { 70 | it('should encode DPT7 value', function() { 71 | var buffer = enc.encode('DPT7',0); 72 | assert.equal(buffer.readUInt16BE(0), 0x0000); 73 | 74 | buffer = enc.encode('DPT7',2550); 75 | assert.equal(buffer.readUInt16BE(0), 0x09F6); 76 | 77 | buffer = enc.encode('DPT7',31247); 78 | assert.equal(buffer.readUInt16BE(0), 0x7A0F); 79 | 80 | buffer = enc.encode('DPT7',65535); 81 | assert.equal(buffer.readUInt16BE(0), 0xFFFF); 82 | }); 83 | }); 84 | 85 | describe('DPT9 encode', function() { 86 | it('should encode DPT9 value', function() { 87 | var buffer = enc.encode('DPT9',40); 88 | var value = buffer.readUInt16BE(0); 89 | assert.equal(value, 0x0FD0); // was: 0x13e8 90 | 91 | buffer = enc.encode('DPT9.001',50); 92 | value = buffer.readUInt16BE(0); 93 | assert.equal(value, 0x14e2); 94 | }); 95 | 96 | it('should encode DPT9 floating value', function() { 97 | var buffer = enc.encode('DPT9',20.2); 98 | var value = buffer.readUInt16BE(0); 99 | assert.equal(value, 0x07E4); // was 0x11F9 100 | 101 | buffer = enc.encode('DPT9',20.2); 102 | value = buffer.readUInt16BE(0); 103 | assert.equal(value, 0x07E4); 104 | }); 105 | 106 | it('should encode DPT9 negative value', function() { 107 | var buffer = enc.encode('DPT9',-100.32); 108 | var value = buffer.readUInt16BE(0); 109 | assert.equal(value, 0x9B1A); 110 | 111 | buffer = enc.encode('DPT9',-175.84); 112 | value = buffer.readUInt16BE(0); 113 | assert.equal(value, 0xA3B5); 114 | 115 | buffer = enc.encode('DPT9',-199.52); 116 | value = buffer.readUInt16BE(0); 117 | assert.equal(value, 0xA321); 118 | 119 | buffer = enc.encode('DPT9',-200.32); 120 | value = buffer.readUInt16BE(0); 121 | assert.equal(value, 0xA31C); // was: 0xB6C7 122 | 123 | buffer = enc.encode('DPT9',-5.08); 124 | value = buffer.readUInt16BE(0); 125 | assert.equal(value, 0x8604); 126 | }); 127 | 128 | it('should not encode DPTNOTIMPLEMENTED', function() { 129 | var buffer = enc.encode('DPTNOTIMPLEMENTED', -5.08); 130 | assert.equal(buffer, undefined); 131 | }); 132 | 133 | }); 134 | 135 | describe('DPT10 encoder', function() { 136 | it('should encode DPT10 value', function(done) { 137 | 138 | var dayOfTheWeek = 2; // Tuesday -> 010 139 | var hour = 10; // 10 AM -> 01010 140 | var minutes = 43; 141 | var seconds = 5; 142 | 143 | var buffer = enc.encode('DPT10', [dayOfTheWeek, hour, minutes, seconds]); 144 | var value0 = buffer.readUInt8(0); // Read the first octect 145 | var value1 = buffer.readUInt8(1); // Read the second octect 146 | var value2 = buffer.readUInt8(2); // Read the third octect 147 | 148 | assert.equal(value0, 0x4A); // 01001010 = 4A 149 | assert.equal(value1, 0x2B); // 43 = 00101011 = 2B 150 | assert.equal(value2, 0x05); // 5 = 00000101 = 05 151 | 152 | done(); 153 | }); 154 | }); 155 | 156 | describe('DPT11 encoder', function() { 157 | it('should encode DPT11 value', function(done) { 158 | 159 | var day = 2; // February -> 010 160 | var month = 10; // 10 day -> A 161 | var year = 14; // 14 -> 0xE -> 00001110 162 | 163 | var buffer = enc.encode('DPT11', [day, month, year]); 164 | var value0 = buffer.readUInt8(0); // Read the first octect 165 | var value1 = buffer.readUInt8(1); // Read the second octect 166 | var value2 = buffer.readUInt8(2); // Read the third octect 167 | 168 | assert.equal(value0, 0x02); // 00000010 = 2 169 | assert.equal(value1, 0x0A); // 10 = 00001010 = A 170 | assert.equal(value2, 0x0E); // 14 = 00001110 = 05 171 | 172 | done(); 173 | }); 174 | }); 175 | 176 | }); 177 | --------------------------------------------------------------------------------