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.
Files changed (50) hide show
  1. package/LICENSE +51 -21
  2. package/README.md +274 -61
  3. package/dist/@types/interfaces/connection.d.ts +80 -13
  4. package/dist/@types/interfaces/servers.d.ts +18 -0
  5. package/dist/@types/interfaces/servers.js +2 -0
  6. package/dist/connection/KNXService.d.ts +13 -30
  7. package/dist/connection/KNXService.js +4 -164
  8. package/dist/connection/KNXTunneling.d.ts +4 -4
  9. package/dist/connection/KNXTunneling.js +35 -62
  10. package/dist/connection/KNXUSBConnection.d.ts +20 -0
  11. package/dist/connection/KNXUSBConnection.js +358 -0
  12. package/dist/connection/KNXnetIPServer.d.ts +29 -12
  13. package/dist/connection/KNXnetIPServer.js +261 -83
  14. package/dist/connection/Router.d.ts +52 -32
  15. package/dist/connection/Router.js +225 -153
  16. package/dist/connection/TPUART.d.ts +8 -3
  17. package/dist/connection/TPUART.js +41 -37
  18. package/dist/connection/TunnelConnection.d.ts +3 -1
  19. package/dist/connection/TunnelConnection.js +6 -4
  20. package/dist/core/CEMI.d.ts +7 -2
  21. package/dist/core/CEMI.js +5 -8
  22. package/dist/core/EMI.d.ts +312 -200
  23. package/dist/core/EMI.js +511 -1007
  24. package/dist/core/KNXnetIPStructures.d.ts +10 -1
  25. package/dist/core/KNXnetIPStructures.js +15 -10
  26. package/dist/core/MessageCodeField.d.ts +1 -1
  27. package/dist/core/cache/GroupAddressCache.d.ts +57 -0
  28. package/dist/core/cache/GroupAddressCache.js +227 -0
  29. package/dist/core/data/KNXDataDecode.d.ts +2 -2
  30. package/dist/core/data/KNXDataDecode.js +198 -183
  31. package/dist/core/enum/EnumControlField.d.ts +0 -5
  32. package/dist/core/enum/EnumControlField.js +1 -7
  33. package/dist/core/enum/EnumControlFieldExtended.d.ts +1 -1
  34. package/dist/core/enum/EnumShortACKFrame.d.ts +1 -1
  35. package/dist/core/enum/ErrorCodeSet.js +59 -0
  36. package/dist/core/enum/KNXnetIPEnum.d.ts +2 -2
  37. package/dist/core/enum/KNXnetIPEnum.js +19 -1
  38. package/dist/core/layers/data/NPDU.d.ts +2 -1
  39. package/dist/core/layers/data/NPDU.js +6 -3
  40. package/dist/index.d.ts +19 -2
  41. package/dist/index.js +36 -1
  42. package/dist/server/KNXMQTTGateway.d.ts +13 -0
  43. package/dist/server/KNXMQTTGateway.js +164 -0
  44. package/dist/server/KNXWebSocketServer.d.ts +12 -0
  45. package/dist/server/KNXWebSocketServer.js +118 -0
  46. package/dist/utils/CEMIAdapter.d.ts +4 -3
  47. package/dist/utils/CEMIAdapter.js +26 -30
  48. package/dist/utils/Logger.d.ts +4 -4
  49. package/dist/utils/Logger.js +3 -7
  50. 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
- this.connectionState !== TPUARTState.ERROR)
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 = { resolve, reject };
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
- let frame = Buffer.isBuffer(data)
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
- byte === UART_SERVICES.LDATA_CON_POS
355
- ? item.resolve()
356
- : item.reject(new Error("NAK"));
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: 'process' | 'discard' | 'retransmit_ack';
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: 'process', status: KNXnetIPEnum_1.KNXnetIPErrorCodes.E_NO_ERROR };
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: 'retransmit_ack', status: KNXnetIPEnum_1.KNXnetIPErrorCodes.E_NO_ERROR };
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: 'discard', status: 0 };
152
+ return { action: "discard", status: 0 };
151
153
  }
152
154
  }
153
155
  /**
@@ -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): ServiceMessage;
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.GetAddress_(this.destinationAddress).copy(buffer, baseOffset + 4);
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;