knx.ts 1.0.2 → 1.0.4
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/LICENSE +51 -21
- package/README.md +274 -61
- package/dist/@types/interfaces/connection.d.ts +80 -13
- package/dist/@types/interfaces/servers.d.ts +18 -0
- package/dist/@types/interfaces/servers.js +2 -0
- package/dist/connection/KNXService.d.ts +13 -30
- package/dist/connection/KNXService.js +4 -164
- package/dist/connection/KNXTunneling.d.ts +4 -4
- package/dist/connection/KNXTunneling.js +35 -62
- package/dist/connection/KNXUSBConnection.d.ts +20 -0
- package/dist/connection/KNXUSBConnection.js +358 -0
- package/dist/connection/KNXnetIPServer.d.ts +29 -12
- package/dist/connection/KNXnetIPServer.js +261 -83
- package/dist/connection/Router.d.ts +52 -32
- package/dist/connection/Router.js +225 -153
- package/dist/connection/TPUART.d.ts +8 -3
- package/dist/connection/TPUART.js +41 -37
- package/dist/connection/TunnelConnection.d.ts +3 -1
- package/dist/connection/TunnelConnection.js +6 -4
- package/dist/core/CEMI.d.ts +7 -2
- package/dist/core/CEMI.js +5 -8
- package/dist/core/EMI.d.ts +312 -200
- package/dist/core/EMI.js +511 -1007
- package/dist/core/KNXnetIPStructures.d.ts +10 -1
- package/dist/core/KNXnetIPStructures.js +15 -10
- package/dist/core/MessageCodeField.d.ts +1 -1
- package/dist/core/cache/GroupAddressCache.d.ts +57 -0
- package/dist/core/cache/GroupAddressCache.js +227 -0
- package/dist/core/data/KNXDataDecode.d.ts +2 -2
- package/dist/core/data/KNXDataDecode.js +198 -183
- package/dist/core/enum/EnumControlField.d.ts +0 -5
- package/dist/core/enum/EnumControlField.js +1 -7
- package/dist/core/enum/EnumControlFieldExtended.d.ts +1 -1
- package/dist/core/enum/EnumShortACKFrame.d.ts +1 -1
- package/dist/core/enum/ErrorCodeSet.js +59 -0
- package/dist/core/enum/KNXnetIPEnum.d.ts +2 -2
- package/dist/core/enum/KNXnetIPEnum.js +19 -1
- package/dist/core/layers/data/NPDU.d.ts +2 -1
- package/dist/core/layers/data/NPDU.js +6 -3
- package/dist/index.d.ts +19 -2
- package/dist/index.js +36 -1
- package/dist/server/KNXMQTTGateway.d.ts +13 -0
- package/dist/server/KNXMQTTGateway.js +164 -0
- package/dist/server/KNXWebSocketServer.d.ts +12 -0
- package/dist/server/KNXWebSocketServer.js +118 -0
- package/dist/utils/CEMIAdapter.d.ts +4 -3
- package/dist/utils/CEMIAdapter.js +26 -30
- package/dist/utils/Logger.d.ts +4 -4
- package/dist/utils/Logger.js +3 -7
- package/package.json +27 -7
|
@@ -4,7 +4,6 @@ exports.TPUARTConnection = void 0;
|
|
|
4
4
|
const serialport_1 = require("serialport");
|
|
5
5
|
const KNXService_1 = require("./KNXService");
|
|
6
6
|
const CEMIAdapter_1 = require("../utils/CEMIAdapter");
|
|
7
|
-
const EMI_1 = require("../core/EMI");
|
|
8
7
|
const KNXHelper_1 = require("../utils/KNXHelper");
|
|
9
8
|
const CEMI_1 = require("../core/CEMI");
|
|
10
9
|
const UART_SERVICES = {
|
|
@@ -33,6 +32,7 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
33
32
|
serialPort;
|
|
34
33
|
receiver;
|
|
35
34
|
connectionState = TPUARTState.DISCONNECTED;
|
|
35
|
+
isOpening = false;
|
|
36
36
|
initPromise = null;
|
|
37
37
|
msgQueue = [];
|
|
38
38
|
isProcessing = false;
|
|
@@ -76,8 +76,7 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
76
76
|
const isExtended = (frame[0] & 0x80) === 0;
|
|
77
77
|
const controlByte = isExtended ? frame[1] : frame[5];
|
|
78
78
|
const isGroup = (controlByte & 0x80) !== 0;
|
|
79
|
-
if ((isGroup && options.ackGroup) ||
|
|
80
|
-
(!isGroup && options.ackIndividual)) {
|
|
79
|
+
if ((isGroup && options.ackGroup) || (!isGroup && options.ackIndividual)) {
|
|
81
80
|
ackByte = 0x11; // Send ACK
|
|
82
81
|
}
|
|
83
82
|
}
|
|
@@ -98,13 +97,17 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
98
97
|
this.emit("raw_indication", cemi.toBuffer());
|
|
99
98
|
}
|
|
100
99
|
}
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
/* empty */
|
|
101
104
|
}
|
|
102
|
-
catch (e) { }
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
handleFatalError(err) {
|
|
106
108
|
this.stopTimers();
|
|
107
109
|
this.connectionState = TPUARTState.ERROR;
|
|
110
|
+
this.isOpening = false;
|
|
108
111
|
// Reject all pending messages
|
|
109
112
|
while (this.msgQueue.length > 0) {
|
|
110
113
|
this.msgQueue.shift()?.reject(err);
|
|
@@ -122,6 +125,7 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
122
125
|
this.keepaliveTimer = null;
|
|
123
126
|
this.confirmationTimer = null;
|
|
124
127
|
this.initTimer = null;
|
|
128
|
+
this.removeAllListeners();
|
|
125
129
|
}
|
|
126
130
|
resetKeepalive() {
|
|
127
131
|
if (this.keepaliveTimer)
|
|
@@ -134,14 +138,26 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
134
138
|
}, 10000);
|
|
135
139
|
}
|
|
136
140
|
async connect() {
|
|
137
|
-
if (this.connectionState !== TPUARTState.DISCONNECTED &&
|
|
138
|
-
|
|
141
|
+
if (this.connectionState !== TPUARTState.DISCONNECTED && this.connectionState !== TPUARTState.ERROR)
|
|
142
|
+
return;
|
|
143
|
+
if (this.isOpening)
|
|
139
144
|
return;
|
|
145
|
+
this.isOpening = true;
|
|
140
146
|
return new Promise((resolve, reject) => {
|
|
141
|
-
this.initPromise = {
|
|
147
|
+
this.initPromise = {
|
|
148
|
+
resolve: () => {
|
|
149
|
+
this.isOpening = false;
|
|
150
|
+
resolve();
|
|
151
|
+
},
|
|
152
|
+
reject: (e) => {
|
|
153
|
+
this.isOpening = false;
|
|
154
|
+
reject(e);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
142
157
|
this.serialPort.open(async (err) => {
|
|
143
158
|
if (err) {
|
|
144
159
|
this.initPromise = null;
|
|
160
|
+
this.isOpening = false;
|
|
145
161
|
reject(err);
|
|
146
162
|
return;
|
|
147
163
|
}
|
|
@@ -174,6 +190,7 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
174
190
|
async disconnect() {
|
|
175
191
|
this.stopTimers();
|
|
176
192
|
this.connectionState = TPUARTState.DISCONNECTED;
|
|
193
|
+
this.isOpening = false;
|
|
177
194
|
// Clear queue
|
|
178
195
|
while (this.msgQueue.length > 0) {
|
|
179
196
|
this.msgQueue.shift()?.reject(new Error("Disconnected by user"));
|
|
@@ -191,6 +208,10 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
191
208
|
}
|
|
192
209
|
});
|
|
193
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Enable or disable busmonitor mode.
|
|
213
|
+
* @param enabled
|
|
214
|
+
*/
|
|
194
215
|
async setBusmonitor(enabled) {
|
|
195
216
|
if (this.connectionState < TPUARTState.ONLINE)
|
|
196
217
|
throw new Error("TPUART offline");
|
|
@@ -207,11 +228,10 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
207
228
|
async send(data) {
|
|
208
229
|
if (this.connectionState < TPUARTState.ONLINE)
|
|
209
230
|
throw new Error("TPUART offline");
|
|
210
|
-
|
|
211
|
-
? tryEmi(data) || data
|
|
212
|
-
: CEMIAdapter_1.CEMIAdapter.cemiToEmi(data)?.toBuffer().subarray(1);
|
|
231
|
+
const frame = Buffer.isBuffer(data) ? data : CEMIAdapter_1.CEMIAdapter.cemiToEmi(data)?.toBuffer();
|
|
213
232
|
if (!frame)
|
|
214
233
|
throw new Error("Invalid data");
|
|
234
|
+
this.emit("send", data);
|
|
215
235
|
return this.enqueueFrame(frame);
|
|
216
236
|
}
|
|
217
237
|
async enqueueFrame(frame) {
|
|
@@ -268,9 +288,7 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
268
288
|
toUartServices(telegram) {
|
|
269
289
|
const result = Buffer.alloc(telegram.length * 2);
|
|
270
290
|
for (let i = 0; i < telegram.length; i++) {
|
|
271
|
-
const ctrl = i === telegram.length - 1
|
|
272
|
-
? UART_SERVICES.LDATA_END | (i & 0x3f)
|
|
273
|
-
: UART_SERVICES.LDATA_START | (i & 0x3f);
|
|
291
|
+
const ctrl = i === telegram.length - 1 ? UART_SERVICES.LDATA_END | (i & 0x3f) : UART_SERVICES.LDATA_START | (i & 0x3f);
|
|
274
292
|
result[i * 2] = ctrl;
|
|
275
293
|
result[i * 2 + 1] = telegram[i];
|
|
276
294
|
}
|
|
@@ -290,11 +308,7 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
290
308
|
if ((byte & 0x17) === 0x13) {
|
|
291
309
|
const hasError = (byte & 0x07) !== 0;
|
|
292
310
|
if (hasError) {
|
|
293
|
-
const error = byte & 0x04
|
|
294
|
-
? "Checksum Error"
|
|
295
|
-
: byte & 0x02
|
|
296
|
-
? "Timing Error"
|
|
297
|
-
: "Bit Error";
|
|
311
|
+
const error = byte & 0x04 ? "Checksum Error" : byte & 0x02 ? "Timing Error" : "Bit Error";
|
|
298
312
|
this.emit("warning", `TPUART Frame Error: ${error}`);
|
|
299
313
|
}
|
|
300
314
|
return;
|
|
@@ -307,10 +321,7 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
307
321
|
const options = this.options;
|
|
308
322
|
if (options.individualAddress) {
|
|
309
323
|
this.connectionState = TPUARTState.SET_ADDR_WAIT;
|
|
310
|
-
this.writeRaw(Buffer.concat([
|
|
311
|
-
Buffer.from([0x28]),
|
|
312
|
-
KNXHelper_1.KNXHelper.GetAddress(options.individualAddress, "."),
|
|
313
|
-
])).catch((e) => this.emit("error", e));
|
|
324
|
+
this.writeRaw(Buffer.concat([Buffer.from([0x28]), KNXHelper_1.KNXHelper.GetAddress(options.individualAddress, ".")])).catch((e) => this.emit("error", e));
|
|
314
325
|
// knxd immediately transitions to get state after setting address
|
|
315
326
|
this.requestState();
|
|
316
327
|
}
|
|
@@ -344,16 +355,16 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
344
355
|
}
|
|
345
356
|
return;
|
|
346
357
|
}
|
|
347
|
-
if (byte === UART_SERVICES.LDATA_CON_POS ||
|
|
348
|
-
byte === UART_SERVICES.LDATA_CON_NEG) {
|
|
358
|
+
if (byte === UART_SERVICES.LDATA_CON_POS || byte === UART_SERVICES.LDATA_CON_NEG) {
|
|
349
359
|
if (this.confirmationTimer)
|
|
350
360
|
clearTimeout(this.confirmationTimer);
|
|
351
361
|
const item = this.msgQueue.shift();
|
|
352
362
|
this.isProcessing = false;
|
|
353
|
-
if (item) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
363
|
+
if (item && byte === UART_SERVICES.LDATA_CON_POS) {
|
|
364
|
+
item.resolve();
|
|
365
|
+
}
|
|
366
|
+
else if (item) {
|
|
367
|
+
item.reject(new Error("NAK"));
|
|
357
368
|
}
|
|
358
369
|
this.processQueue();
|
|
359
370
|
return;
|
|
@@ -416,14 +427,6 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
416
427
|
}
|
|
417
428
|
}
|
|
418
429
|
exports.TPUARTConnection = TPUARTConnection;
|
|
419
|
-
function tryEmi(data) {
|
|
420
|
-
try {
|
|
421
|
-
return EMI_1.EMI.fromBuffer(data).toBuffer().subarray(1);
|
|
422
|
-
}
|
|
423
|
-
catch {
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
430
|
class Receiver {
|
|
428
431
|
connection;
|
|
429
432
|
buffer = Buffer.alloc(0);
|
|
@@ -433,8 +436,9 @@ class Receiver {
|
|
|
433
436
|
this.connection = connection;
|
|
434
437
|
}
|
|
435
438
|
handleData(data) {
|
|
436
|
-
for (const byte of data)
|
|
439
|
+
for (const byte of data) {
|
|
437
440
|
this.processByte(byte);
|
|
441
|
+
}
|
|
438
442
|
}
|
|
439
443
|
processByte(byte) {
|
|
440
444
|
// BUG FIX: Only handle control bytes if we are NOT in the middle of a frame
|
|
@@ -6,6 +6,8 @@ import { Logger } from "pino";
|
|
|
6
6
|
* Encapsulates a single KNXnet/IP Tunnelling or Management connection state.
|
|
7
7
|
* Handles sequence numbers, heartbeats, reliable delivery (stop-and-wait),
|
|
8
8
|
* and retransmissions according to KNX Spec Vol 3/8/4.
|
|
9
|
+
*
|
|
10
|
+
* ***Don't use this for connect a KNX device. Use KNXTunneling instead.***
|
|
9
11
|
*/
|
|
10
12
|
export declare class TunnelConnection {
|
|
11
13
|
readonly channelId: number;
|
|
@@ -47,7 +49,7 @@ export declare class TunnelConnection {
|
|
|
47
49
|
* Validates an incoming request from the client according to sequence number rules.
|
|
48
50
|
*/
|
|
49
51
|
validateRequest(seq: number): {
|
|
50
|
-
action:
|
|
52
|
+
action: "process" | "discard" | "retransmit_ack";
|
|
51
53
|
status: number;
|
|
52
54
|
};
|
|
53
55
|
/**
|
|
@@ -7,6 +7,8 @@ const KNXnetIPEnum_1 = require("../core/enum/KNXnetIPEnum");
|
|
|
7
7
|
* Encapsulates a single KNXnet/IP Tunnelling or Management connection state.
|
|
8
8
|
* Handles sequence numbers, heartbeats, reliable delivery (stop-and-wait),
|
|
9
9
|
* and retransmissions according to KNX Spec Vol 3/8/4.
|
|
10
|
+
*
|
|
11
|
+
* ***Don't use this for connect a KNX device. Use KNXTunneling instead.***
|
|
10
12
|
*/
|
|
11
13
|
class TunnelConnection {
|
|
12
14
|
channelId;
|
|
@@ -106,7 +108,7 @@ class TunnelConnection {
|
|
|
106
108
|
this.logger.error(`Second ACK timeout for seq ${seq}. Terminating connection.`);
|
|
107
109
|
this.onDisconnect(this.channelId, true);
|
|
108
110
|
}
|
|
109
|
-
}, this.retransmitTimeoutMs)
|
|
111
|
+
}, this.retransmitTimeoutMs),
|
|
110
112
|
};
|
|
111
113
|
}
|
|
112
114
|
/**
|
|
@@ -138,16 +140,16 @@ class TunnelConnection {
|
|
|
138
140
|
if (seq === this.rno) {
|
|
139
141
|
// Expected sequence number
|
|
140
142
|
this.rno = (this.rno + 1) % 256;
|
|
141
|
-
return { action:
|
|
143
|
+
return { action: "process", status: KNXnetIPEnum_1.KNXnetIPErrorCodes.E_NO_ERROR };
|
|
142
144
|
}
|
|
143
145
|
else if (seq === (this.rno - 1 + 256) % 256) {
|
|
144
146
|
// Previous sequence number (retransmit ACK)
|
|
145
|
-
return { action:
|
|
147
|
+
return { action: "retransmit_ack", status: KNXnetIPEnum_1.KNXnetIPErrorCodes.E_NO_ERROR };
|
|
146
148
|
}
|
|
147
149
|
else {
|
|
148
150
|
// Out of sequence - discard without reply (Spec 2.6.1)
|
|
149
151
|
this.logger.warn(`Out of sequence request: got ${seq}, expected ${this.rno}`);
|
|
150
|
-
return { action:
|
|
152
|
+
return { action: "discard", status: 0 };
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
/**
|
package/dist/core/CEMI.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ServiceMessage } from "../@types/interfaces/ServiceMessage";
|
|
2
1
|
import { ControlField } from "./ControlField";
|
|
3
2
|
import { ExtendedControlField } from "./ControlFieldExtended";
|
|
4
3
|
import { AddInfoBase } from "./KNXAddInfoTypes";
|
|
@@ -48,7 +47,7 @@ export declare class AdditionalInformationField {
|
|
|
48
47
|
}
|
|
49
48
|
export declare class CEMI {
|
|
50
49
|
constructor();
|
|
51
|
-
static fromBuffer(buffer: Buffer):
|
|
50
|
+
static fromBuffer(buffer: Buffer): CEMIInstance;
|
|
52
51
|
static DataLinkLayerCEMI: {
|
|
53
52
|
readonly "L_Data.req": {
|
|
54
53
|
new (additionalInfo: AddInfoBase[] | null | undefined, controlField1: ControlField, controlField2: ExtendedControlField, sourceAddress: string, destinationAddress: string, TPDU: TPDU): {
|
|
@@ -1128,3 +1127,9 @@ export declare class CEMI {
|
|
|
1128
1127
|
};
|
|
1129
1128
|
};
|
|
1130
1129
|
}
|
|
1130
|
+
type KeysOfCEMI = "DataLinkLayerCEMI" | "TransportLayerCEMI" | "ManagementCEMI";
|
|
1131
|
+
type CEMIClasses = {
|
|
1132
|
+
[K in KeysOfCEMI]: (typeof CEMI)[K][keyof (typeof CEMI)[K]];
|
|
1133
|
+
}[KeysOfCEMI];
|
|
1134
|
+
export type CEMIInstance = InstanceType<CEMIClasses>;
|
|
1135
|
+
export {};
|
package/dist/core/CEMI.js
CHANGED
|
@@ -236,7 +236,7 @@ class CEMI {
|
|
|
236
236
|
controlField2: this.controlField2.describe(),
|
|
237
237
|
sourceAddress: this.sourceAddress,
|
|
238
238
|
destinationAddress: this.destinationAddress,
|
|
239
|
-
TPDU: this.TPDU.describe()
|
|
239
|
+
TPDU: this.TPDU.describe(),
|
|
240
240
|
};
|
|
241
241
|
}
|
|
242
242
|
},
|
|
@@ -321,7 +321,7 @@ class CEMI {
|
|
|
321
321
|
controlField2: this.controlField2.describe(),
|
|
322
322
|
sourceAddress: this.sourceAddress,
|
|
323
323
|
destinationAddress: this.destinationAddress,
|
|
324
|
-
TPDU: this.TPDU.describe()
|
|
324
|
+
TPDU: this.TPDU.describe(),
|
|
325
325
|
};
|
|
326
326
|
}
|
|
327
327
|
},
|
|
@@ -358,7 +358,7 @@ class CEMI {
|
|
|
358
358
|
this.controlField1.buffer.copy(buffer, baseOffset);
|
|
359
359
|
this.controlField2.getBuffer().copy(buffer, baseOffset + 1);
|
|
360
360
|
KNXHelper_1.KNXHelper.GetAddress_(this.sourceAddress).copy(buffer, baseOffset + 2);
|
|
361
|
-
KNXHelper_1.KNXHelper.
|
|
361
|
+
KNXHelper_1.KNXHelper.GetAddress(this.destinationAddress, this.controlField2.addressType === 1 ? "/" : ".").copy(buffer, baseOffset + 4);
|
|
362
362
|
buffer[baseOffset + 6] = this.length;
|
|
363
363
|
this.TPDU.toBuffer().copy(buffer, baseOffset + 7);
|
|
364
364
|
return buffer;
|
|
@@ -406,7 +406,7 @@ class CEMI {
|
|
|
406
406
|
controlField2: this.controlField2.describe(),
|
|
407
407
|
sourceAddress: this.sourceAddress,
|
|
408
408
|
destinationAddress: this.destinationAddress,
|
|
409
|
-
TPDU: this.TPDU.describe()
|
|
409
|
+
TPDU: this.TPDU.describe(),
|
|
410
410
|
};
|
|
411
411
|
}
|
|
412
412
|
},
|
|
@@ -699,10 +699,7 @@ class CEMI {
|
|
|
699
699
|
this.additionalInfo = new AdditionalInformationField(additionalInfo);
|
|
700
700
|
}
|
|
701
701
|
messageCode = MessageCodeField_1.MESSAGE_CODE_FIELD["L_Busmon.ind"].CEMI.value;
|
|
702
|
-
additionalInfo = new AdditionalInformationField([
|
|
703
|
-
new KNXAddInfoTypes_1.BusmonitorStatusInfo(),
|
|
704
|
-
new KNXAddInfoTypes_1.TimestampRelative(),
|
|
705
|
-
]);
|
|
702
|
+
additionalInfo = new AdditionalInformationField([new KNXAddInfoTypes_1.BusmonitorStatusInfo(), new KNXAddInfoTypes_1.TimestampRelative()]);
|
|
706
703
|
data = Buffer.alloc(1);
|
|
707
704
|
toBuffer() {
|
|
708
705
|
const baseOffset = 2 + this.additionalInfo.length;
|