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,338 @@
1
+ "use strict";
2
+ // +-----------------------------------------------+
3
+ // 16 bits | INDIVIDUAL ADDRESS |
4
+ // +-----------------------+-----------------------+
5
+ // | OCTET 0 (high byte) | OCTET 1 (low byte) |
6
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
7
+ // bits | 7| 6| 5| 4| 3| 2| 1| 0| 7| 6| 5| 4| 3| 2| 1| 0|
8
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
9
+ // | Subnetwork Address | |
10
+ // +-----------+-----------+ Device Address |
11
+ // |(Area Adrs)|(Line Adrs)| |
12
+ // +-----------------------+-----------------------+
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.KNXHelper = void 0;
15
+ const InvalidKnxAddresExeption_1 = require("../errors/InvalidKnxAddresExeption");
16
+ // +-----------------------------------------------+
17
+ // 16 bits | GROUP ADDRESS (3 level) |
18
+ // +-----------------------+-----------------------+
19
+ // | OCTET 0 (high byte) | OCTET 1 (low byte) |
20
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
21
+ // bits | 7| 6| 5| 4| 3| 2| 1| 0| 7| 6| 5| 4| 3| 2| 1| 0|
22
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
23
+ // | | Main Grp | Midd G | Sub Group |
24
+ // +--+--------------------+-----------------------+
25
+ // +-----------------------------------------------+
26
+ // 16 bits | GROUP ADDRESS (2 level) |
27
+ // +-----------------------+-----------------------+
28
+ // | OCTET 0 (high byte) | OCTET 1 (low byte) |
29
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
30
+ // bits | 7| 6| 5| 4| 3| 2| 1| 0| 7| 6| 5| 4| 3| 2| 1| 0|
31
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
32
+ // | | Main Grp | Sub Group |
33
+ // +--+--------------------+-----------------------+
34
+ /**
35
+ * This class is completely based on the knx.js repo, which has been very helpful.
36
+ * @see https://github.com/estbeetoo/knx.js
37
+ */
38
+ class KNXHelper {
39
+ constructor() {
40
+ throw new Error("This class is static");
41
+ }
42
+ static GetAddress(addr, separator = "/", threeLevelAddressing = true) {
43
+ // 1. Caso String: Delegar a GetAddress_
44
+ if (typeof addr === "string") {
45
+ return this.GetAddress_(addr);
46
+ }
47
+ // 2. Caso Number: llamar recursivamente
48
+ if (typeof addr === "number") {
49
+ const buffer = Buffer.alloc(2);
50
+ buffer.writeUint16BE(addr & 0xFFFF);
51
+ return this.GetAddress(buffer, separator, threeLevelAddressing);
52
+ }
53
+ // 3. Caso Buffer
54
+ if (Buffer.isBuffer(addr)) {
55
+ if (addr.length < 2) {
56
+ throw new Error("KNX Address buffer must be at least 2 bytes");
57
+ }
58
+ const highByte = addr[0];
59
+ const lowByte = addr[1];
60
+ const isGroup = separator === "/";
61
+ // Caso A: Dirección de Grupo 2 Niveles (Main/Sub11)
62
+ if (isGroup && !threeLevelAddressing) {
63
+ const main = highByte >> 3; // 5 bits
64
+ const sub = ((highByte & 0x07) << 8) | lowByte; // 3 bits altos + 8 bits bajos (Usar OR bitwise | es más seguro que +)
65
+ return `${main}/${sub}`;
66
+ }
67
+ // Caso B: Dirección de Grupo 3 Niveles (Main/Mid/Sub)
68
+ if (isGroup) {
69
+ const main = (highByte >> 3) & 0x1f; // 5 bits (0-31)
70
+ const middle = highByte & 0x07; // 3 bits (0-7)
71
+ const sub = lowByte; // 8 bits (0-255)
72
+ return `${main}/${middle}/${sub}`;
73
+ }
74
+ // Caso C: Dirección Individual (Area.Line.Device)
75
+ // Estándar: Area (4 bits), Line (4 bits), Device (8 bits)
76
+ const area = highByte >> 4; // 4 bits
77
+ const line = highByte & 0x0f; // 4 bits
78
+ const device = lowByte; // 8 bits
79
+ return `${area}.${line}.${device}`;
80
+ }
81
+ throw new TypeError("Address must be a string or a Buffer");
82
+ }
83
+ /**
84
+ * Converts a KNX address (string) into a 2-byte buffer, this method is an alternative to the {@link GetAddress_} method, both methods do the same thing
85
+ * @param address Group or individual address (e.g., "1.1.100" or "1/2/3")
86
+ * @param separator Address separator ("." for individual, "/" for group)
87
+ * @param group `true` for group address, `false` for individual address
88
+ * @param threeLevelAddressing `true` for 3-level, `false` for 2-level
89
+ * @returns 2-byte buffer with the encoded address
90
+ */
91
+ static addressToBuffer(address, separator = ".", group = false, threeLevelAddressing = true) {
92
+ const parts = address.split(separator).map(Number);
93
+ let addr = Buffer.alloc(2);
94
+ if (parts.length < (threeLevelAddressing ? 3 : 2)) {
95
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException("Invalid address. Incorrect format.");
96
+ }
97
+ if (group) {
98
+ if (threeLevelAddressing) {
99
+ if (parts[0] > 31 || parts[1] > 7 || parts[2] > 255) {
100
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException("Invalid group address (3 levels)");
101
+ }
102
+ addr[0] = ((parts[0] & 0x1f) << 3) | (parts[1] & 0x07);
103
+ addr[1] = parts[2] & 0xff;
104
+ }
105
+ else {
106
+ if (parts[0] > 31 || parts[1] > 2047) {
107
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException("Invalid group address (2 levels)");
108
+ }
109
+ addr[0] = (parts[0] << 3) | ((parts[1] >> 8) & 0x07);
110
+ addr[1] = parts[1] & 0xff;
111
+ }
112
+ }
113
+ else {
114
+ if (parts[0] > 15 || parts[1] > 15 || parts[2] > 255) {
115
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException("Invalid individual address.");
116
+ }
117
+ addr[0] = ((parts[0] & 0x0f) << 4) | (parts[1] & 0x0f);
118
+ addr[1] = parts[2] & 0xff;
119
+ }
120
+ return addr;
121
+ }
122
+ /**
123
+ * Converts a group address from a string into a buffer, first validating if it is a source address if its separator is **"."** otherwise validating if it is a group address if the separator is **"/"**, the alternative to this method is {@link addressToBuffer}
124
+ * @param address Group address
125
+ * @returns
126
+ */
127
+ static GetAddress_(address) {
128
+ try {
129
+ let addr = Buffer.alloc(2);
130
+ let threeLevelAddressing = true;
131
+ let parts;
132
+ let group = address.indexOf("/") !== -1;
133
+ if (!group) {
134
+ // individual address
135
+ parts = address.split(".");
136
+ if (parts.length != 3 || parts[0].length > 2 || parts[1].length > 2 || parts[2].length > 3)
137
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
138
+ }
139
+ else {
140
+ // group address
141
+ parts = address.split("/");
142
+ if (parts.length != 3 || parts[0].length > 2 || parts[1].length > 1 || parts[2].length > 3) {
143
+ if (parts.length != 2 || parts[0].length > 2 || parts[1].length > 4)
144
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
145
+ threeLevelAddressing = false;
146
+ }
147
+ }
148
+ if (!threeLevelAddressing) {
149
+ let part = parseInt(parts[0]);
150
+ if (part > 15)
151
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
152
+ addr[0] = (part << 3) & 255;
153
+ part = parseInt(parts[1]);
154
+ if (part > 2047)
155
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
156
+ let part2 = Buffer.alloc(2);
157
+ part2.writeUInt16BE(part, 0);
158
+ if (part2.length > 2)
159
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
160
+ addr[0] = (addr[0] | part2[0]) & 255;
161
+ addr[1] = part2[1];
162
+ }
163
+ else {
164
+ let part = parseInt(parts[0]);
165
+ if (part > 31)
166
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
167
+ addr[0] = group ? (part << 3) & 255 : (part << 4) & 255;
168
+ part = parseInt(parts[1]);
169
+ if ((group && part > 7) || (!group && part > 15))
170
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
171
+ addr[0] = (addr[0] | part) & 255;
172
+ part = parseInt(parts[2]);
173
+ if (part > 255)
174
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
175
+ addr[1] = part & 255;
176
+ }
177
+ return addr;
178
+ }
179
+ catch (e) {
180
+ throw new InvalidKnxAddresExeption_1.InvalidKnxAddressException(address);
181
+ }
182
+ }
183
+ static GetDataLength(data, isShort = false) {
184
+ if (data.length <= 0)
185
+ return 0;
186
+ if (isShort)
187
+ return 1;
188
+ if (data.length == 1 && data[0] <= 0x3f)
189
+ return 1;
190
+ if (data.length == 4)
191
+ return 3;
192
+ // if (data[0] <= 0x3f) return data.length;
193
+ return data.length + 1;
194
+ }
195
+ /**
196
+ * Método auxiliar para codificar un número en formato DPT9 (2 bytes).
197
+ * Implementación corregida usando aritmética de bits y complemento a dos real.
198
+ */
199
+ // private static encodeFloatToDPT9(value: number): Buffer {
200
+ // // 1. Manejo de errores y casos especiales
201
+ // if (isNaN(value) || !isFinite(value)) {
202
+ // // Retornar valor de error estándar KNX o manejar excepción
203
+ // console.warn("DPT9: Valor inválido, enviando buffer vacío/error");
204
+ // return Buffer.from([0x7f, 0xff]);
205
+ // }
206
+ // // 2. Cálculo matemático
207
+ // // FloatValue = (0.01 * M) * 2^E
208
+ // let m = value / 0.01;
209
+ // let e = 0;
210
+ // // 3. Normalización: Buscar encajar M en 11 bits [-2048, 2047]
211
+ // while ((m > 2047 || m < -2048) && e < 15) {
212
+ // m /= 2;
213
+ // e++;
214
+ // }
215
+ // // Redondeo final
216
+ // let mInt = Math.round(m);
217
+ // // 4. Clamping (Asegurar límites para no romper el protocolo)
218
+ // if (mInt > 2047) mInt = 2047;
219
+ // if (mInt < -2048) mInt = -2048;
220
+ // // Caso especial: Evitar la colisión con "Invalid Data" (0x7FFF) si se diera el caso extremo
221
+ // if (e === 15 && mInt > 2046) mInt = 2046;
222
+ // // 5. Empaquetado
223
+ // // El signo se maneja automáticamente en la mantisa de 11 bits (complemento a 2)
224
+ // // pero KNX requiere que el bit de signo esté explícitamente en el bit 15 (MSB del buffer)
225
+ // const signBit = mInt < 0 ? 1 : 0;
226
+ // // Máscara 0x7FF (2047) obtiene los 11 bits bajos de la mantisa (sea positiva o negativa)
227
+ // const mantissaBits = mInt & 0x7ff;
228
+ // // Estructura: S (1 bit) | E (4 bits) | M (11 bits)
229
+ // const encoded = (signBit << 15) | ((e & 0x0f) << 11) | mantissaBits;
230
+ // const buffer = Buffer.alloc(2);
231
+ // buffer.writeUInt16BE(encoded, 0);
232
+ // return buffer;
233
+ // }
234
+ /**
235
+ * Escribe los datos en el datagrama.
236
+ * @param datagram PDU
237
+ * @param data
238
+ * @param dataStart Debe ser siempre en el indice donde están los bits menos significativos del APCI
239
+ * @param isShort Si es true, el dato se incrusta en los 6 bits del APCI (DPT 1, 2, 3)
240
+ */
241
+ static WriteData(datagram, data, dataStart, isShort = false) {
242
+ if (!data || data.length === 0)
243
+ return;
244
+ // ESTRATEGIA: DPT9 (Float 16-bit)
245
+ // Asumimos que si vienen 4 bytes, es un Float32 crudo que debe convertirse a DPT9
246
+ // !! No es necesario si ya hay una clase que lo codifica, decidir procesarlo aqui desafia lo que se espera de esta escritura
247
+ // if (data.length === 4) {
248
+ // const floatValue = data.readFloatLE(0);
249
+ // const dpt9Buffer = this.encodeFloatToDPT9(floatValue);
250
+ // // DPT9 siempre ocupa 2 bytes y va después del byte de control APCI
251
+ // datagram[dataStart + 1] = dpt9Buffer[0];
252
+ // datagram[dataStart + 2] = dpt9Buffer[1];
253
+ // return;
254
+ // }
255
+ // ESTRATEGIA: Optimización "Short Data" (6 bits)
256
+ // Si es 1 byte y el valor es pequeño (<= 0x3F), asumimos que es DPT1/2/3 y lo incrustamos.
257
+ if (data.length === 1 && data[0] <= 0x3f || isShort) {
258
+ // Usamos OR para mezclar los 6 bits bajos con el comando existente en dataStart
259
+ datagram[dataStart] = (datagram[dataStart] & 0xc0) | (data[0] & 0x3f);
260
+ return;
261
+ }
262
+ // ESTRATEGIA: Datos estándar (> 6 bits o arrays largos)
263
+ // Se escriben a partir del siguiente byte (dataStart + 1)
264
+ datagram[dataStart] = datagram[dataStart] & 0xc0; // Limpiamos la parte de datos del byte de control
265
+ for (var i = 0; i < data.length; i++) {
266
+ datagram[dataStart + 1 + i] = data[i];
267
+ }
268
+ }
269
+ /**
270
+ * Verifica si una dirección de grupo KNX es válida.
271
+ * Los formatos admitidos son:
272
+ * - 3 niveles: "main/middle/sub" donde:
273
+ * main: 0-31, middle: 0-7, sub: 0-255.
274
+ * - 2 niveles: "main/sub" donde:
275
+ * main: 0-31, sub: 0-2047.
276
+ * - 1 nivel: un número entre 0 y 65535.
277
+ *
278
+ * @param address Dirección de grupo en formato string.
279
+ * @returns true si la dirección es válida; false en caso contrario.
280
+ */
281
+ static isValidGroupAddress(address) {
282
+ const threeLevelRegex = /^(\d{1,2})\/(\d{1,2})\/(\d{1,3})$/;
283
+ const twoLevelRegex = /^(\d{1,2})\/(\d{1,4})$/;
284
+ const oneLevelRegex = /^\d+$/;
285
+ let match;
286
+ if ((match = address.match(threeLevelRegex))) {
287
+ const main = parseInt(match[1], 10);
288
+ const middle = parseInt(match[2], 10);
289
+ const sub = parseInt(match[3], 10);
290
+ return main >= 0 && main <= 31 && middle >= 0 && middle <= 7 && sub >= 0 && sub <= 255;
291
+ }
292
+ else if ((match = address.match(twoLevelRegex))) {
293
+ const main = parseInt(match[1], 10);
294
+ const sub = parseInt(match[2], 10);
295
+ return main >= 0 && main <= 31 && sub >= 0 && sub <= 2047;
296
+ }
297
+ else if (oneLevelRegex.test(address)) {
298
+ const value = parseInt(address, 10);
299
+ return value >= 0 && value <= 65535;
300
+ }
301
+ return false;
302
+ }
303
+ /**
304
+ * Verifica si una dirección individual KNX es válida.
305
+ * Formato válido: "area.line.device" donde:
306
+ * - area: 0–15
307
+ * - line: 0–15
308
+ * - device: 0–255
309
+ *
310
+ * @param address Dirección individual en formato string.
311
+ * @returns true si la dirección es válida; false en caso contrario.
312
+ */
313
+ static isValidIndividualAddress(address) {
314
+ const regex = /^(\d{1,2})\.(\d{1,2})\.(\d{1,3})$/;
315
+ const match = address.match(regex);
316
+ if (!match)
317
+ return false;
318
+ const area = parseInt(match[1], 10);
319
+ const line = parseInt(match[2], 10);
320
+ const device = parseInt(match[3], 10);
321
+ return area >= 0 && area <= 15 && line >= 0 && line <= 15 && device >= 0 && device <= 255;
322
+ }
323
+ /**
324
+ * Verifica si un Buffer de 2 octetos representa una dirección de grupo KNX válida.
325
+ *
326
+ * Se asume que la dirección se codifica en 2 bytes (0–65535).
327
+ *
328
+ * @param buffer Buffer con la dirección de grupo.
329
+ * @returns true si el buffer es válido; false en caso contrario.
330
+ */
331
+ static isValidGroupAddressBuffer(buffer) {
332
+ if (buffer.length !== 2)
333
+ return false;
334
+ const value = buffer.readUInt16BE(0);
335
+ return value >= 0 && value <= 65535;
336
+ }
337
+ }
338
+ exports.KNXHelper = KNXHelper;
@@ -0,0 +1,17 @@
1
+ import { Logger } from "pino";
2
+ import { KNXLoggerOptions } from "../@types/interfaces/connection";
3
+ /**
4
+ * Creates a configured pino logger instance.
5
+ * Completely configurable via options, no environment variable dependencies.
6
+ * Production mode (JSON) is only active if NOT in a test environment.
7
+ */
8
+ export declare const createKNXLogger: (options?: KNXLoggerOptions) => Logger;
9
+ /**
10
+ * Global default logger instance.
11
+ */
12
+ export declare let knxLogger: Logger;
13
+ /**
14
+ * Configures the global knxLogger instance.
15
+ * Call this at the beginning of your application to apply custom settings.
16
+ */
17
+ export declare const setupLogger: (options: KNXLoggerOptions) => Logger;
@@ -0,0 +1,96 @@
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.setupLogger = exports.knxLogger = exports.createKNXLogger = void 0;
7
+ const pino_1 = __importDefault(require("pino"));
8
+ const colorette_1 = require("colorette");
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ /**
12
+ * Creates a configured pino logger instance.
13
+ * Completely configurable via options, no environment variable dependencies.
14
+ * Production mode (JSON) is only active if NOT in a test environment.
15
+ */
16
+ const createKNXLogger = (options) => {
17
+ const isProduction = process.env.NODE_ENV === "production";
18
+ const targets = [];
19
+ // 1. Console Transport
20
+ if (!isProduction) {
21
+ targets.push({
22
+ target: "pino-pretty",
23
+ options: {
24
+ colorize: colorette_1.isColorSupported,
25
+ messageFormat: "[{module}] {msg}",
26
+ translateTime: "SYS:HH:MM:ss.l",
27
+ ignore: "pid,hostname,module",
28
+ customColors: "info:blue,warn:yellow,error:red,debug:magenta",
29
+ },
30
+ });
31
+ }
32
+ else if (options?.logToFile) {
33
+ // In production with file logging, we must explicitly add stdout if we want it
34
+ targets.push({
35
+ target: "pino/file",
36
+ options: { destination: 1 }, // 1 = stdout
37
+ });
38
+ }
39
+ // 2. File Transport (pino-roll)
40
+ if (options?.logToFile) {
41
+ const logDir = options.logDir || "./logs";
42
+ let logFile = options.logFilename;
43
+ if (!logFile || logFile.trim() === "") {
44
+ const now = new Date();
45
+ const dateStr = now.toISOString().split("T")[0]; // YYYY-MM-DD
46
+ logFile = `${dateStr}.log`;
47
+ }
48
+ if (!fs_1.default.existsSync(logDir)) {
49
+ fs_1.default.mkdirSync(logDir, { recursive: true });
50
+ }
51
+ targets.push({
52
+ target: "pino-roll",
53
+ options: {
54
+ file: path_1.default.join(logDir, logFile),
55
+ size: options.logSize || "10M",
56
+ interval: options.logInterval || "1d",
57
+ limit: {
58
+ count: options.logKeepCount || 7,
59
+ },
60
+ mkdir: true,
61
+ },
62
+ });
63
+ }
64
+ const transportConfig = targets.length > 0
65
+ ? { targets }
66
+ : undefined;
67
+ const defaultOptions = {
68
+ level: options?.level || "info",
69
+ timestamp: () => `,"time":"${new Date(Date.now()).toISOString()}"`,
70
+ enabled: options?.enabled ?? true,
71
+ transport: options?.transport || transportConfig,
72
+ ...options,
73
+ };
74
+ // Pino does not allow custom level formatters when using transports with targets
75
+ if (!defaultOptions.transport) {
76
+ defaultOptions.formatters = {
77
+ level: (label) => ({ level: label }),
78
+ ...options?.formatters,
79
+ };
80
+ }
81
+ return (0, pino_1.default)(defaultOptions);
82
+ };
83
+ exports.createKNXLogger = createKNXLogger;
84
+ /**
85
+ * Global default logger instance.
86
+ */
87
+ exports.knxLogger = (0, exports.createKNXLogger)();
88
+ /**
89
+ * Configures the global knxLogger instance.
90
+ * Call this at the beginning of your application to apply custom settings.
91
+ */
92
+ const setupLogger = (options) => {
93
+ exports.knxLogger = (0, exports.createKNXLogger)(options);
94
+ return exports.knxLogger;
95
+ };
96
+ exports.setupLogger = setupLogger;
@@ -0,0 +1,19 @@
1
+ type StandardType = "EMI1" | "EMI2/IMI2" | "IMI1" | "CEMI";
2
+ export declare class MessageCodeTranslator {
3
+ private static emi1Map;
4
+ private static emi2Map;
5
+ private static cemiMap;
6
+ /**
7
+ * Traduce un código de mensaje de un estándar origen a un estándar destino.
8
+ * @param sourceCode El byte recibido (ej. 0x49)
9
+ * @param from El estándar origen (ej. "EMI1")
10
+ * @param to El estándar destino (ej. "CEMI")
11
+ * @returns El código traducido (ej. 0x29) o null si no existe equivalencia.
12
+ */
13
+ static translate(sourceCode: number, from: StandardType, to: StandardType): number | null;
14
+ /**
15
+ * Obtiene el nombre legible del servicio dado un código (útil para logs/debug)
16
+ */
17
+ static getServiceName(code: number, standard: StandardType): string | null;
18
+ }
19
+ export {};
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessageCodeTranslator = void 0;
4
+ const MessageCodeField_1 = require("../core/MessageCodeField");
5
+ class MessageCodeTranslator {
6
+ // Mapas para búsqueda rápida: Code -> ServiceName
7
+ static emi1Map = new Map();
8
+ static emi2Map = new Map();
9
+ static cemiMap = new Map();
10
+ // Inicialización estática (se ejecuta una sola vez)
11
+ static {
12
+ for (const [serviceName, standards] of Object.entries(MessageCodeField_1.MESSAGE_CODE_FIELD)) {
13
+ if (!standards)
14
+ continue;
15
+ // Mapear EMI1
16
+ if ("EMI1" in standards && standards.EMI1) {
17
+ this.emi1Map.set(standards.EMI1.value, serviceName);
18
+ }
19
+ // Mapear EMI2
20
+ if ("EMI2/IMI2" in standards && standards["EMI2/IMI2"]) {
21
+ this.emi2Map.set(standards["EMI2/IMI2"].value, serviceName);
22
+ }
23
+ // Mapear CEMI
24
+ if ("CEMI" in standards && standards.CEMI) {
25
+ this.cemiMap.set(standards.CEMI.value, serviceName);
26
+ }
27
+ }
28
+ }
29
+ /**
30
+ * Traduce un código de mensaje de un estándar origen a un estándar destino.
31
+ * @param sourceCode El byte recibido (ej. 0x49)
32
+ * @param from El estándar origen (ej. "EMI1")
33
+ * @param to El estándar destino (ej. "CEMI")
34
+ * @returns El código traducido (ej. 0x29) o null si no existe equivalencia.
35
+ */
36
+ static translate(sourceCode, from, to) {
37
+ // 1. Identificar el nombre del servicio (ej. "L_Data.ind")
38
+ let serviceName;
39
+ switch (from) {
40
+ case "EMI1":
41
+ serviceName = this.emi1Map.get(sourceCode);
42
+ break;
43
+ case "EMI2/IMI2":
44
+ serviceName = this.emi2Map.get(sourceCode);
45
+ break;
46
+ case "CEMI":
47
+ serviceName = this.cemiMap.get(sourceCode);
48
+ break;
49
+ }
50
+ if (!serviceName)
51
+ return null; // Código no reconocido en el origen
52
+ // 2. Obtener el valor en el estándar destino
53
+ const targetStandard = MessageCodeField_1.MESSAGE_CODE_FIELD[serviceName];
54
+ // Verificación de tipo segura
55
+ if (targetStandard && to in targetStandard) {
56
+ // @ts-ignore: TypeScript puede quejarse del acceso dinámico, pero la lógica es sólida
57
+ return targetStandard[to]?.value ?? null;
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Obtiene el nombre legible del servicio dado un código (útil para logs/debug)
63
+ */
64
+ static getServiceName(code, standard) {
65
+ switch (standard) {
66
+ case "EMI1":
67
+ return this.emi1Map.get(code) || null;
68
+ case "EMI2/IMI2":
69
+ return this.emi2Map.get(code) || null;
70
+ case "CEMI":
71
+ return this.cemiMap.get(code) || null;
72
+ default:
73
+ return null;
74
+ }
75
+ }
76
+ }
77
+ exports.MessageCodeTranslator = MessageCodeTranslator;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Calcula el FCS (Frame Check Sequence) de un buffer KNX (sin incluir el FCS final)
3
+ * @param buffer Buffer con el contenido del telegrama SIN el FCS
4
+ * @returns Byte FCS calculado
5
+ */
6
+ export declare function checksum(buffer: Buffer): number;
7
+ /**
8
+ * Verifica el FCS (Frame Check Sequence) de un buffer KNX.
9
+ * @param buffer Buffer completo del frame (incluyendo FCS al final)
10
+ * @returns true si el FCS es válido, false en caso contrario
11
+ */
12
+ export declare function verifyFCS(buffer: Buffer): boolean;
13
+ /**
14
+ * Verifica y remueve el FCS de un buffer.
15
+ * @param buffer Buffer completo del frame
16
+ * @returns Buffer sin el FCS si es válido, lanza error si no lo es
17
+ */
18
+ export declare function verifyAndRemoveFCS(buffer: Buffer): Buffer;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checksum = checksum;
4
+ exports.verifyFCS = verifyFCS;
5
+ exports.verifyAndRemoveFCS = verifyAndRemoveFCS;
6
+ /**
7
+ * Calcula el FCS (Frame Check Sequence) de un buffer KNX (sin incluir el FCS final)
8
+ * @param buffer Buffer con el contenido del telegrama SIN el FCS
9
+ * @returns Byte FCS calculado
10
+ */
11
+ function checksum(buffer) {
12
+ let fcs = 0x00;
13
+ for (let i = 0; i < buffer.length; i++) {
14
+ fcs ^= buffer[i];
15
+ }
16
+ return fcs;
17
+ }
18
+ /**
19
+ * Verifica el FCS (Frame Check Sequence) de un buffer KNX.
20
+ * @param buffer Buffer completo del frame (incluyendo FCS al final)
21
+ * @returns true si el FCS es válido, false en caso contrario
22
+ */
23
+ function verifyFCS(buffer) {
24
+ if (buffer.length === 0) {
25
+ return false;
26
+ }
27
+ const receivedFCS = buffer[buffer.length - 1];
28
+ const calculatedFCS = checksum(buffer.subarray(0, buffer.length - 1));
29
+ return receivedFCS === calculatedFCS;
30
+ }
31
+ /**
32
+ * Verifica y remueve el FCS de un buffer.
33
+ * @param buffer Buffer completo del frame
34
+ * @returns Buffer sin el FCS si es válido, lanza error si no lo es
35
+ */
36
+ function verifyAndRemoveFCS(buffer) {
37
+ if (!verifyFCS(buffer)) {
38
+ throw new Error("Invalid FCS in frame");
39
+ }
40
+ return buffer.subarray(0, buffer.length - 1);
41
+ }
@@ -0,0 +1,7 @@
1
+ export declare function getLocalIP(): string;
2
+ export declare function getLocalMac(): string;
3
+ export declare function getNetworkInfo(): {
4
+ address: string;
5
+ netmask: string;
6
+ mac: string;
7
+ };
@@ -0,0 +1,45 @@
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.getLocalIP = getLocalIP;
7
+ exports.getLocalMac = getLocalMac;
8
+ exports.getNetworkInfo = getNetworkInfo;
9
+ const os_1 = __importDefault(require("os"));
10
+ function getLocalIP() {
11
+ const networkInterfaces = os_1.default.networkInterfaces();
12
+ for (const interfaceName in networkInterfaces) {
13
+ const interfaces = networkInterfaces[interfaceName];
14
+ for (const net of interfaces) {
15
+ if (net.family === 'IPv4' && !net.internal) {
16
+ return net.address;
17
+ }
18
+ }
19
+ }
20
+ return 'localhost';
21
+ }
22
+ function getLocalMac() {
23
+ const info = getNetworkInfo();
24
+ return info.mac;
25
+ }
26
+ function getNetworkInfo() {
27
+ const networkInterfaces = os_1.default.networkInterfaces();
28
+ for (const interfaceName in networkInterfaces) {
29
+ const interfaces = networkInterfaces[interfaceName];
30
+ for (const net of interfaces) {
31
+ if (net.family === 'IPv4' && !net.internal) {
32
+ return {
33
+ address: net.address,
34
+ netmask: net.netmask,
35
+ mac: net.mac
36
+ };
37
+ }
38
+ }
39
+ }
40
+ return {
41
+ address: '0.0.0.0',
42
+ netmask: '0.0.0.0',
43
+ mac: '00:00:00:00:00:00'
44
+ };
45
+ }