knx.ts 1.0.0

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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +211 -0
  3. package/dist/@types/interfaces/DPTs.d.ts +144 -0
  4. package/dist/@types/interfaces/DPTs.js +3 -0
  5. package/dist/@types/interfaces/EMI.d.ts +396 -0
  6. package/dist/@types/interfaces/EMI.js +2 -0
  7. package/dist/@types/interfaces/ServiceMessage.d.ts +11 -0
  8. package/dist/@types/interfaces/ServiceMessage.js +2 -0
  9. package/dist/@types/interfaces/SystemStatus.d.ts +18 -0
  10. package/dist/@types/interfaces/SystemStatus.js +2 -0
  11. package/dist/@types/interfaces/connection.d.ts +139 -0
  12. package/dist/@types/interfaces/connection.js +2 -0
  13. package/dist/@types/interfaces/localEndPoint.d.ts +5 -0
  14. package/dist/@types/interfaces/localEndPoint.js +2 -0
  15. package/dist/@types/types/AllDpts.d.ts +3 -0
  16. package/dist/@types/types/AllDpts.js +3 -0
  17. package/dist/@types/types/DecodedDPTType.d.ts +21 -0
  18. package/dist/@types/types/DecodedDPTType.js +2 -0
  19. package/dist/connection/KNXService.d.ts +58 -0
  20. package/dist/connection/KNXService.js +242 -0
  21. package/dist/connection/KNXTunneling.d.ts +44 -0
  22. package/dist/connection/KNXTunneling.js +509 -0
  23. package/dist/connection/KNXnetIPServer.d.ts +64 -0
  24. package/dist/connection/KNXnetIPServer.js +900 -0
  25. package/dist/connection/Router.d.ts +49 -0
  26. package/dist/connection/Router.js +269 -0
  27. package/dist/connection/TPUART.d.ts +32 -0
  28. package/dist/connection/TPUART.js +497 -0
  29. package/dist/connection/TunnelConnection.d.ts +57 -0
  30. package/dist/connection/TunnelConnection.js +167 -0
  31. package/dist/core/CEMI.d.ts +1130 -0
  32. package/dist/core/CEMI.js +1281 -0
  33. package/dist/core/ControlField.d.ts +57 -0
  34. package/dist/core/ControlField.js +120 -0
  35. package/dist/core/ControlFieldExtended.d.ts +56 -0
  36. package/dist/core/ControlFieldExtended.js +114 -0
  37. package/dist/core/EMI.d.ts +2515 -0
  38. package/dist/core/EMI.js +3898 -0
  39. package/dist/core/KNXAddInfoTypes.d.ts +225 -0
  40. package/dist/core/KNXAddInfoTypes.js +602 -0
  41. package/dist/core/KNXnetIPHeader.d.ts +10 -0
  42. package/dist/core/KNXnetIPHeader.js +38 -0
  43. package/dist/core/KNXnetIPStructures.d.ts +179 -0
  44. package/dist/core/KNXnetIPStructures.js +622 -0
  45. package/dist/core/MessageCodeField.d.ts +886 -0
  46. package/dist/core/MessageCodeField.js +399 -0
  47. package/dist/core/SystemStatus.d.ts +144 -0
  48. package/dist/core/SystemStatus.js +325 -0
  49. package/dist/core/data/KNXData.d.ts +7 -0
  50. package/dist/core/data/KNXData.js +30 -0
  51. package/dist/core/data/KNXDataDecode.d.ts +396 -0
  52. package/dist/core/data/KNXDataDecode.js +1186 -0
  53. package/dist/core/data/KNXDataEncode.d.ts +332 -0
  54. package/dist/core/data/KNXDataEncode.js +1504 -0
  55. package/dist/core/enum/APCIEnum.d.ts +587 -0
  56. package/dist/core/enum/APCIEnum.js +591 -0
  57. package/dist/core/enum/EnumControlField.d.ts +24 -0
  58. package/dist/core/enum/EnumControlField.js +36 -0
  59. package/dist/core/enum/EnumControlFieldExtended.d.ts +36 -0
  60. package/dist/core/enum/EnumControlFieldExtended.js +41 -0
  61. package/dist/core/enum/EnumShortACKFrame.d.ts +6 -0
  62. package/dist/core/enum/EnumShortACKFrame.js +10 -0
  63. package/dist/core/enum/ErrorCodeSet.d.ts +57 -0
  64. package/dist/core/enum/ErrorCodeSet.js +2 -0
  65. package/dist/core/enum/KNXnetIPEnum.d.ts +95 -0
  66. package/dist/core/enum/KNXnetIPEnum.js +90 -0
  67. package/dist/core/enum/SAP.d.ts +19 -0
  68. package/dist/core/enum/SAP.js +23 -0
  69. package/dist/core/layers/data/APDU.d.ts +38 -0
  70. package/dist/core/layers/data/APDU.js +115 -0
  71. package/dist/core/layers/data/NPDU.d.ts +73 -0
  72. package/dist/core/layers/data/NPDU.js +103 -0
  73. package/dist/core/layers/data/TPDU.d.ts +53 -0
  74. package/dist/core/layers/data/TPDU.js +73 -0
  75. package/dist/core/layers/interfaces/APCI.d.ts +61 -0
  76. package/dist/core/layers/interfaces/APCI.js +92 -0
  77. package/dist/core/layers/interfaces/TPCI.d.ts +110 -0
  78. package/dist/core/layers/interfaces/TPCI.js +196 -0
  79. package/dist/core/resources/DeviceDescriptorType.d.ts +46 -0
  80. package/dist/core/resources/DeviceDescriptorType.js +69 -0
  81. package/dist/errors/DPTNotFound.d.ts +6 -0
  82. package/dist/errors/DPTNotFound.js +15 -0
  83. package/dist/errors/InvalidKnxAddresExeption.d.ts +3 -0
  84. package/dist/errors/InvalidKnxAddresExeption.js +9 -0
  85. package/dist/index.d.ts +7 -0
  86. package/dist/index.js +18 -0
  87. package/dist/utils/CEMIAdapter.d.ts +16 -0
  88. package/dist/utils/CEMIAdapter.js +94 -0
  89. package/dist/utils/KNXHelper.d.ts +78 -0
  90. package/dist/utils/KNXHelper.js +338 -0
  91. package/dist/utils/Logger.d.ts +17 -0
  92. package/dist/utils/Logger.js +96 -0
  93. package/dist/utils/MessageCodeTranslator.d.ts +19 -0
  94. package/dist/utils/MessageCodeTranslator.js +77 -0
  95. package/dist/utils/checksumFrame.d.ts +18 -0
  96. package/dist/utils/checksumFrame.js +41 -0
  97. package/dist/utils/localIp.d.ts +7 -0
  98. package/dist/utils/localIp.js +45 -0
  99. package/package.json +49 -0
