iobroker.openknx 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/admin/index_m.html +16 -59
- package/io-package.json +1 -1
- package/lib/knx/AUTHORS +3 -0
- package/lib/knx/LICENSE +19 -0
- package/lib/knx/README-API.md +138 -0
- package/lib/knx/README-datapoints.md +62 -0
- package/lib/knx/README-events.md +50 -0
- package/lib/knx/README-knxd.md +5 -0
- package/lib/knx/README-resilience.md +38 -0
- package/lib/knx/README.md +54 -0
- package/lib/knx/bitbucket-pipelines.yml +7 -0
- package/lib/knx/index.d.ts +70 -0
- package/lib/knx/index.js +19 -0
- package/lib/knx/manualtest/test-toggle.js +54 -0
- package/lib/knx/manualtest/test-writestorm.js +43 -0
- package/lib/knx/package.json +117 -0
- package/lib/knx/run-wired-tests.sh +2 -0
- package/lib/knx/src/Address.js +116 -0
- package/lib/knx/src/Connection.js +328 -0
- package/lib/knx/src/Datapoint.js +136 -0
- package/lib/knx/src/FSM.js +649 -0
- package/lib/knx/src/IpRoutingConnection.js +68 -0
- package/lib/knx/src/IpTunnelingConnection.js +43 -0
- package/lib/knx/src/KnxConstants.js +129 -0
- package/lib/knx/src/KnxLog.js +28 -0
- package/lib/knx/src/KnxProtocol.js +691 -0
- package/lib/knx/src/devices/BinarySwitch.js +51 -0
- package/lib/knx/src/devices/index.js +1 -0
- package/lib/knx/src/dptlib/dpt1.js +223 -0
- package/lib/knx/src/dptlib/dpt10.js +96 -0
- package/lib/knx/src/dptlib/dpt11.js +78 -0
- package/lib/knx/src/dptlib/dpt12.js +25 -0
- package/lib/knx/src/dptlib/dpt13.js +73 -0
- package/lib/knx/src/dptlib/dpt14.js +165 -0
- package/lib/knx/src/dptlib/dpt15.js +24 -0
- package/lib/knx/src/dptlib/dpt16.js +45 -0
- package/lib/knx/src/dptlib/dpt17.js +25 -0
- package/lib/knx/src/dptlib/dpt18.js +84 -0
- package/lib/knx/src/dptlib/dpt19.js +56 -0
- package/lib/knx/src/dptlib/dpt2.js +136 -0
- package/lib/knx/src/dptlib/dpt20.js +40 -0
- package/lib/knx/src/dptlib/dpt21.js +78 -0
- package/lib/knx/src/dptlib/dpt232.js +51 -0
- package/lib/knx/src/dptlib/dpt237.js +72 -0
- package/lib/knx/src/dptlib/dpt238.js +74 -0
- package/lib/knx/src/dptlib/dpt3.js +78 -0
- package/lib/knx/src/dptlib/dpt4.js +49 -0
- package/lib/knx/src/dptlib/dpt5.js +54 -0
- package/lib/knx/src/dptlib/dpt6.js +33 -0
- package/lib/knx/src/dptlib/dpt7.js +94 -0
- package/lib/knx/src/dptlib/dpt8.js +83 -0
- package/lib/knx/src/dptlib/dpt9.js +229 -0
- package/lib/knx/src/dptlib/index.js +215 -0
- package/lib/knx/typescript-sample/README.md +15 -0
- package/lib/knx/typescript-sample/package.json +17 -0
- package/lib/knx/typescript-sample/test-toggle-onoff.ts +43 -0
- package/lib/knx/typescript-sample/tsconfig.json +20 -0
- package/lib/knx/typescript-sample/tslint.json +58 -0
- package/lib/{generateGAS.js → projectImport.js} +4 -28
- package/lib/tools.js +3 -2
- package/main.js +20 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -168,24 +168,23 @@ If the adapter startet successfully your datapoints will be available for commun
|
|
|
168
168
|
|
|
169
169
|
## Datapoint Types
|
|
170
170
|
Wide DPT (datapoint type) support (DPT1 - DPT21, DPT232, DPT237, DPT238 supported)
|
|
171
|
+
Values of unhandeled DPTs can be written and read out in raw format.
|
|
171
172
|
|
|
172
173
|
# Special settings
|
|
173
174
|
|
|
174
|
-
|
|
175
175
|
# Known Problems
|
|
176
|
-
- raw value write where missing DPTs not fully implemented
|
|
177
|
-
- cannot receive ga of unsupported dpt
|
|
178
176
|
- sends write instead of response on GroupValue_Read
|
|
179
|
-
- setting onlyAddNewObjects not working
|
|
180
177
|
- knxprj file only tested from ETS 5.7.4
|
|
181
178
|
- IOBroker object role definition missing
|
|
182
179
|
|
|
183
180
|
# limitations
|
|
184
|
-
-
|
|
181
|
+
- three level group address are only supported
|
|
185
182
|
|
|
186
183
|
## Changelog
|
|
187
|
-
### 0.0.
|
|
188
|
-
* raw value handling
|
|
184
|
+
### 0.0.17
|
|
185
|
+
* raw value handling, can now write and receive ga of unsupported dpt
|
|
186
|
+
* setting onlyAddNewObjects fixed
|
|
187
|
+
* adapter restart after import
|
|
189
188
|
|
|
190
189
|
### 0.0.14
|
|
191
190
|
* import ga xml
|
package/admin/index_m.html
CHANGED
|
@@ -19,8 +19,6 @@
|
|
|
19
19
|
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
20
20
|
<script type="text/javascript" src="words.js"></script>
|
|
21
21
|
|
|
22
|
-
<script type="text/javascript" src="zip/zip.js"></script>
|
|
23
|
-
<script type="text/javascript" src="zip/inflate.js"></script>
|
|
24
22
|
|
|
25
23
|
<style>
|
|
26
24
|
#progress {
|
|
@@ -163,7 +161,7 @@
|
|
|
163
161
|
if (ValidateIPaddress(ip1)) {
|
|
164
162
|
$('#gwip').val(ip1);
|
|
165
163
|
} else {
|
|
166
|
-
alert("
|
|
164
|
+
alert("invalid address");
|
|
167
165
|
}
|
|
168
166
|
}
|
|
169
167
|
|
|
@@ -172,7 +170,7 @@
|
|
|
172
170
|
if (ValidatePortaddress(gwipport1)) {
|
|
173
171
|
$('#gwipport').val(gwipport1);
|
|
174
172
|
} else {
|
|
175
|
-
alert("
|
|
173
|
+
alert("invalid portnumber");
|
|
176
174
|
}
|
|
177
175
|
}
|
|
178
176
|
|
|
@@ -181,7 +179,7 @@
|
|
|
181
179
|
if (ValidateEIBaddress(eibadr1)) {
|
|
182
180
|
$('#eibadr').val(eibadr1);
|
|
183
181
|
} else {
|
|
184
|
-
alert("
|
|
182
|
+
alert("physical EIB address not valid");
|
|
185
183
|
}
|
|
186
184
|
}
|
|
187
185
|
|
|
@@ -224,7 +222,6 @@
|
|
|
224
222
|
}
|
|
225
223
|
});
|
|
226
224
|
|
|
227
|
-
|
|
228
225
|
//start with no changed settings, disable save button
|
|
229
226
|
onChange(false);
|
|
230
227
|
// reinitialize all the Materialize labels on the page if you are dynamically adding inputs:
|
|
@@ -249,7 +246,6 @@
|
|
|
249
246
|
callback(obj);
|
|
250
247
|
}
|
|
251
248
|
|
|
252
|
-
|
|
253
249
|
function fileHandler(event) {
|
|
254
250
|
event.preventDefault();
|
|
255
251
|
|
|
@@ -259,18 +255,10 @@
|
|
|
259
255
|
console.warn('invalid file object');
|
|
260
256
|
return false;
|
|
261
257
|
}
|
|
262
|
-
if (file.size > 100000000) {
|
|
263
|
-
showMessage('Filezise is too big');
|
|
264
|
-
$('#knxprojFile').replaceWith($(knxprojFile));
|
|
265
|
-
$('#knxprojFile').change(fileHandler);
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
258
|
|
|
269
259
|
$('.file-field .btn').addClass('disabled');
|
|
270
260
|
$('.file-field .file-path').prop('disabled', true);
|
|
271
261
|
|
|
272
|
-
zip.useWebWorkers = false;
|
|
273
|
-
|
|
274
262
|
if (file.name.split('.').pop() == 'xml') {
|
|
275
263
|
var reader = new FileReader();
|
|
276
264
|
reader.addEventListener('load', function readFile(event) {
|
|
@@ -278,18 +266,14 @@
|
|
|
278
266
|
transmitXML();
|
|
279
267
|
|
|
280
268
|
function transmitXML() {
|
|
281
|
-
|
|
282
|
-
//if (callback < maxCat) {
|
|
283
|
-
// setTimeout(transmitXML, 100);
|
|
284
|
-
// return;
|
|
285
|
-
//}
|
|
269
|
+
|
|
286
270
|
$('#knxprojFile').replaceWith($(knxprojFile));
|
|
287
271
|
$('#knxprojFile').change(fileHandler);
|
|
288
272
|
|
|
289
|
-
showToast(null, _('Please wait while
|
|
273
|
+
showToast(null, _('Please wait while importing Objects...'), null, 5000);
|
|
290
274
|
$('#progress .determinate').removeClass('determinate').addClass('indeterminate');
|
|
291
275
|
$('#onlyAddNewObjects').addClass('disabled');
|
|
292
|
-
sendTo(null, '
|
|
276
|
+
sendTo(null, 'import', { xml: event.target.result, onlyAddNewObjects: $('#onlyAddNewObjects').prop('checked') }, function (result) {
|
|
293
277
|
$('#onlyAddNewObjects').removeClass('disabled');
|
|
294
278
|
$('.file-field .btn').removeClass('disabled');
|
|
295
279
|
$('.file-field .file-path').prop('disabled', false);
|
|
@@ -299,7 +283,11 @@
|
|
|
299
283
|
showMessage(_('Error: ') + (result ? result.error : _('Unknown')));
|
|
300
284
|
} else {
|
|
301
285
|
showMessage(_('Extracted %s states', result.count));
|
|
302
|
-
sendTo(null, '
|
|
286
|
+
sendTo(null, 'reset', null);
|
|
287
|
+
|
|
288
|
+
//todo enable save button
|
|
289
|
+
$('.file-field .btn').addClass('enabled');
|
|
290
|
+
$('.file-field .file-path').prop('enabled', true);
|
|
303
291
|
}
|
|
304
292
|
});
|
|
305
293
|
}
|
|
@@ -320,15 +308,12 @@
|
|
|
320
308
|
// I will check only 2 first numbers of ipV4
|
|
321
309
|
var i1 = ip1.split('.');
|
|
322
310
|
var i2 = ip2.split('.');
|
|
323
|
-
|
|
324
311
|
// If ipv6 or DNS name
|
|
325
312
|
if (i1.length !== i2.length || i1.length !== 4) return 1;
|
|
326
|
-
|
|
327
313
|
if (i1[0] !== i2[0]) return 0;
|
|
328
314
|
if (i1[1] !== i2[1]) return 1;
|
|
329
315
|
if (i1[2] !== i2[2]) return 2;
|
|
330
316
|
if (i1[3] !== i2[3]) return 3;
|
|
331
|
-
|
|
332
317
|
return 4;
|
|
333
318
|
}
|
|
334
319
|
|
|
@@ -348,11 +333,9 @@
|
|
|
348
333
|
}
|
|
349
334
|
|
|
350
335
|
|
|
351
|
-
|
|
352
336
|
//todo ??
|
|
353
337
|
$('.switch').find('input[type=checkbox]').on('change', function () {
|
|
354
338
|
var status = $(this).prop('checked');
|
|
355
|
-
|
|
356
339
|
$.ajax({
|
|
357
340
|
url: url,
|
|
358
341
|
type: 'post',
|
|
@@ -360,35 +343,6 @@
|
|
|
360
343
|
})
|
|
361
344
|
});
|
|
362
345
|
|
|
363
|
-
|
|
364
|
-
//todo ??
|
|
365
|
-
var modalInstance;
|
|
366
|
-
document.addEventListener('DOMContentLoaded', function () {
|
|
367
|
-
var modalelem = document.querySelector(".modal");
|
|
368
|
-
modalInstance = M.Modal.init(modalelem);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
//todo ??
|
|
372
|
-
function openPasswordModal() {
|
|
373
|
-
// document.getElementById("password").value = "";
|
|
374
|
-
modalInstance.open();
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
//todo ??
|
|
378
|
-
function getPassword() {
|
|
379
|
-
openPasswordModal(this);
|
|
380
|
-
// passwordOKBtn.onclick = function() {
|
|
381
|
-
// console.log(document.getElementById("password").value);
|
|
382
|
-
// modalInstance.value = document.getElementById("password");
|
|
383
|
-
// modalInstance.close();
|
|
384
|
-
// }
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function closePasswordModal() {
|
|
388
|
-
//console.log(document.getElementById("password").value);
|
|
389
|
-
modalInstance.close();
|
|
390
|
-
}
|
|
391
|
-
|
|
392
346
|
function disableAllDialogs() {
|
|
393
347
|
$('.c-tab').removeClass('tab-active');
|
|
394
348
|
var x = document.getElementsByClassName("dialog");
|
|
@@ -404,12 +358,14 @@
|
|
|
404
358
|
x.style.display = "block";
|
|
405
359
|
}
|
|
406
360
|
|
|
361
|
+
/*
|
|
407
362
|
function showDebugDialog() {
|
|
408
363
|
disableAllDialogs();
|
|
409
364
|
$('.dialog-debug').addClass('tab-active');
|
|
410
365
|
var x = document.getElementById("debugDialog");
|
|
411
366
|
x.style.display = "block";
|
|
412
367
|
}
|
|
368
|
+
*/
|
|
413
369
|
|
|
414
370
|
</script>
|
|
415
371
|
</head>
|
|
@@ -417,16 +373,17 @@
|
|
|
417
373
|
<body>
|
|
418
374
|
<!-- you have to put your config page in a div with id adapter-container -->
|
|
419
375
|
<div class="m adapter-container">
|
|
376
|
+
<!--
|
|
420
377
|
<nav>
|
|
421
378
|
<div class="nav-wrapper">
|
|
422
379
|
<a href="#!" class="brand-logo"><img src="openknx.png" width="65" height="auto" alt="logo" /></a>
|
|
423
380
|
<ul class="right hide-on-med-and-down">
|
|
424
381
|
<li><a onclick="showConfigDialog()" class="dialog-config c-tab translate">Configuration</a></li>
|
|
425
|
-
|
|
382
|
+
<li><a onclick="showDebugDialog()" class="dialog-debug c-tab translate">Debug</a></li>
|
|
426
383
|
</ul>
|
|
427
384
|
</div>
|
|
428
385
|
</nav>
|
|
429
|
-
|
|
386
|
+
-->
|
|
430
387
|
<div id="passwordModal" class="modal" role="dialog">
|
|
431
388
|
<div class="modal-dialog">
|
|
432
389
|
<div class="modal-content">
|
package/io-package.json
CHANGED
package/lib/knx/AUTHORS
ADDED
package/lib/knx/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2016 Elias Karakoulakis
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
8
|
+
so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
## Connect to your KNX IP router
|
|
2
|
+
|
|
3
|
+
By default *you only need to specify a 'handlers' object* containing your functions to handle KNX events. All the other options have defaults that can be overridden according to your needs.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
var connection = new knx.Connection( {
|
|
8
|
+
// ip address and port of the KNX router or interface
|
|
9
|
+
ipAddr: '127.0.0.1', ipPort: 3671,
|
|
10
|
+
// in case you need to specify the multicast interface (say if you have more than one)
|
|
11
|
+
interface: 'eth0',
|
|
12
|
+
// the KNX physical address we'd like to use
|
|
13
|
+
physAddr: '15.15.15',
|
|
14
|
+
// set the log level for messsages printed on the console. This can be 'error', 'warn', 'info' (default), 'debug', or 'trace'.
|
|
15
|
+
loglevel: 'info',
|
|
16
|
+
// do not automatically connect, but use connection.Connect() to establish connection
|
|
17
|
+
manualConnect: true,
|
|
18
|
+
// use tunneling with multicast (router) - this is NOT supported by all routers! See README-resilience.md
|
|
19
|
+
forceTunneling: true,
|
|
20
|
+
// wait at least 10 millisec between each datagram
|
|
21
|
+
minimumDelay: 10,
|
|
22
|
+
// enable this option to suppress the acknowledge flag with outgoing L_Data.req requests. LoxOne needs this
|
|
23
|
+
suppress_ack_ldatareq: false,
|
|
24
|
+
// 14/03/2020 In tunneling mode, echoes the sent message by emitting a new emitEvent, so other object with same group address, can receive the sent message. Default is false.
|
|
25
|
+
localEchoInTunneling:false,
|
|
26
|
+
// define your event handlers here:
|
|
27
|
+
handlers: {
|
|
28
|
+
// wait for connection establishment before sending anything!
|
|
29
|
+
connected: function() {
|
|
30
|
+
console.log('Hurray, I can talk KNX!');
|
|
31
|
+
// WRITE an arbitrary boolean request to a DPT1 group address
|
|
32
|
+
connection.write("1/0/0", 1);
|
|
33
|
+
// you also WRITE to an explicit datapoint type, eg. DPT9.001 is temperature Celcius
|
|
34
|
+
connection.write("2/1/0", 22.5, "DPT9.001");
|
|
35
|
+
// you can also issue a READ request and pass a callback to capture the response
|
|
36
|
+
connection.read("1/0/1", (src, responsevalue) => { ... });
|
|
37
|
+
},
|
|
38
|
+
// get notified for all KNX events:
|
|
39
|
+
event: function(evt, src, dest, value) { console.log(
|
|
40
|
+
"event: %s, src: %j, dest: %j, value: %j",
|
|
41
|
+
evt, src, dest, value
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
// get notified on connection errors
|
|
45
|
+
error: function(connstatus) {
|
|
46
|
+
console.log("**** ERROR: %j", connstatus);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Important**: connection.write() will only accept *raw APDU payloads* and a DPT.
|
|
53
|
+
This practically means that for *reading and writing to anything other than a binary
|
|
54
|
+
switch* (eg. for dimmer controls) you'll need to declare one or more *datapoints*.
|
|
55
|
+
|
|
56
|
+
### Declare datapoints based on their DPT
|
|
57
|
+
|
|
58
|
+
Datapoints correlate an *endpoint* (identifed by a group address such as '1/2/3')
|
|
59
|
+
with a *DPT* (DataPoint Type), so that *serialization* of values to and from KNX
|
|
60
|
+
works correctly (eg. temperatures as 16bit floats), and values are being translated
|
|
61
|
+
to Javascript objects and back.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// declare a simple binary control datapoint
|
|
65
|
+
var binary_control = new knx.Datapoint({ga: '1/0/1', dpt: 'DPT1.001'});
|
|
66
|
+
// bind it to the active connection
|
|
67
|
+
binary_control.bind(connection);
|
|
68
|
+
// write a new value to the bus
|
|
69
|
+
binary_control.write(true); // or false!
|
|
70
|
+
// send a read request, and fire the callback upon response
|
|
71
|
+
binary_control.read( function (response) {
|
|
72
|
+
console.log("KNX response: %j", response);
|
|
73
|
+
};
|
|
74
|
+
// or declare a dimmer control
|
|
75
|
+
var dimmer_control = new knx.Datapoint({ga: '1/2/33', dpt: 'DPT3.007'});
|
|
76
|
+
// declare a binary STATUS datapoint, which will automatically read off its value
|
|
77
|
+
var binary_status = new knx.Datapoint({ga: '1/0/1', dpt: 'DPT1.001', autoread: true});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Datapoints need to be bound to a connection. This can be done either at their
|
|
81
|
+
creation, *or* using their `bind()` call. Its important to highlight that before
|
|
82
|
+
you start defining datapoints (and devices as we'll see later), your code
|
|
83
|
+
*needs to ensure that the connection has been established*, usually by declaring them in the 'connected' handler:
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
var connection = knx.Connection({
|
|
87
|
+
handlers: {
|
|
88
|
+
connected: function() {
|
|
89
|
+
console.log('----------');
|
|
90
|
+
console.log('Connected!');
|
|
91
|
+
console.log('----------');
|
|
92
|
+
var dp = new knx.Datapoint({ga: '1/1/1'}, connection);
|
|
93
|
+
// Now send off a couple of requests:
|
|
94
|
+
dp.read((src, value) => {
|
|
95
|
+
console.log("**** RESPONSE %j reports current value: %j", src, value);
|
|
96
|
+
});
|
|
97
|
+
dp.write(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Declare your devices
|
|
104
|
+
|
|
105
|
+
You can define a device (basically a set of GA's that are related to a
|
|
106
|
+
physical KNX device eg. a binary switch) so that you have higher level of control:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
var light = new knx.Devices.BinarySwitch({ga: '1/1/8', status_ga: '1/1/108'}, connection);
|
|
110
|
+
console.log("The current light status is %j", light.status.current_value);
|
|
111
|
+
light.control.on('change', function(oldvalue, newvalue) {
|
|
112
|
+
console.log("**** LIGHT control changed from: %j to: %j", oldvalue, newvalue);
|
|
113
|
+
});
|
|
114
|
+
light.status.on('change', function(oldvalue, newvalue) {
|
|
115
|
+
console.log("**** LIGHT status changed from: %j to: %j", oldvalue, newvalue);
|
|
116
|
+
});
|
|
117
|
+
light.switchOn(); // or switchOff();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This effectively creates a pair of datapoints typically associated with a binary
|
|
121
|
+
switch, one for controlling it and another for getting a status feedback (eg via
|
|
122
|
+
manual operation)
|
|
123
|
+
|
|
124
|
+
### Write raw buffers
|
|
125
|
+
|
|
126
|
+
If you encode the values by yourself, you can write raw buffers with `writeRaw(groupaddress: string, buffer: Buffer, bitlength?: Number, callback?: () => void)`.
|
|
127
|
+
|
|
128
|
+
The third (optional) parameter `bitlength` is necessary for datapoint types
|
|
129
|
+
where the bitlength does not equal the buffers bytelength * 8.
|
|
130
|
+
This is the case for dpt 1 (bitlength 1), 2 (bitlength 2) and 3 (bitlength 4).
|
|
131
|
+
For other dpts the paramter can be omitted.
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
// Write raw buffer to a groupaddress with dpt 1 (e.g light on = value true = Buffer<01>) with a bitlength of 1
|
|
135
|
+
connection.writeRaw('1/0/0', Buffer.from('01', 'hex'), 1)
|
|
136
|
+
// Write raw buffer to a groupaddress with dpt 9 (e.g temperature 18.4 °C = Buffer<0730>) without bitlength
|
|
137
|
+
connection.writeRaw('1/0/0', Buffer.from('0730', 'hex'))
|
|
138
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
## Datapoint Types
|
|
2
|
+
|
|
3
|
+
|DPT | Description | Value type | Example | Notes |
|
|
4
|
+
|--- |--- |--- |--- |--- |
|
|
5
|
+
|DPT1 | 1-bit control | Boolean/Numeric | true/"true"/1 false/"false"/0 | |
|
|
6
|
+
|DPT2 | 1-bit control w/prio | Object | {priority: 0, data: 1} | |
|
|
7
|
+
|DPT3 | 4-bit dimming/blinds | Object | {decr_incr: 1, data: 0} | data: 3-bit (0..7)|
|
|
8
|
+
|DPT4 | 8-bit character | String | "a" | 1st char must be ASCII |
|
|
9
|
+
|DPT5 | 8-bit unsigned int | Numeric | 127 | 0..255 |
|
|
10
|
+
|DPT6 | 8-bit signed int | Numeric | -12 | -128..127 |
|
|
11
|
+
|DPT7 | 16-bit unsigned int | Numeric | | 0..65535 |
|
|
12
|
+
|DPT8 | 16-bit signed integer | Numeric | | -32768..32767 |
|
|
13
|
+
|DPT9 | 16-bit floating point | Numeric | | |
|
|
14
|
+
|DPT10 | 24-bit time + day of week | Date | new Date() | only the time part is used, see note |
|
|
15
|
+
|DPT11 | 24-bit date | Date | new Date() | only the date part is used, see note |
|
|
16
|
+
|DPT12 | 32-bit unsigned int | Numeric | | |
|
|
17
|
+
|DPT13 | 32-bit signed int | Numeric | | |
|
|
18
|
+
|DPT14 | 32-bit floating point | Numeric | | incomplete: subtypes |
|
|
19
|
+
|DPT15 | 32-bit access control | | | incomplete|
|
|
20
|
+
|DPT16 | ASCII string | String | | |
|
|
21
|
+
|DPT17 | Scene number | | | incomplete|
|
|
22
|
+
|DPT18 | Scene control | | | incomplete|
|
|
23
|
+
|DPT19 | 8-byte Date and Time | Date | new Date() | |
|
|
24
|
+
|DPT20-255 | feel free to contribute! | | | |
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
When you add new DPT's, please ensure that you add the corresponding unit test
|
|
28
|
+
under the `test/dptlib` subdirectory. The unit tests come with a small helper
|
|
29
|
+
library that provides the boilerplate code to marshal and unlarshal your test cases.
|
|
30
|
+
|
|
31
|
+
Take for example the unit test for DPT5, which carries a single-byte payload.
|
|
32
|
+
Some of its subtypes (eg. 5.001 for percentages and 5.003 for angle degrees)
|
|
33
|
+
need to be scaled up or down, whereas other subtypes *must not* be scaled at all:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
// DPT5 without subtype: no scaling
|
|
37
|
+
commontest.do('DPT5', [
|
|
38
|
+
{ apdu_data: [0x00], jsval: 0},
|
|
39
|
+
{ apdu_data: [0x40], jsval: 64},
|
|
40
|
+
{ apdu_data: [0x41], jsval: 65},
|
|
41
|
+
{ apdu_data: [0x80], jsval: 128},
|
|
42
|
+
{ apdu_data: [0xff], jsval: 255}
|
|
43
|
+
]);
|
|
44
|
+
// 5.001 percentage (0=0..ff=100%)
|
|
45
|
+
commontest.do('DPT5.001', [
|
|
46
|
+
{ apdu_data: [0x00], jsval: 0 },
|
|
47
|
+
{ apdu_data: [0x80], jsval: 50},
|
|
48
|
+
{ apdu_data: [0xff], jsval: 100}
|
|
49
|
+
]);
|
|
50
|
+
// 5.003 angle (degrees 0=0, ff=360)
|
|
51
|
+
commontest.do('DPT5.003', [
|
|
52
|
+
{ apdu_data: [0x00], jsval: 0 },
|
|
53
|
+
{ apdu_data: [0x80], jsval: 181 },
|
|
54
|
+
{ apdu_data: [0xff], jsval: 360 }
|
|
55
|
+
]);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Date and time DPTs (DPT10, DPT11)
|
|
59
|
+
Please have in mind that Javascript and KNX have very different base type for time and date.
|
|
60
|
+
|
|
61
|
+
- DPT10 is time (hh:mm:ss) plus "day of week". This concept is unavailable in JS, so you'll be getting/setting a regular *Date* Js object, but *please remember* you'll need to _ignore_ the date, month and year. The *exact same datagram* that converts to "Mon, Jul 1st 12:34:56", will evaluate to a wildly different JS Date of "Mon, Jul 8th 12:34:56" one week later. Be warned!
|
|
62
|
+
- DPT11 is date (dd/mm/yyyy): the same applies for DPT11, you'll need to *ignore the time part*.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
## Events
|
|
2
|
+
|
|
3
|
+
There's a ton of information being emitted by the library, so you can get full disclosure as to what's going on under the hood.
|
|
4
|
+
|
|
5
|
+
### Connection events
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
// Generic groupwrite event: device with 'src' physical address wrote to 'dest' group address
|
|
9
|
+
connection.on('GroupValue_Write', function (src, dest, value) { ... });
|
|
10
|
+
// Generic groupread event: device with physical address 'src', is asking on the KNX
|
|
11
|
+
// bus the current value of group address 'dest'
|
|
12
|
+
connection.on('GroupValue_Read', function (src, dest) { ... });
|
|
13
|
+
// response event: device with physical address 'src', is responding to a
|
|
14
|
+
// read request that the current value of group address 'dest' is 'value'
|
|
15
|
+
connection.on('GroupValue_Response', function (src, dest, value) { ... });
|
|
16
|
+
|
|
17
|
+
// Specific group address event: device with 'src' physical address
|
|
18
|
+
// .. wrote to group address
|
|
19
|
+
connection.on('GroupValue_Write_1/2/3', function (src, value) { ... });
|
|
20
|
+
// .. or responded about current value
|
|
21
|
+
connection.on('GroupValue_Response_1/2/3', function (src, value) { ... });
|
|
22
|
+
|
|
23
|
+
// there's also the generic catch-all event which passes the event type
|
|
24
|
+
// as its 1st argument, along with all the other info
|
|
25
|
+
connection.on('event', function (evt, src, dest, value) { ... });)
|
|
26
|
+
// the generic catch-all event can also be used with group addresses
|
|
27
|
+
connection.on('event_1/2/3', function (evt, src, dest, value) { ... });)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Here's the full list of events emitted by the connection:
|
|
31
|
+
```
|
|
32
|
+
["GroupValue_Read", "GroupValue_Response", "GroupValue_Write",
|
|
33
|
+
"PhysicalAddress_Write", "PhysicalAddress_Read", "PhysicalAddress_Response",
|
|
34
|
+
"ADC_Read", "ADC_Response", "Memory_Read", "Memory_Response", "Memory_Write",
|
|
35
|
+
"UserMemory", "DeviceDescriptor_Read", "DeviceDescriptor_Response",
|
|
36
|
+
"Restart", "OTHER"]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Other connection events:
|
|
40
|
+
```js
|
|
41
|
+
// an outgoing tunnelling request didn't get any acknowledgement
|
|
42
|
+
connection.on('unacknowledged', function (datagram) { ... });)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Datapoint events
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
// a datapoint has had its value changed
|
|
49
|
+
datapoint.on('change', function(oldvalue, newvalue){...});
|
|
50
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
**Please note**: if you use `eibd` or `knxd` as your IP router, and you have it running on the **same** box as your NodeJS app, then eibd/knxd will DROP multicast packets coming from the same source IP address. This is meant to prevent endless loops: if by error knxd is configured also as a client (that will join the same multicast group), then a multicast storm will flood your LAN. I'm sure you've experienced something similar if you take a microphone connected to an amplifier and put it near the speakers (ECHO!)
|
|
2
|
+
|
|
3
|
+
`Layer 0 [11:server/Server 7.133] Dropped(017): 06 10 05 30 00 11 29 00 BC D0 11 64 29 0F 01 00 80`
|
|
4
|
+
|
|
5
|
+
The trick here (although not entirely within the specs) is to use the loopback interface, so if you define the KNX connection address/port to `127.0.0.1:3671` then this will bypass the source address check (and happily route your packets down your USB or TPUART interface).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
## And why should I bother?
|
|
2
|
+
|
|
3
|
+
The main cause for writing my own KNX protocol stack is that I couldn't find a *robust* access layer that properly handles state management.
|
|
4
|
+
Connections tend to fail all the time; consider flakey Wi-Fi, RRR's (Recalcitrant Rebooting Routers), bad karma, it happens all the time. A KNX access layer should be *resilient* and be able to recover if needed.
|
|
5
|
+
|
|
6
|
+
Also, although seemingly innocent, the consecutive calls to *read()* and then *write()* on the same group address will either *confuse* your KNX IP router, or *return incoherent results*.
|
|
7
|
+
KNXnet/IP uses **UDP** sockets, which is not ideal from a programmer's perspective. Packets can come and go in any order; very few libraries offer the robustness to reconcile state and ensure a **steady and reliable connection**.
|
|
8
|
+
|
|
9
|
+
This library is, to the best of my knowledge, the only one that can handle the *serialisation* of tunneling requests in a way that your program will have a *robust and reliable* KNX connection. Try toggling your Wi-Fi or disconnect your Ethernet cable while you're connected; the library will detect this and reconnect when network access is restored :)
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
27 Oct 15:44:24 - [info] Started flows
|
|
13
|
+
27 Oct 15:44:24 - [info] [knx-controller:9ab91ab8.547938] KNX: successfully connected to 224.0.23.12:3671
|
|
14
|
+
27 Oct 15:44:24 - [info] [knx-controller:9ab91ab8.547938] GroupValue_Read {"srcphy":"15.15.15","dstgad":"0/0/15"}
|
|
15
|
+
...
|
|
16
|
+
27 Oct 15:44:54 - [info] [knx-controller:9ab91ab8.547938] KNX Connection Error: timed out waiting for CONNECTIONSTATE_RESPONSE
|
|
17
|
+
27 Oct 15:45:36 - [info] [knx-controller:9ab91ab8.547938] KNX: successfully connected to 224.0.23.12:3671
|
|
18
|
+
27 Oct 15:45:36 - [info] [knx-in:input] GroupValue_Read {"srcphy":"15.15.15","dstgad":"0/0/15"}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## A note on resilience
|
|
23
|
+
|
|
24
|
+
There are basically *two* ways to talk to KNX via UDP/IP:
|
|
25
|
+
|
|
26
|
+
- **Tunneling** is effectively UDP **unicast** with connection state (essentially mimicking TCP), thus we get to make a CONNECT_REQUEST to establish a session id with the router or interface. This enables us to periodically check on the connection's health (with CONNECTIONSTATE_REQUESTs) and handle retries, acknowledgements etc. The disadvantage here is that KNXnet/IP lacks a service discovery mechanism, thus you need to specify the router/interface endpoint IP address and port.
|
|
27
|
+
|
|
28
|
+
- **Routing** is a plain UDP multicast transport *without any connection or reliability semantics whatsoever* - which makes it much harder to detect dropped packets eg due to congested networks. The multicast approach works well on wired high-speed (eg Ethernet) segments that are dedicated to KNX traffic only. As we all know, KNX/TP1 has a bandwidth that is several orders of magnitude slower than a LAN, but this isn't necessarily the case when you connect over a VPN! *In reality, your network is definately going to drop some packets*. The advantage of multicast though is that it needs no configuration, as long as the IP router is configured to the default KNX multicast address (224.0.23.12)
|
|
29
|
+
|
|
30
|
+
- Finally, this library allows a **hybrid** approach, that's taking the best of the two methods above: You can use **multicast** transport with a **tunnelling** connection to ensure reliable communication. *Unfortunately this deviates from the official KNXnet/IP spec*, and is therefore not compatible with some IP routers. You can enable this "hybrid mode" by enabling the `forceTunneling` option when constructing a new Connection object as follows:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
var connection = new knx.Connection( {
|
|
34
|
+
// use tunneling with multicast - this is NOT supported by all routers!
|
|
35
|
+
forceTunneling: true,
|
|
36
|
+
...
|
|
37
|
+
});
|
|
38
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
## KNXnet/IP for Node.JS
|
|
2
|
+
|
|
3
|
+
**New:** [Join the Gitter.im chatroom!](https://gitter.im/knx-js/Lobby)
|
|
4
|
+
|
|
5
|
+
A feature-complete [KNXnet/IP protocol stack](https://www.knx.org/en-us/knx/technology/developing/devices/ip-devices/index.php) in pure Javascript, capable of talking multicast (routing) and unicast (tunneling). Adding KNX to your Node.JS applications is now finally easy as pie.
|
|
6
|
+
|
|
7
|
+
* Wide DPT (datapoint type) support (DPT1 - DPT20 supported)
|
|
8
|
+
* Extensible Device support (binary lights, dimmers, ...)
|
|
9
|
+
* You won't need to install a specialised eibd daemon with its arcane dependencies and most importantly,
|
|
10
|
+
* If you got an IP router and a network that supports IP multicast, you can start talking to KNX within seconds!
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Make sure your machine has Node.JS (version 4.x or greater) and do:
|
|
15
|
+
|
|
16
|
+
`npm install knx`
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
At last, here's a **reliable** KNX connection that simply works without any configs. To get a basic KNX monitor, you just need to run this in Node:
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
var knx = require('knx');
|
|
24
|
+
var connection = knx.Connection({
|
|
25
|
+
handlers: {
|
|
26
|
+
connected: function() {
|
|
27
|
+
console.log('Connected!');
|
|
28
|
+
},
|
|
29
|
+
event: function (evt, src, dest, value) {
|
|
30
|
+
console.log("%s **** KNX EVENT: %j, src: %j, dest: %j, value: %j",
|
|
31
|
+
new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''),
|
|
32
|
+
evt, src, dest, value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Ahhh, KNX telegrams, what a joy:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
> 2016-09-24 05:34:07 **** KNX EVENT: "GroupValue_Write", src: "1.1.100", dest: "5/0/8", value: 1
|
|
42
|
+
2016-09-24 05:34:09 **** KNX EVENT: "GroupValue_Write", src: "1.1.100", dest: "5/1/15", value: 0
|
|
43
|
+
2016-09-24 05:34:09 **** KNX EVENT: "GroupValue_Write", src: "1.1.100", dest: "5/0/8", value: 0
|
|
44
|
+
2016-09-24 05:34:17 **** KNX EVENT: "GroupValue_Write", src: "1.1.100", dest: "5/1/15", value: 0
|
|
45
|
+
2016-09-24 05:34:17 **** KNX EVENT: "GroupValue_Write", src: "1.1.100", dest: "5/0/8", value: 1
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Development documentation
|
|
49
|
+
|
|
50
|
+
- [Basic API usage](../master/README-API.md)
|
|
51
|
+
- [List of supported datapoints](../master/README-datapoints.md)
|
|
52
|
+
- [List of supported events](../master/README-events.md)
|
|
53
|
+
- [eibd/knxd compatibility](../master/README-knxd.md)
|
|
54
|
+
- [On resilience](../master/README-resilience.md)
|