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,242 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.KNXService = void 0;
7
+ const events_1 = require("events");
8
+ const dgram_1 = __importDefault(require("dgram"));
9
+ const KNXnetIPHeader_1 = require("../core/KNXnetIPHeader");
10
+ const KNXnetIPStructures_1 = require("../core/KNXnetIPStructures");
11
+ const KNXnetIPEnum_1 = require("../core/enum/KNXnetIPEnum");
12
+ const localIp_1 = require("../utils/localIp");
13
+ const KNXDataEncode_1 = require("../core/data/KNXDataEncode");
14
+ const CEMI_1 = require("../core/CEMI");
15
+ const ControlField_1 = require("../core/ControlField");
16
+ const ControlFieldExtended_1 = require("../core/ControlFieldExtended");
17
+ const TPDU_1 = require("../core/layers/data/TPDU");
18
+ const TPCI_1 = require("../core/layers/interfaces/TPCI");
19
+ const APDU_1 = require("../core/layers/data/APDU");
20
+ const APCI_1 = require("../core/layers/interfaces/APCI");
21
+ const APCIEnum_1 = require("../core/enum/APCIEnum");
22
+ const Logger_1 = require("../utils/Logger");
23
+ const KNXnetIPServer_1 = require("./KNXnetIPServer");
24
+ const KNXHelper_1 = require("../utils/KNXHelper");
25
+ const InvalidKnxAddresExeption_1 = require("../errors/InvalidKnxAddresExeption");
26
+ class KNXService extends events_1.EventEmitter {
27
+ socket = null;
28
+ options;
29
+ _transport = "UDP";
30
+ logger;
31
+ constructor(options = {}) {
32
+ super();
33
+ this.options = {
34
+ ip: "224.0.23.12",
35
+ port: 3671,
36
+ localIp: (0, localIp_1.getLocalIP)(),
37
+ localPort: 0,
38
+ ...options,
39
+ };
40
+ if (this.options.logOptions) {
41
+ (0, Logger_1.setupLogger)(this.options.logOptions);
42
+ }
43
+ this.logger = Logger_1.knxLogger;
44
+ }
45
+ /**
46
+ * Send a GroupValue_Write telegram to a group address.
47
+ * @param destination The group address (e.g., "1/1/1")
48
+ * @param value The value to write.
49
+ * @param dpt Optional Datapoint Type to help with encoding.
50
+ */
51
+ async write(destination, dpt, value) {
52
+ let data;
53
+ let isShort = false;
54
+ // data validation
55
+ if (dpt !== undefined) {
56
+ data = KNXDataEncode_1.KnxDataEncoder.encodeThis(dpt, value);
57
+ isShort = KNXDataEncode_1.KnxDataEncoder.isShortDpt(dpt);
58
+ }
59
+ else if (typeof value === "boolean") {
60
+ data = Buffer.from([value ? 1 : 0]);
61
+ isShort = true;
62
+ }
63
+ else if (Buffer.isBuffer(value)) {
64
+ data = value;
65
+ isShort = data.length === 1 && data[0] <= 0x3f;
66
+ }
67
+ else if (typeof value === "number") {
68
+ data = Buffer.from([value]);
69
+ isShort = value <= 0x3f;
70
+ }
71
+ else {
72
+ throw new Error("Cannot encode value without DPT or basic type (boolean/number/Buffer)");
73
+ }
74
+ let cf2Value = 0;
75
+ if (KNXHelper_1.KNXHelper.isValidGroupAddress(destination)) {
76
+ cf2Value = 0xe0;
77
+ }
78
+ else if (KNXHelper_1.KNXHelper.isValidIndividualAddress(destination)) {
79
+ cf2Value = 0x60;
80
+ }
81
+ else {
82
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(`This address ${destination} is not valid`);
83
+ }
84
+ const cf1 = new ControlField_1.ControlField(0xbc);
85
+ const cf2 = new ControlFieldExtended_1.ExtendedControlField(cf2Value);
86
+ const tpdu = new TPDU_1.TPDU(new TPCI_1.TPCI(TPCI_1.TPCIType.T_DATA_GROUP_PDU), new APDU_1.APDU(new TPCI_1.TPCI(TPCI_1.TPCIType.T_DATA_GROUP_PDU), new APCI_1.APCI(APCIEnum_1.APCIEnum.A_GroupValue_Write_Protocol_Data_Unit), data, isShort), data);
87
+ const cemi = new CEMI_1.CEMI.DataLinkLayerCEMI["L_Data.req"](null, cf1, cf2, this instanceof KNXnetIPServer_1.KNXnetIPServer ? this.individualAddress : "0.0.0", destination, tpdu);
88
+ this.logger.debug({ service: cemi.constructor.name }, "Sending GroupValue_Write");
89
+ return this.send(cemi);
90
+ }
91
+ /**
92
+ * Send a GroupValue_Read telegram to a group address.
93
+ * @param destination The group address (e.g., "1/1/1")
94
+ */
95
+ async read(destination) {
96
+ const cf1 = new ControlField_1.ControlField(0xbc);
97
+ const cf2 = new ControlFieldExtended_1.ExtendedControlField(0xe0);
98
+ const tpdu = new TPDU_1.TPDU(new TPCI_1.TPCI(TPCI_1.TPCIType.T_DATA_GROUP_PDU), new APDU_1.APDU(new TPCI_1.TPCI(TPCI_1.TPCIType.T_DATA_GROUP_PDU), new APCI_1.APCI(APCIEnum_1.APCIEnum.A_GroupValue_Read_Protocol_Data_Unit), Buffer.alloc(0)), Buffer.alloc(0));
99
+ const cemi = new CEMI_1.CEMI.DataLinkLayerCEMI["L_Data.req"](null, cf1, cf2, "0.0.0", destination, tpdu);
100
+ return this.send(cemi);
101
+ }
102
+ /**
103
+ * Discovery Process (Search Request)
104
+ */
105
+ static async discover(timeout = 3000, localIp) {
106
+ return new Promise((resolve, reject) => {
107
+ const socket = dgram_1.default.createSocket("udp4");
108
+ const devices = [];
109
+ const _localIp = localIp || (0, localIp_1.getLocalIP)();
110
+ socket.on("message", (msg, rinfo) => {
111
+ try {
112
+ const header = KNXnetIPHeader_1.KNXnetIPHeader.fromBuffer(msg);
113
+ if (header.serviceType === KNXnetIPEnum_1.KNXnetIPServiceType.SEARCH_RESPONSE) {
114
+ const hpaiData = msg.subarray(6, 14);
115
+ const hpai = KNXnetIPStructures_1.HPAI.fromBuffer(hpaiData);
116
+ // Parse DIBs starting from byte 14
117
+ const dibs = [];
118
+ let offset = 14;
119
+ while (offset < msg.length) {
120
+ const dibLen = msg.readUInt8(offset);
121
+ if (offset + dibLen > msg.length)
122
+ break;
123
+ const dibBuffer = msg.subarray(offset, offset + dibLen);
124
+ dibs.push(KNXnetIPStructures_1.DIB.fromBuffer(dibBuffer));
125
+ offset += dibLen;
126
+ }
127
+ devices.push({
128
+ ip: hpai.ipAddress,
129
+ port: hpai.port,
130
+ dibs,
131
+ });
132
+ }
133
+ }
134
+ catch (e) {
135
+ // Ignore malformed packets
136
+ }
137
+ });
138
+ socket.bind(() => {
139
+ const localHPAI = new KNXnetIPStructures_1.HPAI(KNXnetIPEnum_1.HostProtocolCode.IPV4_UDP, _localIp, socket.address().port);
140
+ const header = new KNXnetIPHeader_1.KNXnetIPHeader(KNXnetIPEnum_1.KNXnetIPServiceType.SEARCH_REQUEST, 0);
141
+ const hpaiBuffer = localHPAI.toBuffer();
142
+ header.totalLength = header.toBuffer().length + hpaiBuffer.length;
143
+ const packet = Buffer.concat([header.toBuffer(), hpaiBuffer]);
144
+ // Send to Multicast Address
145
+ socket.send(packet, 3671, "224.0.23.12");
146
+ setTimeout(() => {
147
+ socket.close();
148
+ resolve(devices);
149
+ }, timeout);
150
+ });
151
+ });
152
+ }
153
+ /**
154
+ * Extended Discovery Process (Search Request Extended)
155
+ * Allows searching with filters (SRPs).
156
+ */
157
+ static async discoverExtended(srps, timeout = 3000, localIp) {
158
+ return new Promise((resolve, reject) => {
159
+ const socket = dgram_1.default.createSocket("udp4");
160
+ const devices = [];
161
+ const _localIp = localIp || (0, localIp_1.getLocalIP)();
162
+ socket.on("message", (msg, rinfo) => {
163
+ try {
164
+ const header = KNXnetIPHeader_1.KNXnetIPHeader.fromBuffer(msg);
165
+ if (header.serviceType === KNXnetIPEnum_1.KNXnetIPServiceType.SEARCH_RESPONSE ||
166
+ header.serviceType === KNXnetIPEnum_1.KNXnetIPServiceType.SEARCH_RESPONSE_EXTENDED) {
167
+ // Extract IP/Port from HPAI
168
+ const hpai = KNXnetIPStructures_1.HPAI.fromBuffer(msg.subarray(6, 14));
169
+ const dibs = [];
170
+ let offset = 14;
171
+ while (offset < msg.length) {
172
+ const dibLen = msg.readUInt8(offset);
173
+ if (dibLen === 0)
174
+ break;
175
+ dibs.push(KNXnetIPStructures_1.DIB.fromBuffer(msg.subarray(offset, offset + dibLen)));
176
+ offset += dibLen;
177
+ }
178
+ devices.push({ ip: hpai.ipAddress, port: hpai.port, dibs });
179
+ }
180
+ }
181
+ catch (e) { }
182
+ });
183
+ socket.bind(() => {
184
+ const localHPAI = new KNXnetIPStructures_1.HPAI(KNXnetIPEnum_1.HostProtocolCode.IPV4_UDP, _localIp, socket.address().port);
185
+ const header = new KNXnetIPHeader_1.KNXnetIPHeader(KNXnetIPEnum_1.KNXnetIPServiceType.SEARCH_REQUEST_EXTENDED, 0);
186
+ const hpaiBuf = localHPAI.toBuffer();
187
+ const srpBufs = srps.map((s) => s.toBuffer());
188
+ const body = Buffer.concat([hpaiBuf, ...srpBufs]);
189
+ header.totalLength = 6 + body.length;
190
+ socket.send(Buffer.concat([header.toBuffer(), body]), 3671, "224.0.23.12");
191
+ setTimeout(() => {
192
+ socket.close();
193
+ resolve(devices);
194
+ }, timeout);
195
+ });
196
+ });
197
+ }
198
+ /**
199
+ * Description Request (Self Description) * Queries a specific device for its capabilities.
200
+ */
201
+ async describe() {
202
+ return new Promise((resolve, reject) => {
203
+ // Description is usually connectionless via UDP on the Control Endpoint
204
+ const descSocket = dgram_1.default.createSocket("udp4");
205
+ const targetIp = this.options.ip;
206
+ const targetPort = this.options.port;
207
+ const timeout = setTimeout(() => {
208
+ descSocket.close();
209
+ reject(new Error("Description request timed out"));
210
+ }, 2000);
211
+ descSocket.on("message", (msg) => {
212
+ try {
213
+ const header = KNXnetIPHeader_1.KNXnetIPHeader.fromBuffer(msg);
214
+ if (header.serviceType === KNXnetIPEnum_1.KNXnetIPServiceType.DESCRIPTION_RESPONSE) {
215
+ clearTimeout(timeout);
216
+ // Similar parsing logic as Discovery
217
+ const dibs = [];
218
+ let offset = 6; // Header length
219
+ while (offset < msg.length) {
220
+ const dibLen = msg.readUInt8(offset);
221
+ const dibBuffer = msg.subarray(offset, offset + dibLen);
222
+ dibs.push(KNXnetIPStructures_1.DIB.fromBuffer(dibBuffer));
223
+ offset += dibLen;
224
+ }
225
+ descSocket.close();
226
+ resolve({ dibs });
227
+ }
228
+ }
229
+ catch (e) { }
230
+ });
231
+ descSocket.bind(() => {
232
+ const localHPAI = new KNXnetIPStructures_1.HPAI(KNXnetIPEnum_1.HostProtocolCode.IPV4_UDP, this.options.localIp, descSocket.address().port);
233
+ const header = new KNXnetIPHeader_1.KNXnetIPHeader(KNXnetIPEnum_1.KNXnetIPServiceType.DESCRIPTION_REQUEST, 0);
234
+ const hpaiBuffer = localHPAI.toBuffer();
235
+ header.totalLength = 6 + hpaiBuffer.length;
236
+ const packet = Buffer.concat([header.toBuffer(), hpaiBuffer]);
237
+ descSocket.send(packet, targetPort, targetIp);
238
+ });
239
+ });
240
+ }
241
+ }
242
+ exports.KNXService = KNXService;
@@ -0,0 +1,44 @@
1
+ import { KNXService } from "./KNXService";
2
+ import { ServiceMessage } from "../@types/interfaces/ServiceMessage";
3
+ import { KNXTunnelingOptions } from "../@types/interfaces/connection";
4
+ /**
5
+ * Handles KNXnet/IP Tunneling connections for point-to-point communication with a KNX gateway.
6
+ * This class manages the connection state, sequence numbering for reliable delivery,
7
+ * heartbeat monitoring (ConnectionState), and message queuing over both UDP and TCP transports.
8
+ */
9
+ export declare class KNXTunneling extends KNXService {
10
+ private channelId;
11
+ private sequenceNumber;
12
+ private rxSequenceNumber;
13
+ private isConnected;
14
+ private tcpBuffer;
15
+ assignedAddress: number;
16
+ private heartbeatTimer;
17
+ private heartbeatFailures;
18
+ private heartbeatRetryTimer;
19
+ private msgQueue;
20
+ private isSending;
21
+ private pendingAck;
22
+ private activeRequest;
23
+ private readonly MAX_QUEUE_SIZE;
24
+ private disconnectTimeout;
25
+ constructor(options: KNXTunnelingOptions);
26
+ connect(): Promise<void>;
27
+ private connectUDP;
28
+ private connectTCP;
29
+ private sendConnectRequest;
30
+ disconnect(): void;
31
+ private closeSocket;
32
+ send(cemi: ServiceMessage | Buffer): Promise<void>;
33
+ private processQueue;
34
+ private handleAckTimeout;
35
+ getFeature(featureId: number): Promise<Buffer>;
36
+ private handleMessage;
37
+ private handleRequest;
38
+ private sendAck;
39
+ private sendRaw;
40
+ private startHeartbeat;
41
+ private sendHeartbeatRequest;
42
+ private handleHeartbeatTimeout;
43
+ private stopHeartbeat;
44
+ }