├── .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 [](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 |
--------------------------------------------------------------------------------