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.
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/@types/interfaces/DPTs.d.ts +144 -0
- package/dist/@types/interfaces/DPTs.js +3 -0
- package/dist/@types/interfaces/EMI.d.ts +396 -0
- package/dist/@types/interfaces/EMI.js +2 -0
- package/dist/@types/interfaces/ServiceMessage.d.ts +11 -0
- package/dist/@types/interfaces/ServiceMessage.js +2 -0
- package/dist/@types/interfaces/SystemStatus.d.ts +18 -0
- package/dist/@types/interfaces/SystemStatus.js +2 -0
- package/dist/@types/interfaces/connection.d.ts +139 -0
- package/dist/@types/interfaces/connection.js +2 -0
- package/dist/@types/interfaces/localEndPoint.d.ts +5 -0
- package/dist/@types/interfaces/localEndPoint.js +2 -0
- package/dist/@types/types/AllDpts.d.ts +3 -0
- package/dist/@types/types/AllDpts.js +3 -0
- package/dist/@types/types/DecodedDPTType.d.ts +21 -0
- package/dist/@types/types/DecodedDPTType.js +2 -0
- package/dist/connection/KNXService.d.ts +58 -0
- package/dist/connection/KNXService.js +242 -0
- package/dist/connection/KNXTunneling.d.ts +44 -0
- package/dist/connection/KNXTunneling.js +509 -0
- package/dist/connection/KNXnetIPServer.d.ts +64 -0
- package/dist/connection/KNXnetIPServer.js +900 -0
- package/dist/connection/Router.d.ts +49 -0
- package/dist/connection/Router.js +269 -0
- package/dist/connection/TPUART.d.ts +32 -0
- package/dist/connection/TPUART.js +497 -0
- package/dist/connection/TunnelConnection.d.ts +57 -0
- package/dist/connection/TunnelConnection.js +167 -0
- package/dist/core/CEMI.d.ts +1130 -0
- package/dist/core/CEMI.js +1281 -0
- package/dist/core/ControlField.d.ts +57 -0
- package/dist/core/ControlField.js +120 -0
- package/dist/core/ControlFieldExtended.d.ts +56 -0
- package/dist/core/ControlFieldExtended.js +114 -0
- package/dist/core/EMI.d.ts +2515 -0
- package/dist/core/EMI.js +3898 -0
- package/dist/core/KNXAddInfoTypes.d.ts +225 -0
- package/dist/core/KNXAddInfoTypes.js +602 -0
- package/dist/core/KNXnetIPHeader.d.ts +10 -0
- package/dist/core/KNXnetIPHeader.js +38 -0
- package/dist/core/KNXnetIPStructures.d.ts +179 -0
- package/dist/core/KNXnetIPStructures.js +622 -0
- package/dist/core/MessageCodeField.d.ts +886 -0
- package/dist/core/MessageCodeField.js +399 -0
- package/dist/core/SystemStatus.d.ts +144 -0
- package/dist/core/SystemStatus.js +325 -0
- package/dist/core/data/KNXData.d.ts +7 -0
- package/dist/core/data/KNXData.js +30 -0
- package/dist/core/data/KNXDataDecode.d.ts +396 -0
- package/dist/core/data/KNXDataDecode.js +1186 -0
- package/dist/core/data/KNXDataEncode.d.ts +332 -0
- package/dist/core/data/KNXDataEncode.js +1504 -0
- package/dist/core/enum/APCIEnum.d.ts +587 -0
- package/dist/core/enum/APCIEnum.js +591 -0
- package/dist/core/enum/EnumControlField.d.ts +24 -0
- package/dist/core/enum/EnumControlField.js +36 -0
- package/dist/core/enum/EnumControlFieldExtended.d.ts +36 -0
- package/dist/core/enum/EnumControlFieldExtended.js +41 -0
- package/dist/core/enum/EnumShortACKFrame.d.ts +6 -0
- package/dist/core/enum/EnumShortACKFrame.js +10 -0
- package/dist/core/enum/ErrorCodeSet.d.ts +57 -0
- package/dist/core/enum/ErrorCodeSet.js +2 -0
- package/dist/core/enum/KNXnetIPEnum.d.ts +95 -0
- package/dist/core/enum/KNXnetIPEnum.js +90 -0
- package/dist/core/enum/SAP.d.ts +19 -0
- package/dist/core/enum/SAP.js +23 -0
- package/dist/core/layers/data/APDU.d.ts +38 -0
- package/dist/core/layers/data/APDU.js +115 -0
- package/dist/core/layers/data/NPDU.d.ts +73 -0
- package/dist/core/layers/data/NPDU.js +103 -0
- package/dist/core/layers/data/TPDU.d.ts +53 -0
- package/dist/core/layers/data/TPDU.js +73 -0
- package/dist/core/layers/interfaces/APCI.d.ts +61 -0
- package/dist/core/layers/interfaces/APCI.js +92 -0
- package/dist/core/layers/interfaces/TPCI.d.ts +110 -0
- package/dist/core/layers/interfaces/TPCI.js +196 -0
- package/dist/core/resources/DeviceDescriptorType.d.ts +46 -0
- package/dist/core/resources/DeviceDescriptorType.js +69 -0
- package/dist/errors/DPTNotFound.d.ts +6 -0
- package/dist/errors/DPTNotFound.js +15 -0
- package/dist/errors/InvalidKnxAddresExeption.d.ts +3 -0
- package/dist/errors/InvalidKnxAddresExeption.js +9 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +18 -0
- package/dist/utils/CEMIAdapter.d.ts +16 -0
- package/dist/utils/CEMIAdapter.js +94 -0
- package/dist/utils/KNXHelper.d.ts +78 -0
- package/dist/utils/KNXHelper.js +338 -0
- package/dist/utils/Logger.d.ts +17 -0
- package/dist/utils/Logger.js +96 -0
- package/dist/utils/MessageCodeTranslator.d.ts +19 -0
- package/dist/utils/MessageCodeTranslator.js +77 -0
- package/dist/utils/checksumFrame.d.ts +18 -0
- package/dist/utils/checksumFrame.js +41 -0
- package/dist/utils/localIp.d.ts +7 -0
- package/dist/utils/localIp.js +45 -0
- package/package.json +49 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TunnelConnection = void 0;
|
|
4
|
+
const KNXnetIPHeader_1 = require("../core/KNXnetIPHeader");
|
|
5
|
+
const KNXnetIPEnum_1 = require("../core/enum/KNXnetIPEnum");
|
|
6
|
+
/**
|
|
7
|
+
* Encapsulates a single KNXnet/IP Tunnelling or Management connection state.
|
|
8
|
+
* Handles sequence numbers, heartbeats, reliable delivery (stop-and-wait),
|
|
9
|
+
* and retransmissions according to KNX Spec Vol 3/8/4.
|
|
10
|
+
*/
|
|
11
|
+
class TunnelConnection {
|
|
12
|
+
channelId;
|
|
13
|
+
controlHPAI;
|
|
14
|
+
dataHPAI;
|
|
15
|
+
knxAddress;
|
|
16
|
+
knxAddressStr;
|
|
17
|
+
knxLayer;
|
|
18
|
+
socket;
|
|
19
|
+
heartbeatTimeoutMs;
|
|
20
|
+
retransmitTimeoutMs;
|
|
21
|
+
maxQueueSize;
|
|
22
|
+
onDisconnect;
|
|
23
|
+
sno = 0;
|
|
24
|
+
rno = 0;
|
|
25
|
+
heartbeatTimer = null;
|
|
26
|
+
pendingAck = null;
|
|
27
|
+
queue = [];
|
|
28
|
+
isSending = false;
|
|
29
|
+
// Rate limiting / Pacing state
|
|
30
|
+
rxCount = 0;
|
|
31
|
+
lastRxTime = Date.now();
|
|
32
|
+
logger;
|
|
33
|
+
constructor(channelId, controlHPAI, dataHPAI, knxAddress, knxAddressStr, knxLayer, socket, heartbeatTimeoutMs, retransmitTimeoutMs, maxQueueSize, onDisconnect, parentLogger) {
|
|
34
|
+
this.channelId = channelId;
|
|
35
|
+
this.controlHPAI = controlHPAI;
|
|
36
|
+
this.dataHPAI = dataHPAI;
|
|
37
|
+
this.knxAddress = knxAddress;
|
|
38
|
+
this.knxAddressStr = knxAddressStr;
|
|
39
|
+
this.knxLayer = knxLayer;
|
|
40
|
+
this.socket = socket;
|
|
41
|
+
this.heartbeatTimeoutMs = heartbeatTimeoutMs;
|
|
42
|
+
this.retransmitTimeoutMs = retransmitTimeoutMs;
|
|
43
|
+
this.maxQueueSize = maxQueueSize;
|
|
44
|
+
this.onDisconnect = onDisconnect;
|
|
45
|
+
this.logger = parentLogger.child({ channelId, IA: knxAddressStr });
|
|
46
|
+
this.resetHeartbeat();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resets the heartbeat timer. Should be called on any valid activity.
|
|
50
|
+
*/
|
|
51
|
+
resetHeartbeat() {
|
|
52
|
+
if (this.heartbeatTimer)
|
|
53
|
+
clearTimeout(this.heartbeatTimer);
|
|
54
|
+
this.heartbeatTimer = setTimeout(() => {
|
|
55
|
+
this.logger.warn(`Heartbeat timeout`);
|
|
56
|
+
this.onDisconnect(this.channelId, true);
|
|
57
|
+
}, this.heartbeatTimeoutMs);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Enqueues a CEMI message to be sent to the client.
|
|
61
|
+
*/
|
|
62
|
+
enqueue(cemiBuffer, serviceType) {
|
|
63
|
+
if (this.maxQueueSize > 0 && this.queue.length >= this.maxQueueSize) {
|
|
64
|
+
this.logger.warn(`Outgoing queue full. Dropping connection.`);
|
|
65
|
+
this.onDisconnect(this.channelId, true);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const seq = this.sno;
|
|
69
|
+
const tunnelHeader = Buffer.from([0x04, this.channelId, seq, 0x00]);
|
|
70
|
+
const responseHeader = new KNXnetIPHeader_1.KNXnetIPHeader(serviceType, KNXnetIPHeader_1.KNXnetIPHeader.HEADER_SIZE_10 + tunnelHeader.length + cemiBuffer.length);
|
|
71
|
+
const packet = Buffer.concat([responseHeader.toBuffer(), tunnelHeader, cemiBuffer]);
|
|
72
|
+
this.queue.push({ packet, seq, serviceType });
|
|
73
|
+
this.sno = (this.sno + 1) % 256;
|
|
74
|
+
this.processQueue();
|
|
75
|
+
}
|
|
76
|
+
processQueue() {
|
|
77
|
+
if (this.isSending || this.queue.length === 0)
|
|
78
|
+
return;
|
|
79
|
+
this.isSending = true;
|
|
80
|
+
const item = this.queue.shift();
|
|
81
|
+
this.sendWithRetry(item.packet, item.seq, false);
|
|
82
|
+
}
|
|
83
|
+
sendWithRetry(packet, seq, isRetransmission) {
|
|
84
|
+
if (!this.socket)
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
this.socket.send(packet, this.dataHPAI.port, this.dataHPAI.ipAddress);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
this.logger.error(`Failed to send packet to ${this.dataHPAI.ipAddress}: ${err.message}`);
|
|
91
|
+
this.onDisconnect(this.channelId, false);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.pendingAck = {
|
|
95
|
+
packet,
|
|
96
|
+
seq,
|
|
97
|
+
isRetransmission,
|
|
98
|
+
timer: setTimeout(() => {
|
|
99
|
+
if (!isRetransmission) {
|
|
100
|
+
// Spec 2.6.1: Repeat once
|
|
101
|
+
this.logger.warn(`ACK timeout for seq ${seq}, retransmitting...`);
|
|
102
|
+
this.sendWithRetry(packet, seq, true);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Spec 2.6.1: Terminate connection
|
|
106
|
+
this.logger.error(`Second ACK timeout for seq ${seq}. Terminating connection.`);
|
|
107
|
+
this.onDisconnect(this.channelId, true);
|
|
108
|
+
}
|
|
109
|
+
}, this.retransmitTimeoutMs)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Handles an incoming ACK from the client.
|
|
114
|
+
*/
|
|
115
|
+
handleAck(seq, status) {
|
|
116
|
+
this.resetHeartbeat();
|
|
117
|
+
if (this.pendingAck && this.pendingAck.seq === seq) {
|
|
118
|
+
clearTimeout(this.pendingAck.timer);
|
|
119
|
+
this.pendingAck = null;
|
|
120
|
+
this.isSending = false;
|
|
121
|
+
if (status !== KNXnetIPEnum_1.KNXnetIPErrorCodes.E_NO_ERROR) {
|
|
122
|
+
this.logger.warn(`Received ACK with error ${status}. Closing connection.`);
|
|
123
|
+
this.onDisconnect(this.channelId, true);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Process next item in queue
|
|
127
|
+
this.processQueue();
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.logger.debug(`Ignored ACK for seq ${seq} (expected ${this.pendingAck?.seq})`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Validates an incoming request from the client according to sequence number rules.
|
|
135
|
+
*/
|
|
136
|
+
validateRequest(seq) {
|
|
137
|
+
this.resetHeartbeat();
|
|
138
|
+
if (seq === this.rno) {
|
|
139
|
+
// Expected sequence number
|
|
140
|
+
this.rno = (this.rno + 1) % 256;
|
|
141
|
+
return { action: 'process', status: KNXnetIPEnum_1.KNXnetIPErrorCodes.E_NO_ERROR };
|
|
142
|
+
}
|
|
143
|
+
else if (seq === (this.rno - 1 + 256) % 256) {
|
|
144
|
+
// Previous sequence number (retransmit ACK)
|
|
145
|
+
return { action: 'retransmit_ack', status: KNXnetIPEnum_1.KNXnetIPErrorCodes.E_NO_ERROR };
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Out of sequence - discard without reply (Spec 2.6.1)
|
|
149
|
+
this.logger.warn(`Out of sequence request: got ${seq}, expected ${this.rno}`);
|
|
150
|
+
return { action: 'discard', status: 0 };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Closes the connection and cleans up resources.
|
|
155
|
+
*/
|
|
156
|
+
close() {
|
|
157
|
+
if (this.heartbeatTimer)
|
|
158
|
+
clearTimeout(this.heartbeatTimer);
|
|
159
|
+
if (this.pendingAck)
|
|
160
|
+
clearTimeout(this.pendingAck.timer);
|
|
161
|
+
this.heartbeatTimer = null;
|
|
162
|
+
this.pendingAck = null;
|
|
163
|
+
this.queue = [];
|
|
164
|
+
this.isSending = false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.TunnelConnection = TunnelConnection;
|