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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pino from "pino";
|
|
2
2
|
import { ConnectionType } from "../../core/enum/KNXnetIPEnum";
|
|
3
3
|
/**
|
|
4
4
|
* Options for configuring a KNX Tunneling connection.
|
|
@@ -29,7 +29,9 @@ export interface KNXTunnelingOptions extends KNXnetIPOptions {
|
|
|
29
29
|
*/
|
|
30
30
|
maxQueueSize?: number;
|
|
31
31
|
}
|
|
32
|
-
export interface KNXnetIPServerOptions extends KNXnetIPOptions {
|
|
32
|
+
export interface KNXnetIPServerOptions extends Omit<KNXnetIPOptions, "ip" | "port"> {
|
|
33
|
+
ip?: string;
|
|
34
|
+
port?: number;
|
|
33
35
|
individualAddress?: string;
|
|
34
36
|
serialNumber?: Buffer;
|
|
35
37
|
friendlyName?: string;
|
|
@@ -46,10 +48,6 @@ export interface KNXnetIPServerOptions extends KNXnetIPOptions {
|
|
|
46
48
|
* Setting this to a lower value can increase performance but may flood slow KNX segments.
|
|
47
49
|
*/
|
|
48
50
|
routingDelay?: number;
|
|
49
|
-
/**
|
|
50
|
-
* Optional configuration for bridging to external connections (TPUART, Tunneling).
|
|
51
|
-
*/
|
|
52
|
-
externals?: ExternalManagerOptions;
|
|
53
51
|
/**
|
|
54
52
|
* It abruptly stops a Tunneling client connection if it exceeds this limit of request messages per second; this is done to prevent performance degradation (the default is 100); to disable it, set it to less than 1
|
|
55
53
|
*/
|
|
@@ -62,6 +60,10 @@ export interface KNXnetIPServerOptions extends KNXnetIPOptions {
|
|
|
62
60
|
useAllInterfaces?: boolean;
|
|
63
61
|
}
|
|
64
62
|
export interface ExternalManagerOptions {
|
|
63
|
+
/**
|
|
64
|
+
* Optional configuration for a KNXnetIPServer
|
|
65
|
+
*/
|
|
66
|
+
knxNetIpServer?: KNXnetIPServerOptions;
|
|
65
67
|
/**
|
|
66
68
|
* Optional configuration for a physical TPUART connection.
|
|
67
69
|
*/
|
|
@@ -70,12 +72,16 @@ export interface ExternalManagerOptions {
|
|
|
70
72
|
* Optional list of outbound KNX IP Tunneling client connections.
|
|
71
73
|
*/
|
|
72
74
|
tunneling?: KNXTunnelingOptions[];
|
|
75
|
+
/**
|
|
76
|
+
* Optional configuration for a physical KNX USB connection.
|
|
77
|
+
*/
|
|
78
|
+
usb?: KNXUSBOptions;
|
|
73
79
|
/**
|
|
74
80
|
* Pino logger configuration for the Router bridge.
|
|
75
81
|
*/
|
|
76
|
-
logOptions?:
|
|
82
|
+
logOptions?: KNXLoggerOptions;
|
|
77
83
|
}
|
|
78
|
-
export interface KNXLoggerOptions extends LoggerOptions {
|
|
84
|
+
export interface KNXLoggerOptions extends pino.LoggerOptions {
|
|
79
85
|
/**
|
|
80
86
|
* Use pino-pretty but it's slow
|
|
81
87
|
*/
|
|
@@ -110,8 +116,8 @@ export interface KNXLoggerOptions extends LoggerOptions {
|
|
|
110
116
|
logKeepCount?: number;
|
|
111
117
|
}
|
|
112
118
|
export interface KNXnetIPOptions {
|
|
113
|
-
ip
|
|
114
|
-
port
|
|
119
|
+
ip: string;
|
|
120
|
+
port: number;
|
|
115
121
|
localIp?: string;
|
|
116
122
|
localPort?: number;
|
|
117
123
|
/**
|
|
@@ -119,15 +125,15 @@ export interface KNXnetIPOptions {
|
|
|
119
125
|
*/
|
|
120
126
|
logOptions?: KNXLoggerOptions;
|
|
121
127
|
}
|
|
122
|
-
export interface TPUARTOptions
|
|
128
|
+
export interface TPUARTOptions {
|
|
123
129
|
/**
|
|
124
130
|
* The serial port path (e.g., "/dev/ttyS0" or "COM3").
|
|
125
131
|
*/
|
|
126
132
|
path: string;
|
|
127
133
|
/**
|
|
128
|
-
*
|
|
134
|
+
* Physical address to assign to the TPUART chip (e.g., "1.1.255").
|
|
129
135
|
*/
|
|
130
|
-
individualAddress
|
|
136
|
+
individualAddress: string;
|
|
131
137
|
/**
|
|
132
138
|
* If true, the TPUART will send an ACK for all group telegrams.
|
|
133
139
|
*/
|
|
@@ -136,4 +142,65 @@ export interface TPUARTOptions extends KNXnetIPOptions {
|
|
|
136
142
|
* If true, the TPUART will send an ACK for all individual telegrams.
|
|
137
143
|
*/
|
|
138
144
|
ackIndividual?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Pino logger configuration.
|
|
147
|
+
*/
|
|
148
|
+
logOptions?: KNXLoggerOptions;
|
|
149
|
+
}
|
|
150
|
+
export interface RouterConnOptions extends ExternalManagerOptions {
|
|
151
|
+
routerAddress: string;
|
|
152
|
+
/**
|
|
153
|
+
* Filtering IP addresses from KNXnetIP to other interfaces such as TPUART or USB
|
|
154
|
+
*/
|
|
155
|
+
toLocalFilter?: {
|
|
156
|
+
individualAddress?: {
|
|
157
|
+
addresses: string[];
|
|
158
|
+
individualAddressToLocalFilterPolicie: "discard all" | "accept only";
|
|
159
|
+
};
|
|
160
|
+
groupAddress?: {
|
|
161
|
+
addresses: string[];
|
|
162
|
+
groupAddressToLocalFilterPolicie: "discard all" | "accept only";
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Filtering addresses from interfaces such as TP UART or USB to KNXnet IP
|
|
167
|
+
*/
|
|
168
|
+
toIpFilter?: {
|
|
169
|
+
individualAddress?: {
|
|
170
|
+
addresses: string[];
|
|
171
|
+
individualAddressToIpFilterPolicie: "discard all" | "accept only";
|
|
172
|
+
};
|
|
173
|
+
groupAddress?: {
|
|
174
|
+
addresses: string[];
|
|
175
|
+
groupAddressToIpFilterPolicie: "discard all" | "accept only";
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export interface KNXUSBOptions {
|
|
180
|
+
path?: string;
|
|
181
|
+
vendorId?: number;
|
|
182
|
+
productId?: number;
|
|
183
|
+
individualAddress: string;
|
|
184
|
+
/**
|
|
185
|
+
* Pino logger configuration.
|
|
186
|
+
*/
|
|
187
|
+
logOptions?: KNXLoggerOptions;
|
|
188
|
+
}
|
|
189
|
+
export type AllConnectionOptions = TPUARTOptions | KNXUSBOptions | KNXnetIPServerOptions | KNXTunnelingOptions;
|
|
190
|
+
/**
|
|
191
|
+
* Interface for a discovered KNXnet/IP device.
|
|
192
|
+
*/
|
|
193
|
+
export interface KNXDiscoveredDevice {
|
|
194
|
+
ip: string;
|
|
195
|
+
port: number;
|
|
196
|
+
knxMediumRaw: number;
|
|
197
|
+
knxMedium: string;
|
|
198
|
+
deviceStatusRaw: number;
|
|
199
|
+
deviceStatus: string;
|
|
200
|
+
individualAddress: number;
|
|
201
|
+
projectInstallationId: number;
|
|
202
|
+
serialNumber: Buffer;
|
|
203
|
+
routingMulticastAddress: string;
|
|
204
|
+
macAddress: string;
|
|
205
|
+
friendlyName: string;
|
|
139
206
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { KNXnetIPServer } from "../../connection/KNXnetIPServer";
|
|
2
|
+
import { KNXService } from "../../connection/KNXService";
|
|
3
|
+
import { Router } from "../../connection/Router";
|
|
4
|
+
export interface MQTTGatewayOptions {
|
|
5
|
+
knxContext: Router | KNXnetIPServer;
|
|
6
|
+
embeddedBroker?: {
|
|
7
|
+
port: number;
|
|
8
|
+
host?: string;
|
|
9
|
+
};
|
|
10
|
+
brokerUrl?: string;
|
|
11
|
+
mqttUsername?: string;
|
|
12
|
+
mqttPassword?: string;
|
|
13
|
+
topicPrefix?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WebSocketGatewayOptions {
|
|
16
|
+
port: number;
|
|
17
|
+
knxContext: KNXService;
|
|
18
|
+
}
|
|
@@ -1,35 +1,31 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
2
|
import dgram from "dgram";
|
|
3
3
|
import net from "net";
|
|
4
|
-
import { SRP } from "../core/KNXnetIPStructures";
|
|
5
|
-
import { ServiceMessage } from "../@types/interfaces/ServiceMessage";
|
|
6
4
|
import { KnxDataEncoder } from "../core/data/KNXDataEncode";
|
|
5
|
+
import { CEMIInstance } from "../core/CEMI";
|
|
7
6
|
import { AllDpts } from "../@types/types/AllDpts";
|
|
8
|
-
import {
|
|
7
|
+
import { AllConnectionOptions } from "../@types/interfaces/connection";
|
|
9
8
|
import { Logger } from "pino";
|
|
10
|
-
export declare abstract class KNXService extends EventEmitter {
|
|
9
|
+
export declare abstract class KNXService<TOptions extends AllConnectionOptions = AllConnectionOptions> extends EventEmitter {
|
|
11
10
|
protected socket: dgram.Socket | net.Socket | null;
|
|
12
|
-
readonly options:
|
|
11
|
+
readonly options: TOptions;
|
|
13
12
|
protected _transport: "UDP" | "TCP";
|
|
14
13
|
protected logger: Logger;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
abstract disconnect(): void;
|
|
18
|
-
abstract send(data: Buffer | ServiceMessage): Promise<void>;
|
|
14
|
+
individualAddress: string;
|
|
15
|
+
constructor(options?: TOptions);
|
|
19
16
|
/**
|
|
20
|
-
*
|
|
17
|
+
* Start the connection
|
|
21
18
|
*/
|
|
22
|
-
|
|
19
|
+
abstract connect(): Promise<void>;
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
25
|
-
* Return false to block a specific group address.
|
|
21
|
+
* Safe disconnection of the connection
|
|
26
22
|
*/
|
|
27
|
-
|
|
23
|
+
abstract disconnect(): void;
|
|
28
24
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
25
|
+
* Send a telegram
|
|
26
|
+
* @param data A data buffer CEMI or EMI or an instance of a CEMI message
|
|
31
27
|
*/
|
|
32
|
-
|
|
28
|
+
abstract send(data: Buffer | CEMIInstance): Promise<void>;
|
|
33
29
|
/**
|
|
34
30
|
* Send a GroupValue_Write telegram to a group address.
|
|
35
31
|
* @param destination The group address (e.g., "1/1/1")
|
|
@@ -42,17 +38,4 @@ export declare abstract class KNXService extends EventEmitter {
|
|
|
42
38
|
* @param destination The group address (e.g., "1/1/1")
|
|
43
39
|
*/
|
|
44
40
|
read(destination: string): Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
* Discovery Process (Search Request)
|
|
47
|
-
*/
|
|
48
|
-
static discover(timeout?: number, localIp?: string): Promise<any[]>;
|
|
49
|
-
/**
|
|
50
|
-
* Extended Discovery Process (Search Request Extended)
|
|
51
|
-
* Allows searching with filters (SRPs).
|
|
52
|
-
*/
|
|
53
|
-
static discoverExtended(srps: SRP[], timeout?: number, localIp?: string): Promise<any[]>;
|
|
54
|
-
/**
|
|
55
|
-
* Description Request (Self Description) * Queries a specific device for its capabilities.
|
|
56
|
-
*/
|
|
57
|
-
describe(): Promise<any>;
|
|
58
41
|
}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.KNXService = void 0;
|
|
7
4
|
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
5
|
const localIp_1 = require("../utils/localIp");
|
|
13
6
|
const KNXDataEncode_1 = require("../core/data/KNXDataEncode");
|
|
14
7
|
const CEMI_1 = require("../core/CEMI");
|
|
@@ -20,19 +13,15 @@ const APDU_1 = require("../core/layers/data/APDU");
|
|
|
20
13
|
const APCI_1 = require("../core/layers/interfaces/APCI");
|
|
21
14
|
const APCIEnum_1 = require("../core/enum/APCIEnum");
|
|
22
15
|
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
16
|
class KNXService extends events_1.EventEmitter {
|
|
27
17
|
socket = null;
|
|
28
18
|
options;
|
|
29
19
|
_transport = "UDP";
|
|
30
20
|
logger;
|
|
21
|
+
individualAddress = "1.0.1";
|
|
31
22
|
constructor(options = {}) {
|
|
32
23
|
super();
|
|
33
24
|
this.options = {
|
|
34
|
-
ip: "224.0.23.12",
|
|
35
|
-
port: 3671,
|
|
36
25
|
localIp: (0, localIp_1.getLocalIP)(),
|
|
37
26
|
localPort: 0,
|
|
38
27
|
...options,
|
|
@@ -71,20 +60,10 @@ class KNXService extends events_1.EventEmitter {
|
|
|
71
60
|
else {
|
|
72
61
|
throw new Error("Cannot encode value without DPT or basic type (boolean/number/Buffer)");
|
|
73
62
|
}
|
|
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
63
|
const cf1 = new ControlField_1.ControlField(0xbc);
|
|
85
|
-
const cf2 = new ControlFieldExtended_1.ExtendedControlField(
|
|
64
|
+
const cf2 = new ControlFieldExtended_1.ExtendedControlField(0xe0);
|
|
86
65
|
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
|
|
66
|
+
const cemi = new CEMI_1.CEMI.DataLinkLayerCEMI["L_Data.req"](null, cf1, cf2, this.individualAddress, destination, tpdu);
|
|
88
67
|
this.logger.debug({ service: cemi.constructor.name }, "Sending GroupValue_Write");
|
|
89
68
|
return this.send(cemi);
|
|
90
69
|
}
|
|
@@ -95,148 +74,9 @@ class KNXService extends events_1.EventEmitter {
|
|
|
95
74
|
async read(destination) {
|
|
96
75
|
const cf1 = new ControlField_1.ControlField(0xbc);
|
|
97
76
|
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(
|
|
77
|
+
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(1)), Buffer.alloc(1));
|
|
99
78
|
const cemi = new CEMI_1.CEMI.DataLinkLayerCEMI["L_Data.req"](null, cf1, cf2, "0.0.0", destination, tpdu);
|
|
100
79
|
return this.send(cemi);
|
|
101
80
|
}
|
|
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
81
|
}
|
|
242
82
|
exports.KNXService = KNXService;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { KNXService } from "./KNXService";
|
|
2
|
-
import {
|
|
2
|
+
import { CEMIInstance } from "../core/CEMI";
|
|
3
3
|
import { KNXTunnelingOptions } from "../@types/interfaces/connection";
|
|
4
4
|
/**
|
|
5
5
|
* Handles KNXnet/IP Tunneling connections for point-to-point communication with a KNX gateway.
|
|
6
6
|
* This class manages the connection state, sequence numbering for reliable delivery,
|
|
7
7
|
* heartbeat monitoring (ConnectionState), and message queuing over both UDP and TCP transports.
|
|
8
8
|
*/
|
|
9
|
-
export declare class KNXTunneling extends KNXService {
|
|
9
|
+
export declare class KNXTunneling extends KNXService<KNXTunnelingOptions> {
|
|
10
10
|
private channelId;
|
|
11
11
|
private sequenceNumber;
|
|
12
12
|
private rxSequenceNumber;
|
|
13
13
|
private isConnected;
|
|
14
14
|
private tcpBuffer;
|
|
15
|
-
|
|
15
|
+
individualAddress: string;
|
|
16
16
|
private heartbeatTimer;
|
|
17
17
|
private heartbeatFailures;
|
|
18
18
|
private heartbeatRetryTimer;
|
|
@@ -29,7 +29,7 @@ export declare class KNXTunneling extends KNXService {
|
|
|
29
29
|
private sendConnectRequest;
|
|
30
30
|
disconnect(): void;
|
|
31
31
|
private closeSocket;
|
|
32
|
-
send(cemi:
|
|
32
|
+
send(cemi: CEMIInstance | Buffer): Promise<void>;
|
|
33
33
|
private processQueue;
|
|
34
34
|
private handleAckTimeout;
|
|
35
35
|
getFeature(featureId: number): Promise<Buffer>;
|