@@ -0,0 +1,49 @@
1
+ import { EventEmitter } from "events";
2
+ import { KNXService } from "./KNXService";
3
+ import { ExternalManagerOptions } from "../@types/interfaces/connection";
4
+ /**
5
+ * Router: A high-performance, robust Learning Bridge.
6
+ * Architecture strictly follows knxd/src/libserver/router.cpp patterns:
7
+ * 1. Loop Prevention via signature tracking AND Repeat Flag analysis.
8
+ * 2. IA Learning & Selective Routing.
9
+ * 3. Source Address Patching & Sanitization.
10
+ * 4. Client Address Pool Management (KNXnet/IP Tunneling).
11
+ */
12
+ export declare class Router extends EventEmitter {
13
+ private links;
14
+ private addressTable;
15
+ private recentSignatures;
16
+ private readonly MAX_SIGNATURES_SIZE;
17
+ private routerAddress;
18
+ private clientAddrsStart;
19
+ private clientAddrsCount;
20
+ private clientAddrsUsed;
21
+ private logger;
22
+ constructor(options: ExternalManagerOptions & {
23
+ routerAddress?: string;
24
+ clientAddrs?: string;
25
+ });
26
+ /**
27
+ * Allocates a free physical address for a tunneling client.
28
+ */
29
+ getClientAddress(): string | null;
30
+ /**
31
+ * Releases an allocated physical address when a client disconnects.
32
+ */
33
+ releaseClientAddress(addr: string): void;
34
+ private addrToInt;
35
+ private intToAddr;
36
+ registerLink(link: KNXService): void;
37
+ unregisterLink(link: KNXService): void;
38
+ /**
39
+ * Main entry point for any packet received from any link.
40
+ * Based on knxd's Router::recv_L_Data and Router::trigger_cb logic.
41
+ */
42
+ private processIncoming;
43
+ private learnAddress;
44
+ private route;
45
+ private getSignature;
46
+ private gcSignatures;
47
+ connect(): Promise<void>;
48
+ disconnect(): void;
49
+ }
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Router = void 0;
4
+ const events_1 = require("events");
5
+ const TPUART_1 = require("./TPUART");
6
+ const KNXTunneling_1 = require("./KNXTunneling");
7
+ const Logger_1 = require("../utils/Logger");
8
+ /**
9
+ * Router: A high-performance, robust Learning Bridge.
10
+ * Architecture strictly follows knxd/src/libserver/router.cpp patterns:
11
+ * 1. Loop Prevention via signature tracking AND Repeat Flag analysis.
12
+ * 2. IA Learning & Selective Routing.
13
+ * 3. Source Address Patching & Sanitization.
14
+ * 4. Client Address Pool Management (KNXnet/IP Tunneling).
15
+ */
16
+ class Router extends events_1.EventEmitter {
17
+ links = new Set();
18
+ addressTable = new Map();
19
+ // knxd 'ignore' list: prevents infinite loops across different physical paths
20
+ // Only ignores frames if they are marked as repeated in the KNX Control Field.
21
+ recentSignatures = new Map();
22
+ MAX_SIGNATURES_SIZE = 10000;
23
+ routerAddress = "15.15.0"; // Default, should be configurable
24
+ // Dynamic address pool for clients (like KNXnet/IP Tunneling)
25
+ clientAddrsStart = null;
26
+ clientAddrsCount = 0;
27
+ clientAddrsUsed = [];
28
+ logger;
29
+ constructor(options) {
30
+ super();
31
+ if (options.logOptions) {
32
+ (0, Logger_1.setupLogger)(options.logOptions);
33
+ }
34
+ this.logger = Logger_1.knxLogger.child({ module: "Router" });
35
+ if (options.routerAddress)
36
+ this.routerAddress = options.routerAddress;
37
+ // Parse client addrs e.g. "15.15.10:10"
38
+ if (options.clientAddrs) {
39
+ const parts = options.clientAddrs.split(":");
40
+ if (parts.length === 2) {
41
+ this.clientAddrsStart = parts[0];
42
+ this.clientAddrsCount = parseInt(parts[1], 10);
43
+ this.clientAddrsUsed = new Array(this.clientAddrsCount).fill(false);
44
+ }
45
+ }
46
+ if (options.tpuart)
47
+ this.registerLink(new TPUART_1.TPUARTConnection(options.tpuart));
48
+ if (options.tunneling)
49
+ options.tunneling.forEach((c) => this.registerLink(new KNXTunneling_1.KNXTunneling(c)));
50
+ this.logger.info(`Router initialized at ${this.routerAddress}`);
51
+ // Periodically clean the signature cache (knxd pattern)
52
+ setInterval(() => this.gcSignatures(), 1000);
53
+ }
54
+ /**
55
+ * Allocates a free physical address for a tunneling client.
56
+ */
57
+ getClientAddress() {
58
+ if (!this.clientAddrsStart || this.clientAddrsCount === 0)
59
+ return null;
60
+ const startInt = this.addrToInt(this.clientAddrsStart);
61
+ for (let i = 0; i < this.clientAddrsCount; i++) {
62
+ if (!this.clientAddrsUsed[i]) {
63
+ this.clientAddrsUsed[i] = true;
64
+ return this.intToAddr(startInt + i);
65
+ }
66
+ }
67
+ return null; // Pool exhausted
68
+ }
69
+ /**
70
+ * Releases an allocated physical address when a client disconnects.
71
+ */
72
+ releaseClientAddress(addr) {
73
+ if (!this.clientAddrsStart || this.clientAddrsCount === 0)
74
+ return;
75
+ const startInt = this.addrToInt(this.clientAddrsStart);
76
+ const addrInt = this.addrToInt(addr);
77
+ const index = addrInt - startInt;
78
+ if (index >= 0 && index < this.clientAddrsCount) {
79
+ this.clientAddrsUsed[index] = false;
80
+ }
81
+ }
82
+ addrToInt(addr) {
83
+ const [a, b, c] = addr.split(".").map(Number);
84
+ return ((a & 0x0f) << 12) | ((b & 0x0f) << 8) | (c & 0xff);
85
+ }
86
+ intToAddr(val) {
87
+ return `${(val >> 12) & 0x0f}.${(val >> 8) & 0x0f}.${val & 0xff}`;
88
+ }
89
+ registerLink(link) {
90
+ if (this.links.has(link))
91
+ return;
92
+ this.links.add(link);
93
+ this.logger.info(`Link registered: ${link.constructor.name}`);
94
+ link.on("indication", (cemi) => {
95
+ this.processIncoming(cemi, link);
96
+ });
97
+ link.on("error", (err) => {
98
+ this.logger.error({ link: link.constructor.name, err: err.message }, "Link error");
99
+ this.emit("error", { link, error: err });
100
+ });
101
+ // knxd pattern: cleanup when link goes down
102
+ link.on("disconnected", () => {
103
+ this.logger.info(`Link disconnected: ${link.constructor.name}`);
104
+ this.unregisterLink(link);
105
+ });
106
+ }
107
+ unregisterLink(link) {
108
+ if (!this.links.has(link))
109
+ return;
110
+ // Cleanup routing table
111
+ for (const [addr, l] of this.addressTable.entries()) {
112
+ if (l === link) {
113
+ this.addressTable.delete(addr);
114
+ // If it was a dynamic client address, release it
115
+ this.releaseClientAddress(addr);
116
+ }
117
+ }
118
+ this.links.delete(link);
119
+ }
120
+ /**
121
+ * Main entry point for any packet received from any link.
122
+ * Based on knxd's Router::recv_L_Data and Router::trigger_cb logic.
123
+ */
124
+ processIncoming(cemi, source) {
125
+ const cemiAny = cemi;
126
+ let src = cemiAny.sourceAddress;
127
+ // 1. Source Validation (knxd pattern):
128
+ // If we know this IA is on another link, discard to prevent loops/spoofing.
129
+ if (src && src !== "0.0.0") {
130
+ if (src === this.routerAddress) {
131
+ // We shouldn't receive a packet claiming to be us from the outside.
132
+ return;
133
+ }
134
+ const existingLink = this.addressTable.get(src);
135
+ if (existingLink && existingLink !== source) {
136
+ return; // Ignore packet from "wrong" interface
137
+ }
138
+ }
139
+ // 2. Source Patching (Standard KNX Requirement)
140
+ // If src is 0.0.0, it must be replaced by the router's/client's address
141
+ if (!src || src === "0.0.0") {
142
+ cemiAny.sourceAddress =
143
+ "individualAddress" in source.options &&
144
+ source.options.individualAddress
145
+ ? source.options.individualAddress
146
+ : this.routerAddress;
147
+ src = cemiAny.sourceAddress;
148
+ }
149
+ // 3. IA Learning
150
+ this.learnAddress(src, source);
151
+ // 4. Loop Prevention (knxd strict pattern)
152
+ const buf = cemi.toBuffer();
153
+ // In CEMI, Ctrl1 is at offset `2 + AddIL`
154
+ const addIL = buf[1];
155
+ const ctrl1 = buf[2 + addIL];
156
+ // KNX Standard: Repeat bit is bit 5 (0x20). Active LOW (0 = repeated frame).
157
+ const isRepeated = (ctrl1 & 0x20) === 0;
158
+ const signature = this.getSignature(buf, addIL);
159
+ if (isRepeated) {
160
+ if (this.recentSignatures.has(signature)) {
161
+ this.logger.debug({ signature, src }, "Loop prevented: duplicated repeated frame dropped");
162
+ return; // Drop repeated packet we've recently seen on the bus
163
+ }
164
+ }
165
+ // Always record the signature. If a loop occurs, the echoed packet will have
166
+ // the repeat flag set to 0 (since it failed to ACK or was physically echoed),
167
+ // and we will drop it next time.
168
+ if (this.recentSignatures.size >= this.MAX_SIGNATURES_SIZE) {
169
+ const firstKey = this.recentSignatures.keys().next().value;
170
+ if (firstKey !== undefined)
171
+ this.recentSignatures.delete(firstKey);
172
+ }
173
+ this.recentSignatures.set(signature, Date.now());
174
+ // 5. Route
175
+ this.route(cemi, source);
176
+ }
177
+ learnAddress(src, source) {
178
+ // knxd pattern: don't learn 0.0.0 or special 15.15.255 (0xFFFF) addresses
179
+ if (src !== "0.0.0" && src !== "15.15.255") {
180
+ if (this.addressTable.get(src) !== source) {
181
+ this.addressTable.set(src, source);
182
+ this.logger.debug(`Learned IA ${src} on link ${source.constructor.name}`);
183
+ }
184
+ }
185
+ }
186
+ route(cemi, source) {
187
+ const cemiAny = cemi;
188
+ // Hop Count Management (Protect the whole network)
189
+ if (cemiAny.controlField2 &&
190
+ typeof cemiAny.controlField2.hopCount === "number") {
191
+ const hops = cemiAny.controlField2.hopCount;
192
+ if (hops === 0) {
193
+ this.logger.debug({ src: cemiAny.sourceAddress, dst: cemiAny.destinationAddress }, "Packet dropped: hop count reached 0");
194
+ return; // Drop packet
195
+ }
196
+ if (hops < 7)
197
+ cemiAny.controlField2.hopCount = hops - 1;
198
+ }
199
+ const isGroup = cemiAny.controlField2?.addressType === 1;
200
+ const dest = cemiAny.destinationAddress;
201
+ // If packet is destined for the router itself, consume it and don't route
202
+ if (!isGroup && dest === this.routerAddress) {
203
+ this.logger.debug({ src: cemiAny.sourceAddress }, "Packet consumed by router local address");
204
+ this.emit("indication", cemi);
205
+ return;
206
+ }
207
+ // Selective Routing (IA)
208
+ if (!isGroup && dest && dest !== "0.0.0" && dest !== "15.15.255") {
209
+ const target = this.addressTable.get(dest);
210
+ if (target) {
211
+ if (target !== source) {
212
+ target.send(cemi).catch((err) => {
213
+ this.logger.debug({ target: target.constructor.name, err: err.message }, "Selective routing failed");
214
+ });
215
+ }
216
+ // Send to upper layers (KNXnet/IP server core)
217
+ this.emit("indication", cemi);
218
+ return; // Do not flood
219
+ }
220
+ // If target is unknown, knxd broadcasts it to all interfaces
221
+ }
222
+ // Flood to all links except source, respecting filters (knxd pattern)
223
+ for (const link of this.links) {
224
+ if (link === source)
225
+ continue;
226
+ // Avoid looping back to the physical source address if known via another route
227
+ if (this.addressTable.get(cemiAny.sourceAddress) === link)
228
+ continue;
229
+ // Check if the link should filter this message
230
+ if (isGroup) {
231
+ if (link.shouldFilterGroup && !link.shouldFilterGroup(dest))
232
+ continue;
233
+ }
234
+ else {
235
+ if (link.shouldFilterIA && !link.shouldFilterIA(dest))
236
+ continue;
237
+ }
238
+ link.send(cemi).catch((err) => {
239
+ this.logger.debug({ link: link.constructor.name, err: err.message }, "Flooding routing failed for link");
240
+ });
241
+ }
242
+ // Notify upper layers
243
+ this.emit("indication", cemi);
244
+ }
245
+ getSignature(buf, addIL) {
246
+ // Signature based on Dst + APCI + Data (knxd style)
247
+ // We skip Src and HopCount because they change during routing
248
+ // Payload starts after CEMI header (2) + AddIL + Ctrl1 (1) + Ctrl2 (1) + Src (2)
249
+ // Actually, taking from Dst onwards is a solid signature.
250
+ // Dst is at offset 2 + addIL + 4
251
+ const dstOffset = 2 + addIL + 4;
252
+ return buf.subarray(dstOffset).toString("hex");
253
+ }
254
+ gcSignatures() {
255
+ const now = Date.now();
256
+ for (const [sig, time] of this.recentSignatures) {
257
+ // knxd keeps them for 1 second
258
+ if (now - time > 1000)
259
+ this.recentSignatures.delete(sig);
260
+ }
261
+ }
262
+ async connect() {
263
+ await Promise.all(Array.from(this.links).map((l) => l.connect()));
264
+ }
265
+ disconnect() {
266
+ this.links.forEach((l) => l.disconnect());
267
+ }
268
+ }
269
+ exports.Router = Router;
@@ -0,0 +1,32 @@
1
+ import { KNXService } from "./KNXService";
2
+ import { TPUARTOptions } from "../@types/interfaces/connection";
3
+ import { ServiceMessage } from "../@types/interfaces/ServiceMessage";
4
+ export declare class TPUARTConnection extends KNXService {
5
+ private serialPort;
6
+ private receiver;
7
+ private connectionState;
8
+ private initPromise;
9
+ private msgQueue;
10
+ private isProcessing;
11
+ private lastSentFrame;
12
+ private keepaliveTimer;
13
+ private confirmationTimer;
14
+ private initTimer;
15
+ private initRetryCount;
16
+ private isBusmonitorMode;
17
+ constructor(options: TPUARTOptions);
18
+ private handleFatalError;
19
+ private stopTimers;
20
+ private resetKeepalive;
21
+ connect(): Promise<void>;
22
+ private sendResetRequest;
23
+ disconnect(): Promise<void>;
24
+ setBusmonitor(enabled: boolean): Promise<void>;
25
+ send(data: Buffer | ServiceMessage): Promise<void>;
26
+ private enqueueFrame;
27
+ private processQueue;
28
+ private toUartServices;
29
+ _handleControlByte(byte: number): void;
30
+ private writeRaw;
31
+ private requestState;
32
+ }