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.
- package/LICENSE +51 -21
- package/README.md +274 -61
- package/dist/@types/interfaces/connection.d.ts +80 -13
- package/dist/@types/interfaces/servers.d.ts +18 -0
- package/dist/@types/interfaces/servers.js +2 -0
- package/dist/connection/KNXService.d.ts +13 -30
- package/dist/connection/KNXService.js +4 -164
- package/dist/connection/KNXTunneling.d.ts +4 -4
- package/dist/connection/KNXTunneling.js +35 -62
- package/dist/connection/KNXUSBConnection.d.ts +20 -0
- package/dist/connection/KNXUSBConnection.js +358 -0
- package/dist/connection/KNXnetIPServer.d.ts +29 -12
- package/dist/connection/KNXnetIPServer.js +261 -83
- package/dist/connection/Router.d.ts +52 -32
- package/dist/connection/Router.js +225 -153
- package/dist/connection/TPUART.d.ts +8 -3
- package/dist/connection/TPUART.js +41 -37
- package/dist/connection/TunnelConnection.d.ts +3 -1
- package/dist/connection/TunnelConnection.js +6 -4
- package/dist/core/CEMI.d.ts +7 -2
- package/dist/core/CEMI.js +5 -8
- package/dist/core/EMI.d.ts +312 -200
- package/dist/core/EMI.js +511 -1007
- package/dist/core/KNXnetIPStructures.d.ts +10 -1
- package/dist/core/KNXnetIPStructures.js +15 -10
- package/dist/core/MessageCodeField.d.ts +1 -1
- package/dist/core/cache/GroupAddressCache.d.ts +57 -0
- package/dist/core/cache/GroupAddressCache.js +227 -0
- package/dist/core/data/KNXDataDecode.d.ts +2 -2
- package/dist/core/data/KNXDataDecode.js +198 -183
- package/dist/core/enum/EnumControlField.d.ts +0 -5
- package/dist/core/enum/EnumControlField.js +1 -7
- package/dist/core/enum/EnumControlFieldExtended.d.ts +1 -1
- package/dist/core/enum/EnumShortACKFrame.d.ts +1 -1
- package/dist/core/enum/ErrorCodeSet.js +59 -0
- package/dist/core/enum/KNXnetIPEnum.d.ts +2 -2
- package/dist/core/enum/KNXnetIPEnum.js +19 -1
- package/dist/core/layers/data/NPDU.d.ts +2 -1
- package/dist/core/layers/data/NPDU.js +6 -3
- package/dist/index.d.ts +19 -2
- package/dist/index.js +36 -1
- package/dist/server/KNXMQTTGateway.d.ts +13 -0
- package/dist/server/KNXMQTTGateway.js +164 -0
- package/dist/server/KNXWebSocketServer.d.ts +12 -0
- package/dist/server/KNXWebSocketServer.js +118 -0
- package/dist/utils/CEMIAdapter.d.ts +4 -3
- package/dist/utils/CEMIAdapter.js +26 -30
- package/dist/utils/Logger.d.ts +4 -4
- package/dist/utils/Logger.js +3 -7
- package/package.json +27 -7
|
@@ -4,11 +4,11 @@ exports.CEMIAdapter = void 0;
|
|
|
4
4
|
const CEMI_1 = require("../core/CEMI");
|
|
5
5
|
const ControlField_1 = require("../core/ControlField");
|
|
6
6
|
const ControlFieldExtended_1 = require("../core/ControlFieldExtended");
|
|
7
|
-
const TPDU_1 = require("../core/layers/data/TPDU");
|
|
8
7
|
const KNXHelper_1 = require("./KNXHelper");
|
|
9
8
|
const EnumControlFieldExtended_1 = require("../core/enum/EnumControlFieldExtended");
|
|
10
9
|
const MessageCodeTranslator_1 = require("./MessageCodeTranslator");
|
|
11
10
|
const EMI_1 = require("../core/EMI");
|
|
11
|
+
const NPDU_1 = require("../core/layers/data/NPDU");
|
|
12
12
|
/**
|
|
13
13
|
* Adapter utility to convert between EMI (External Message Interface) and cEMI (Common External Message Interface).
|
|
14
14
|
* EMI is typically used by physical interfaces like TPUART, while cEMI is standard for KNXnet/IP.
|
|
@@ -21,31 +21,28 @@ class CEMIAdapter {
|
|
|
21
21
|
static emiToCemi(emiBuffer) {
|
|
22
22
|
if (emiBuffer.length < 7)
|
|
23
23
|
throw new Error("EMI buffer too short");
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const ctrlByte = emiBuffer.readUInt8(offset++);
|
|
24
|
+
const messageCode = emiBuffer.readUInt8(0);
|
|
25
|
+
const ctrlByte = emiBuffer.readUInt8(1);
|
|
27
26
|
// Addresses (2 bytes each)
|
|
28
|
-
const srcAddr = KNXHelper_1.KNXHelper.GetAddress(emiBuffer.subarray(
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
27
|
+
const srcAddr = KNXHelper_1.KNXHelper.GetAddress(emiBuffer.subarray(2, 4), ".");
|
|
28
|
+
let dstAddr = "";
|
|
29
|
+
// const oct6 = emiBuffer.readUInt8(6); // AT,NPCI,LG
|
|
30
|
+
// const addressType = (oct6 >> 7) & 0x01;
|
|
31
|
+
// const hopCount = (oct6 >> 4) & 0x07;
|
|
32
|
+
// const octNum = oct6 & 0x0f; // NPDU Length
|
|
34
33
|
// --- Control Fields Transformation ---
|
|
35
34
|
// 1. cEMI Control Field 1 is identical to EMI Control
|
|
36
35
|
const controlField1 = new ControlField_1.ControlField(ctrlByte);
|
|
37
36
|
// 2. cEMI Control Field 2 (Extended) is extracted from EMI NPCI
|
|
38
37
|
// NPCI: [Res/AddrType] [Hop3] [Hop2] [Hop1] [Len3] [Len2] [Len1] [Len0]
|
|
39
|
-
const hopCount = (npciByte >> 4) & 0x07;
|
|
40
|
-
const addressType = npciByte & 0x80 ? EnumControlFieldExtended_1.AddressType.GROUP : EnumControlFieldExtended_1.AddressType.INDIVIDUAL;
|
|
41
38
|
const controlField2 = new ControlFieldExtended_1.ExtendedControlField();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
// --- NPDU Extraction ---
|
|
40
|
+
const npduBuff = emiBuffer.subarray(6);
|
|
41
|
+
// The implementation of NPDU have NPCI
|
|
42
|
+
const npdu = NPDU_1.NPDU.fromBuffer(npduBuff);
|
|
43
|
+
controlField2.hopCount = npdu.hopCount;
|
|
44
|
+
controlField2.addressType = npdu.addressType;
|
|
45
|
+
dstAddr = KNXHelper_1.KNXHelper.GetAddress(emiBuffer.subarray(4, 6), npdu.addressType === 1 ? "/" : ".");
|
|
49
46
|
// Translate Message Code (EMI 0x11 -> cEMI 0x11, etc.)
|
|
50
47
|
const cemiCode = MessageCodeTranslator_1.MessageCodeTranslator.translate(messageCode, "EMI2/IMI2", "CEMI");
|
|
51
48
|
if (cemiCode === null)
|
|
@@ -53,19 +50,18 @@ class CEMIAdapter {
|
|
|
53
50
|
// Construct the appropriate cEMI class using CEMI.fromBuffer or directly
|
|
54
51
|
// Since we already parsed components, we might want to find the class
|
|
55
52
|
// But CEMI.fromBuffer takes a buffer. Let's use it for simplicity if we can re-construct a cEMI buffer
|
|
56
|
-
// Or just instantiate the specific class.
|
|
53
|
+
// Or just instantiate the specific class.
|
|
57
54
|
// Given the structure of CEMI.ts, it's better to use the constructors if possible or fromBuffer with a temporary buffer.
|
|
58
|
-
const tempCemi = new CEMI_1.CEMI.DataLinkLayerCEMI["L_Data.ind"](null, controlField1, controlField2, srcAddr, dstAddr,
|
|
59
|
-
// Adjust message code if it's not L_Data.ind
|
|
60
|
-
tempCemi.messageCode = cemiCode;
|
|
55
|
+
const tempCemi = new CEMI_1.CEMI.DataLinkLayerCEMI["L_Data.ind"](null, controlField1, controlField2, srcAddr, dstAddr, npdu.TPDU);
|
|
61
56
|
return tempCemi;
|
|
62
57
|
}
|
|
63
58
|
/**
|
|
64
59
|
* Converts a cEMI instance to an EMI2 compatible Buffer or ServiceMessage.
|
|
65
60
|
*/
|
|
66
61
|
static cemiToEmi(cemi) {
|
|
67
|
-
if (!
|
|
62
|
+
if (!("TPDU" in cemi) || !("controlField1" in cemi) || !("controlField2" in cemi)) {
|
|
68
63
|
return null;
|
|
64
|
+
}
|
|
69
65
|
const tpduBuffer = cemi.TPDU.toBuffer();
|
|
70
66
|
// In EMI, NPDU includes NPCI byte at start
|
|
71
67
|
// NPCI = (AddressType << 7) | (HopCount << 4) | (TPDU Length)
|
|
@@ -74,17 +70,17 @@ class CEMIAdapter {
|
|
|
74
70
|
const emiCode = MessageCodeTranslator_1.MessageCodeTranslator.translate(cemi.messageCode, "CEMI", "EMI2/IMI2");
|
|
75
71
|
if (emiCode === null)
|
|
76
72
|
return null;
|
|
77
|
-
// Buffer [MC] [Ctrl] [Src] [Dst] [
|
|
78
|
-
const buffer = Buffer.alloc(7 +
|
|
73
|
+
// Buffer [MC] [Ctrl] [Src] [Dst] [NPCI] [TPDU]
|
|
74
|
+
const buffer = Buffer.alloc(7 + tpduBuffer.length);
|
|
79
75
|
let offset = 0;
|
|
80
76
|
buffer.writeUInt8(emiCode, offset++);
|
|
81
77
|
cemi.controlField1.buffer.copy(buffer, offset++);
|
|
82
|
-
KNXHelper_1.KNXHelper.
|
|
78
|
+
KNXHelper_1.KNXHelper.GetAddress(cemi.sourceAddress, ".").copy(buffer, offset);
|
|
83
79
|
offset += 2;
|
|
84
|
-
KNXHelper_1.KNXHelper.
|
|
80
|
+
KNXHelper_1.KNXHelper.GetAddress(cemi.destinationAddress, cemi.controlField2.addressType === EnumControlFieldExtended_1.AddressType.GROUP ? "/" : ".").copy(buffer, offset);
|
|
85
81
|
offset += 2;
|
|
86
|
-
// NPDU Length (NPCI + TPDU)
|
|
87
|
-
buffer.writeUInt8(tpduBuffer.length + 1, offset++);
|
|
82
|
+
// // NPDU Length (NPCI + TPDU)
|
|
83
|
+
// buffer.writeUInt8(tpduBuffer.length + 1, offset++);
|
|
88
84
|
// Write NPCI then TPDU
|
|
89
85
|
buffer.writeUInt8(npciByte, offset++);
|
|
90
86
|
tpduBuffer.copy(buffer, offset);
|
package/dist/utils/Logger.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pino from "pino";
|
|
2
2
|
import { KNXLoggerOptions } from "../@types/interfaces/connection";
|
|
3
3
|
/**
|
|
4
4
|
* Creates a configured pino logger instance.
|
|
5
5
|
* Completely configurable via options, no environment variable dependencies.
|
|
6
6
|
* Production mode (JSON) is only active if NOT in a test environment.
|
|
7
7
|
*/
|
|
8
|
-
export declare const createKNXLogger: (options?: KNXLoggerOptions) => Logger;
|
|
8
|
+
export declare const createKNXLogger: (options?: KNXLoggerOptions) => pino.Logger;
|
|
9
9
|
/**
|
|
10
10
|
* Global default logger instance.
|
|
11
11
|
*/
|
|
12
|
-
export declare let knxLogger: Logger;
|
|
12
|
+
export declare let knxLogger: pino.Logger;
|
|
13
13
|
/**
|
|
14
14
|
* Configures the global knxLogger instance.
|
|
15
15
|
* Call this at the beginning of your application to apply custom settings.
|
|
16
16
|
*/
|
|
17
|
-
export declare const setupLogger: (options: KNXLoggerOptions) => Logger;
|
|
17
|
+
export declare const setupLogger: (options: KNXLoggerOptions) => pino.Logger;
|
package/dist/utils/Logger.js
CHANGED
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.setupLogger = exports.knxLogger = exports.createKNXLogger = void 0;
|
|
7
7
|
const pino_1 = __importDefault(require("pino"));
|
|
8
|
-
const colorette_1 = require("colorette");
|
|
9
8
|
const path_1 = __importDefault(require("path"));
|
|
10
9
|
const fs_1 = __importDefault(require("fs"));
|
|
11
10
|
/**
|
|
@@ -14,14 +13,13 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
14
13
|
* Production mode (JSON) is only active if NOT in a test environment.
|
|
15
14
|
*/
|
|
16
15
|
const createKNXLogger = (options) => {
|
|
17
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
18
16
|
const targets = [];
|
|
19
17
|
// 1. Console Transport
|
|
20
|
-
if (!
|
|
18
|
+
if (!options?.pretty) {
|
|
21
19
|
targets.push({
|
|
22
20
|
target: "pino-pretty",
|
|
23
21
|
options: {
|
|
24
|
-
colorize:
|
|
22
|
+
colorize: true,
|
|
25
23
|
messageFormat: "[{module}] {msg}",
|
|
26
24
|
translateTime: "SYS:HH:MM:ss.l",
|
|
27
25
|
ignore: "pid,hostname,module",
|
|
@@ -61,9 +59,7 @@ const createKNXLogger = (options) => {
|
|
|
61
59
|
},
|
|
62
60
|
});
|
|
63
61
|
}
|
|
64
|
-
const transportConfig = targets.length > 0
|
|
65
|
-
? { targets }
|
|
66
|
-
: undefined;
|
|
62
|
+
const transportConfig = targets.length > 0 ? { targets } : undefined;
|
|
67
63
|
const defaultOptions = {
|
|
68
64
|
level: options?.level || "info",
|
|
69
65
|
timestamp: () => `,"time":"${new Date(Date.now()).toISOString()}"`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knx.ts",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A high-performance KNXnet/IP server and client library in TypeScript, focused on ETS stability.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,9 +14,15 @@
|
|
|
14
14
|
"prepublishOnly": "npm run build",
|
|
15
15
|
"start": "tsc && node dist/index.js",
|
|
16
16
|
"watch": "tsc -w",
|
|
17
|
-
"test
|
|
17
|
+
"test": "jest",
|
|
18
|
+
"test:router": "tsx src/test/router.test.ts",
|
|
19
|
+
"test:tunneling": "tsx src/test/tunneling_manual.test.ts",
|
|
18
20
|
"test:routing": "tsx src/test/routing_manual.test.ts",
|
|
21
|
+
"test:usb": "tsx src/test/test_usb_manual.test.ts",
|
|
22
|
+
"test:routingUsb": "tsx src/test/usb_routing_manual.test.ts",
|
|
23
|
+
"test:tpuart": "tsx src/test/tpuart_manual.test.ts",
|
|
19
24
|
"test:controlField": "tsx src/test/controlField.test.ts",
|
|
25
|
+
"test:emi": "tsx src/test/test_emi.test.ts",
|
|
20
26
|
"pack:test": "npm pack",
|
|
21
27
|
"release:patch": "npm version patch && npm publish",
|
|
22
28
|
"release:minor": "npm version minor && npm publish",
|
|
@@ -38,18 +44,32 @@
|
|
|
38
44
|
"typescript"
|
|
39
45
|
],
|
|
40
46
|
"author": "Arnold Steven Beleño Zuletta (Wesxt)",
|
|
41
|
-
"license": "
|
|
47
|
+
"license": "Apache-2.0",
|
|
42
48
|
"devDependencies": {
|
|
49
|
+
"@eslint/js": "^10.0.1",
|
|
50
|
+
"@types/jest": "^30.0.0",
|
|
43
51
|
"@types/node": "^22.19.13",
|
|
44
|
-
"@types/
|
|
52
|
+
"@types/node-hid": "^1.3.4",
|
|
53
|
+
"@types/ws": "^8.18.1",
|
|
54
|
+
"eslint": "^10.1.0",
|
|
55
|
+
"eslint-config-prettier": "^10.1.8",
|
|
56
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
57
|
+
"globals": "^17.4.0",
|
|
58
|
+
"jest": "^30.3.0",
|
|
59
|
+
"ts-jest": "^29.4.6",
|
|
45
60
|
"tsx": "^4.19.3",
|
|
46
|
-
"typescript": "^5.9.3"
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"typescript-eslint": "^8.58.0"
|
|
47
63
|
},
|
|
48
64
|
"dependencies": {
|
|
49
|
-
"
|
|
65
|
+
"aedes": "^1.0.2",
|
|
66
|
+
"aedes-server-factory": "^0.2.1",
|
|
67
|
+
"mqtt": "^5.15.1",
|
|
68
|
+
"node-hid": "^3.3.0",
|
|
50
69
|
"pino": "^10.3.1",
|
|
51
70
|
"pino-pretty": "^13.1.3",
|
|
52
71
|
"pino-roll": "^4.0.0",
|
|
53
|
-
"serialport": "^13.0.0"
|
|
72
|
+
"serialport": "^13.0.0",
|
|
73
|
+
"ws": "^8.20.0"
|
|
54
74
|
}
|
|
55
75
|
}
|