knx.ts 1.0.6 โ 1.0.8
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/README.md +166 -73
- package/dist/@types/interfaces/servers.d.ts +26 -3
- package/dist/connection/KNXService.d.ts +2 -0
- package/dist/connection/KNXService.js +2 -0
- package/dist/connection/KNXTunneling.js +38 -3
- package/dist/connection/KNXUSBConnection.js +48 -0
- package/dist/connection/KNXnetIPServer.d.ts +0 -2
- package/dist/connection/KNXnetIPServer.js +0 -2
- package/dist/connection/Router.js +2 -2
- package/dist/connection/TPUART.js +37 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/server/KNXMQTTGateway.js +4 -0
- package/dist/server/KNXWebSocketServer.js +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ This project focuses on protocol strictness, reading and sending any kind of **E
|
|
|
8
8
|
|
|
9
9
|
## ๐ Capabilities
|
|
10
10
|
|
|
11
|
-
- **Robust UDP Tunneling**: Implements a strict
|
|
11
|
+
- **Robust UDP Tunneling**: Implements a strict _Stop-and-Wait_ queue and sequence number management. This eliminates common "connection interrupted" issues in ETS or other applications during long sessions.
|
|
12
12
|
- **KNXnet/IP Routing**: Supports multicast routing (only in the **KNXnet/IP** server).
|
|
13
13
|
- **Discovery in the KNXnet/IP server**: Supports `SEARCH_REQUEST`, `SEARCH_REQUEST_EXTENDED`, `DESCRIPTION_REQUEST`, `CONNECT_REQUEST`, and `CONNECTIONSTATE_REQUEST` so applications such as ETS can discover it without manual configuration.
|
|
14
14
|
- **Direct Hardware Interfaces**: Native support for **KNX USB interfaces** (via `node-hid`) and **TPUART** serial chips (via `serialport`).
|
|
@@ -22,7 +22,7 @@ This project focuses on protocol strictness, reading and sending any kind of **E
|
|
|
22
22
|
According to `TODO.md`, several features are currently **experimental** or under development:
|
|
23
23
|
|
|
24
24
|
- **TCP Support**: The implementation is present, but testing is currently in an experimental phase.
|
|
25
|
-
- **Device Parameterization**: Support for
|
|
25
|
+
- **Device Parameterization**: Support for _Programming Mode_ (`progMode`) is planned to allow full device configuration through ETS.
|
|
26
26
|
- **Source Filtering**: Filtering based on source addresses and selective routing is on the roadmap.
|
|
27
27
|
- **Use of NPDU, TPDU, and APDU layers**: EMI still needs to use them for correct deserialization.
|
|
28
28
|
|
|
@@ -82,6 +82,7 @@ For developers building advanced monitoring or injection tools, the library expo
|
|
|
82
82
|
- `TPCIType` is an enum that helps write or identify TPCI values according to the specification.
|
|
83
83
|
- `DPTs`: the library exports interfaces such as `DPT5001` or `DPT1`. These interfaces are used by `KnxDataDecode` to return JavaScript objects and by `KnxDataEncoder` as parameter types to convert them into `Buffer`s.
|
|
84
84
|
- `ServiceMessage` is an interface implemented by all CEMI and EMI messages, and also by NPDU, TPDU, and APCI layers. This is useful because they all expose two helpful methods: `toBuffer` to serialize the instance into a buffer and `describe` to provide a human-readable view of the instance. **Note**: most exported classes in this library that do not implement this interface, such as `APCI`, still provide a `describe` method.
|
|
85
|
+
- `CEMIInstance` is an type of all CEMI Instances.
|
|
85
86
|
|
|
86
87
|
## ๐ ๏ธ Quick Start
|
|
87
88
|
|
|
@@ -90,60 +91,170 @@ For developers building advanced monitoring or injection tools, the library expo
|
|
|
90
91
|
Perfect for creating a bridge between your IP network and the KNX bus.
|
|
91
92
|
|
|
92
93
|
```typescript
|
|
93
|
-
import { KNXnetIPServer,
|
|
94
|
+
import { KNXnetIPServer, CEMIInstance, KnxDataDecode } from "knx.ts";
|
|
94
95
|
|
|
95
96
|
const server = new KNXnetIPServer({
|
|
96
|
-
localIp:
|
|
97
|
-
individualAddress:
|
|
98
|
-
friendlyName:
|
|
99
|
-
clientAddrs:
|
|
97
|
+
localIp: "192.168.1.50",
|
|
98
|
+
individualAddress: "1.1.0", // Be careful not to create conflicts
|
|
99
|
+
friendlyName: "TypeScript KNX Gateway", // This name is shown in ETS
|
|
100
|
+
clientAddrs: "1.1.10:5", // Provides 5 tunneling slots starting from 1.1.10
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
server.connect().then(() => {
|
|
103
|
-
console.log(
|
|
104
|
+
console.log("The KNXnet/IP server is running");
|
|
104
105
|
});
|
|
105
106
|
|
|
106
107
|
// Specific listener for a Group Address
|
|
107
|
-
server.on(
|
|
108
|
-
console.log(
|
|
109
|
-
console.log(
|
|
108
|
+
server.on("1/1/1", (cemi: CEMIInstance) => {
|
|
109
|
+
console.log("New data on 1/1/1:", cemi.TPDU.apdu.data); // Raw APDU data
|
|
110
|
+
console.log("Decoded data:", KnxDataDecode.decodeThis("1.001", cemi.TPDU.apdu.data)); // Converted JavaScript value
|
|
110
111
|
});
|
|
111
112
|
```
|
|
112
113
|
|
|
113
114
|
### Direct USB Connection
|
|
114
115
|
|
|
115
116
|
```typescript
|
|
116
|
-
import { KNXUSBConnection } from
|
|
117
|
+
import { KNXUSBConnection } from "knx.ts";
|
|
117
118
|
|
|
118
119
|
const usb = new KNXUSBConnection({
|
|
119
120
|
// Omitting path/vendorId will automatically discover the first known KNX USB interface
|
|
120
121
|
});
|
|
121
122
|
|
|
122
123
|
usb.connect().then(() => {
|
|
123
|
-
console.log(
|
|
124
|
+
console.log("Connected directly to the KNX USB interface");
|
|
124
125
|
});
|
|
125
126
|
|
|
126
|
-
usb.on(
|
|
127
|
-
console.log(
|
|
127
|
+
usb.on("indication", (cemi) => {
|
|
128
|
+
console.log("USB telegram source:", cemi.sourceAddress);
|
|
128
129
|
});
|
|
129
130
|
```
|
|
130
131
|
|
|
131
132
|
### Tunneling Client
|
|
132
133
|
|
|
133
134
|
```typescript
|
|
134
|
-
import { KNXTunneling } from
|
|
135
|
+
import { KNXTunneling } from "knx.ts";
|
|
135
136
|
|
|
136
137
|
const tunnel = new KNXTunneling({
|
|
137
|
-
ip:
|
|
138
|
+
ip: "192.168.1.100",
|
|
138
139
|
port: 3671,
|
|
139
|
-
localIp:
|
|
140
|
+
localIp: "192.168.1.50",
|
|
140
141
|
});
|
|
141
142
|
|
|
142
143
|
tunnel.connect().then(() => {
|
|
143
|
-
console.log(
|
|
144
|
+
console.log("Connected to the KNX bus");
|
|
144
145
|
});
|
|
145
146
|
```
|
|
146
147
|
|
|
148
|
+
## ๐ WebSocket & MQTT Gateways (API)
|
|
149
|
+
|
|
150
|
+
### GroupAddressCache (Integrated Caching)
|
|
151
|
+
|
|
152
|
+
The gateways depend heavily on `GroupAddressCache` to know the corresponding DPT for each group address. This allows them to seamlessly decode values and encode short primitives without requiring payload metadata on every request.
|
|
153
|
+
|
|
154
|
+
Every `KNXService` (like `KNXnetIPServer`, `Router`, etc.) has an integrated `GroupAddressCache`. It listens to incoming telegrams and remembers the last known values as well as configured DPTs, which is critical for query actions or state tracking in the Gateways.
|
|
155
|
+
|
|
156
|
+
> **Important**: This cache is disabled by default to conserve memory, but if you plan to use the `KNXWebSocketGateway` or `KNXMQTTGateway` servers, it will be enabled.
|
|
157
|
+
|
|
158
|
+
To enable caching, you must explicitly enable it globally:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { GroupAddressCache } from "knx.ts";
|
|
162
|
+
|
|
163
|
+
// Enable the cache singleton
|
|
164
|
+
GroupAddressCache.getInstance().setEnabled(true);
|
|
165
|
+
|
|
166
|
+
// You can optionally configure its limits (max addresses, max messages per address)
|
|
167
|
+
GroupAddressCache.getInstance().configure(65535, 10);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
If you create an instance of a router and register links with it, the router will handle cache management and do the same for destination address events.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { Router, KNXUSBConnection } from "knx.ts";
|
|
174
|
+
|
|
175
|
+
const router = new Router();
|
|
176
|
+
const usb = new KNXUSBConnection();
|
|
177
|
+
// The router will manage the link address cache and events related to destination addresses, rather than the link itself.
|
|
178
|
+
router.addLink(usb);
|
|
179
|
+
|
|
180
|
+
router.on("1/1/1", (cemi: CEMIInstance) => {
|
|
181
|
+
// <--- it activates
|
|
182
|
+
console.log("New data on 1/1/1:", cemi.TPDU.apdu.data); // Raw APDU data
|
|
183
|
+
console.log("Decoded data:", KnxDataDecode.decodeThis("1.001", cemi.TPDU.apdu.data)); // Converted JavaScript value
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
usb.on("1/1/1", (cemi: CEMIInstance) => {
|
|
187
|
+
// <--- it doesn't activate
|
|
188
|
+
console.log("New data on 1/1/1:", cemi.TPDU.apdu.data); // Raw APDU data
|
|
189
|
+
console.log("Decoded data:", KnxDataDecode.decodeThis("1.001", cemi.TPDU.apdu.data)); // Converted JavaScript value
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The library provides built-in Gateway servers allowing easy integration with web interfaces, dashboards, or IoT platforms. Both gateways act as a bridge over an existing `Router` or `KNXService` (like `KNXnetIPServer` or `KNXUSBConnection`).
|
|
194
|
+
|
|
195
|
+
The messages API payloads are exported as `WSClientPayload`, `WSServerPayload`, `MQTTCommandPayload`, and `MQTTStatePayload` so you can use them directly in your TypeScript projects.
|
|
196
|
+
|
|
197
|
+
### WebSocket Gateway
|
|
198
|
+
|
|
199
|
+
The `KNXWebSocketGateway` provides a simple, JSON-based bidirectional API over WebSockets.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { KNXWebSocketGateway } from "knx.ts/server";
|
|
203
|
+
// Or use: import { KNXWebSocketGateway } from 'knx.ts' if exported from root.
|
|
204
|
+
|
|
205
|
+
const wsGateway = new KNXWebSocketGateway({
|
|
206
|
+
port: 8080,
|
|
207
|
+
knxContext: router, // Provide a Router or any connected KNXService instance
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
wsGateway.start();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**WebSocket API JSON Payloads:**
|
|
214
|
+
|
|
215
|
+
- **Read / Querying**: Request to read from the KNX bus or query cached values.
|
|
216
|
+
- Request: `{ "action": "read", "groupAddress": "1/2/3" }`
|
|
217
|
+
- Response: `{ "action": "read_result", "groupAddress": "1/2/3", "data": ... }`
|
|
218
|
+
- Query Cache: `{ "action": "query", "groupAddress": "1/2/3", "onlyLatest": true }`
|
|
219
|
+
|
|
220
|
+
- **Write**: Send telegrams to the KNX bus.
|
|
221
|
+
- Command: `{ "action": "write", "groupAddress": "1/2/3", "value": 22.5, "dpt": "9.001" }`
|
|
222
|
+
|
|
223
|
+
- **Subscribe / Unsubscribe**: Listen to bus events.
|
|
224
|
+
- Subscribe: `{ "action": "subscribe", "groupAddress": "1/2/3" }` (Use `"*"` for all addresses).
|
|
225
|
+
- Event Response: `{ "action": "event", "groupAddress": "1/2/3", "decodedValue": ... }`
|
|
226
|
+
|
|
227
|
+
- **Configure DPT**: Let the cache know which DPT corresponds to an address.
|
|
228
|
+
- Command: `{ "action": "config_dpt", "groupAddress": "1/2/3", "dpt": "1.001" }`
|
|
229
|
+
|
|
230
|
+
### MQTT Gateway
|
|
231
|
+
|
|
232
|
+
The `KNXMQTTGateway` connects to an existing MQTT broker or sets up an embedded one using `aedes`.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { KNXMQTTGateway } from "knx.ts/server";
|
|
236
|
+
|
|
237
|
+
const mqttGateway = new KNXMQTTGateway({
|
|
238
|
+
embeddedBroker: { port: 1883 }, // Or use brokerUrl: "mqtt://your-broker:1883"
|
|
239
|
+
knxContext: router,
|
|
240
|
+
topicPrefix: "knx", // Default is "knx"
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await mqttGateway.start();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**MQTT API Structure:**
|
|
247
|
+
|
|
248
|
+
- **State Updates**: Whenever the KNX bus emits data, the MQTT gateway publishes the decoded payload to:
|
|
249
|
+
`[prefix]/state/[groupAddress]` (e.g. `knx/state/1/2/3`) with retained flag and JSON: `{ "decodedValue": ... }`.
|
|
250
|
+
|
|
251
|
+
- **Commands (Write / Read / Config DPT)**: Send JSON payloads to the following command topics.
|
|
252
|
+
- Send Write Command: `[prefix]/command/write/[groupAddress]`
|
|
253
|
+
- Payload: `{ "value": 22.5, "dpt": "9.001" }` or just the raw value if DPT is globally cached.
|
|
254
|
+
- Send Read Command: `[prefix]/command/read/[groupAddress]`
|
|
255
|
+
- Configure DPT: `[prefix]/command/config_dpt/[groupAddress]`
|
|
256
|
+
- Payload: `{ "dpt": "9.001" }`
|
|
257
|
+
|
|
147
258
|
## ๐ Logging
|
|
148
259
|
|
|
149
260
|
The library uses a single global logger based on [Pino](https://getpino.io/). You can configure it at the beginning of your application using `setupLogger`.
|
|
@@ -151,13 +262,13 @@ The library uses a single global logger based on [Pino](https://getpino.io/). Yo
|
|
|
151
262
|
This is important because you do not need to instantiate Pino yourself; the internal `knxLogger` manages its state to avoid the performance overhead of multiple instances.
|
|
152
263
|
|
|
153
264
|
```typescript
|
|
154
|
-
import { setupLogger, knxLogger } from
|
|
265
|
+
import { setupLogger, knxLogger } from "knx.ts";
|
|
155
266
|
|
|
156
267
|
// Configure the global logger
|
|
157
268
|
setupLogger({
|
|
158
|
-
level:
|
|
269
|
+
level: "debug", // e.g. 'info', 'warn', 'error', 'debug'
|
|
159
270
|
logToFile: true,
|
|
160
|
-
logDir:
|
|
271
|
+
logDir: "./logs",
|
|
161
272
|
});
|
|
162
273
|
|
|
163
274
|
// You can also use the global logger in your own application
|
|
@@ -174,13 +285,15 @@ The library is event-driven. Depending on the class you use, different events ar
|
|
|
174
285
|
|
|
175
286
|
All connection classes (`KNXnetIPServer`, `KNXTunneling`, `KNXUSBConnection`, `TPUARTConnection`) inherit from `KNXService` and emit the following standard events:
|
|
176
287
|
|
|
177
|
-
| Event
|
|
178
|
-
|
|
179
|
-
| `connected`
|
|
180
|
-
| `disconnected`
|
|
181
|
-
| `error`
|
|
182
|
-
| `indication`
|
|
183
|
-
| `raw_indication
|
|
288
|
+
| Event | Description | Callback Arguments |
|
|
289
|
+
| ---------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
290
|
+
| `connected` | Connection established and hardware/socket ready. | `void` (Server/USB/TPUART) / `{ channelId }` (Tunnel) |
|
|
291
|
+
| `disconnected` | Connection lost or explicitly closed. | `void` |
|
|
292
|
+
| `error` | A fatal error occurred during operation. | `err: Error` |
|
|
293
|
+
| `indication` | Any incoming standard KNX telegram (cEMI / L_Data.ind). | `cemi: CEMIInstance` |
|
|
294
|
+
| `raw_indication` | The raw `Buffer` before parsing (EMI/cEMI/payload). | `data: Buffer` |
|
|
295
|
+
| `indication_emi` | Emitted when an older EMI1/EMI2-formatted message is received from legacy USB interfaces. | `emi: EMIInstance` |
|
|
296
|
+
| `send` | Emitted when a message is sent to the bus. | `data: CEMIInstance` |
|
|
184
297
|
|
|
185
298
|
### Class-Specific Events
|
|
186
299
|
|
|
@@ -210,18 +323,18 @@ Depending on the connection type, some classes emit additional specific events:
|
|
|
210
323
|
|
|
211
324
|
Because `Router` links multiple interfaces, it emits link-specific routing events instead of standard indications:
|
|
212
325
|
|
|
213
|
-
- `indication_link`: Emitted when a packet is routed through the bridge. Argument: `{ src: string, msg:
|
|
326
|
+
- `indication_link`: Emitted when a packet is routed through the bridge. Argument: `{ src: string, msg: CEMIInstance }`, where `src` is the class name of the source connection.
|
|
214
327
|
- `error`: Emitted when an underlying link fails. Argument: `{ link: KNXService, error: Error }`.
|
|
215
328
|
|
|
216
329
|
### Understanding the Telegram Object
|
|
217
330
|
|
|
218
331
|
The `cemi` or `emi` object (implementing `ServiceMessage`) contains all the information about the KNX telegram. Here are the most relevant properties:
|
|
219
332
|
|
|
220
|
-
| Property
|
|
221
|
-
|
|
222
|
-
| `sourceAddress`
|
|
223
|
-
| `destinationAddress`
|
|
224
|
-
| `TPDU.apdu.data`
|
|
333
|
+
| Property | Type | Description |
|
|
334
|
+
| ------------------------ | -------- | --------------------------------------------------------------- |
|
|
335
|
+
| `sourceAddress` | `string` | Physical address of the sender (e.g., `"1.1.5"`). |
|
|
336
|
+
| `destinationAddress` | `string` | Group address (e.g., `"1/1/1"`) or physical address. |
|
|
337
|
+
| `TPDU.apdu.data` | `Buffer` | The raw payload data. |
|
|
225
338
|
| `TPDU.apdu.apci.command` | `string` | Command type (`A_GroupValue_Write`, `A_GroupValue_Read`, etc.). |
|
|
226
339
|
|
|
227
340
|
#### Handling Data Payloads
|
|
@@ -240,23 +353,23 @@ The library provides static utilities to handle conversion of KNX Data Point Typ
|
|
|
240
353
|
Use `KnxDataDecode` to transform raw cEMI data into readable values. There are several methods with the `asDpt` prefix for specific cases; `decodeThis` is convenient if you do not want to deal with those directly:
|
|
241
354
|
|
|
242
355
|
```typescript
|
|
243
|
-
import { KnxDataDecode } from
|
|
356
|
+
import { KnxDataDecode } from "knx.ts";
|
|
244
357
|
|
|
245
|
-
server.on(
|
|
358
|
+
server.on("1/1/1", (cemi) => {
|
|
246
359
|
// Decode as DPT 1 (Boolean)
|
|
247
360
|
const value = KnxDataDecode.decodeThis(1, cemi.TPDU.apdu.data);
|
|
248
|
-
console.log(
|
|
361
|
+
console.log("Decoded value:", value); // true or false
|
|
249
362
|
|
|
250
363
|
// Decode only as DPT 1 (Boolean)
|
|
251
364
|
const value1 = KnxDataDecode.asDpt1(cemi.TPDU.apdu.data);
|
|
252
365
|
|
|
253
366
|
// Decode as DPT 9 (2-byte float, e.g. Temperature)
|
|
254
367
|
const temp = KnxDataDecode.decodeThis(9, cemi.TPDU.apdu.data);
|
|
255
|
-
console.log(
|
|
368
|
+
console.log("Temperature:", temp, "ยฐC");
|
|
256
369
|
|
|
257
370
|
// The first parameter also accepts strings with the standard DPT numbering
|
|
258
|
-
const temp1 = KnxDataDecode.decodeThis("9", cemi.TPDU.apdu.data)
|
|
259
|
-
const percentage = KnxDataDecode.decodeThis("5.001", cemi.TPDU.apdu.data)
|
|
371
|
+
const temp1 = KnxDataDecode.decodeThis("9", cemi.TPDU.apdu.data);
|
|
372
|
+
const percentage = KnxDataDecode.decodeThis("5.001", cemi.TPDU.apdu.data);
|
|
260
373
|
});
|
|
261
374
|
```
|
|
262
375
|
|
|
@@ -265,7 +378,7 @@ server.on('1/1/1', (cemi) => {
|
|
|
265
378
|
Use `KnxDataEncoder` with the `encodeThis` method to prepare buffers for KNX telegrams; the second parameter is always an object. There are several `encodeDpt`-prefixed methods for specific cases, but `encodeThis` is convenient if you do not want to deal with those directly:
|
|
266
379
|
|
|
267
380
|
```typescript
|
|
268
|
-
import { KnxDataEncoder } from
|
|
381
|
+
import { KnxDataEncoder } from "knx.ts";
|
|
269
382
|
|
|
270
383
|
// Encode a Boolean (DPT 1)
|
|
271
384
|
const buf1 = KnxDataEncoder.encodeThis(1, { value: true });
|
|
@@ -305,7 +418,7 @@ In the actual API of this project, you typically build the **APDU** and **TPDU**
|
|
|
305
418
|
Defines the command (**APCI**) and the message data.
|
|
306
419
|
|
|
307
420
|
```typescript
|
|
308
|
-
import { APDU, APCI, APCIEnum } from
|
|
421
|
+
import { APDU, APCI, APCIEnum } from "knx.ts";
|
|
309
422
|
|
|
310
423
|
const apci = new APCI(APCIEnum.A_GroupValue_Write_Protocol_Data_Unit);
|
|
311
424
|
const apdu = new APDU(undefined, apci, Buffer.from([0x01]), true);
|
|
@@ -316,13 +429,9 @@ const apdu = new APDU(undefined, apci, Buffer.from([0x01]), true);
|
|
|
316
429
|
Wraps the APDU and defines the transport type.
|
|
317
430
|
|
|
318
431
|
```typescript
|
|
319
|
-
import { TPDU, TPCI, TPCIType } from
|
|
432
|
+
import { TPDU, TPCI, TPCIType } from "knx.ts";
|
|
320
433
|
|
|
321
|
-
const tpdu = new TPDU(
|
|
322
|
-
new TPCI(TPCIType.T_DATA_GROUP_PDU),
|
|
323
|
-
apdu,
|
|
324
|
-
apdu.data,
|
|
325
|
-
);
|
|
434
|
+
const tpdu = new TPDU(new TPCI(TPCIType.T_DATA_GROUP_PDU), apdu, apdu.data);
|
|
326
435
|
```
|
|
327
436
|
|
|
328
437
|
#### 3. cEMI `L_Data.req`
|
|
@@ -330,13 +439,7 @@ const tpdu = new TPDU(
|
|
|
330
439
|
In this library you do not build a generic `new CEMI()` for this case. You must instantiate the concrete cEMI service and pass its control fields, addresses, and `TPDU`.
|
|
331
440
|
|
|
332
441
|
```typescript
|
|
333
|
-
import {
|
|
334
|
-
AddressType,
|
|
335
|
-
CEMI,
|
|
336
|
-
ControlField,
|
|
337
|
-
ExtendedControlField,
|
|
338
|
-
Priority,
|
|
339
|
-
} from 'knx.ts';
|
|
442
|
+
import { AddressType, CEMI, ControlField, ExtendedControlField, Priority } from "knx.ts";
|
|
340
443
|
|
|
341
444
|
const controlField1 = new ControlField();
|
|
342
445
|
controlField1.frameType = true;
|
|
@@ -346,14 +449,7 @@ const controlField2 = new ExtendedControlField();
|
|
|
346
449
|
controlField2.addressType = AddressType.GROUP;
|
|
347
450
|
controlField2.hopCount = 6;
|
|
348
451
|
|
|
349
|
-
const cemi = new CEMI.DataLinkLayerCEMI["L_Data.req"](
|
|
350
|
-
null,
|
|
351
|
-
controlField1,
|
|
352
|
-
controlField2,
|
|
353
|
-
"1.1.1",
|
|
354
|
-
"1/1/1",
|
|
355
|
-
tpdu,
|
|
356
|
-
);
|
|
452
|
+
const cemi = new CEMI.DataLinkLayerCEMI["L_Data.req"](null, controlField1, controlField2, "1.1.1", "1/1/1", tpdu);
|
|
357
453
|
```
|
|
358
454
|
|
|
359
455
|
#### 4. Additional Information (optional)
|
|
@@ -361,10 +457,7 @@ const cemi = new CEMI.DataLinkLayerCEMI["L_Data.req"](
|
|
|
361
457
|
`AdditionalInformationField` is not filled by assigning `type` and `data` manually. You must create instances of the concrete types defined in `KNXAddInfoTypes` and add them to the field.
|
|
362
458
|
|
|
363
459
|
```typescript
|
|
364
|
-
import {
|
|
365
|
-
AdditionalInformationField,
|
|
366
|
-
ManufacturerSpecificData,
|
|
367
|
-
} from 'knx.ts';
|
|
460
|
+
import { AdditionalInformationField, ManufacturerSpecificData } from "knx.ts";
|
|
368
461
|
|
|
369
462
|
const addInfo = new AdditionalInformationField();
|
|
370
463
|
const manufacturerInfo = new ManufacturerSpecificData();
|
|
@@ -376,17 +469,17 @@ cemi.additionalInfo = addInfo;
|
|
|
376
469
|
|
|
377
470
|
#### 5. EMI (External Message Interface)
|
|
378
471
|
|
|
379
|
-
`EMI` is also not used as a generic instance with `new EMI()`. The class acts as a service container and parser (`EMI.fromBuffer(...)`). In connections such as `KNXUSBConnection`, the library automatically converts a cEMI `
|
|
472
|
+
`EMI` is also not used as a generic instance with `new EMI()`. The class acts as a service container and parser (`EMI.fromBuffer(...)`). In connections such as `KNXUSBConnection`, the library automatically converts a cEMI `CEMIInstance` to EMI when needed.
|
|
380
473
|
|
|
381
474
|
### Final Assembly and Sending Example
|
|
382
475
|
|
|
383
|
-
Once the structure is built, you can send it directly as a `
|
|
476
|
+
Once the structure is built, you can send it directly as a `CEMIInstance`, or serialize it with `toBuffer()` if you really need the raw buffer.
|
|
384
477
|
|
|
385
478
|
```typescript
|
|
386
|
-
import { KNXTunneling } from
|
|
479
|
+
import { KNXTunneling } from "knx.ts";
|
|
387
480
|
|
|
388
481
|
const tunnel = new KNXTunneling({
|
|
389
|
-
ip:
|
|
482
|
+
ip: "192.168.1.10",
|
|
390
483
|
port: 3671,
|
|
391
484
|
});
|
|
392
485
|
|
|
@@ -421,4 +514,4 @@ npm run test:connection
|
|
|
421
514
|
|
|
422
515
|
## โ๏ธ License
|
|
423
516
|
|
|
424
|
-
This project is licensed under the
|
|
517
|
+
This project is licensed under the MIT License.
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { KNXnetIPServer } from "../../connection/KNXnetIPServer";
|
|
2
1
|
import { KNXService } from "../../connection/KNXService";
|
|
3
|
-
import { Router } from "../../connection/Router";
|
|
4
2
|
export interface MQTTGatewayOptions {
|
|
5
|
-
knxContext:
|
|
3
|
+
knxContext: KNXService;
|
|
6
4
|
embeddedBroker?: {
|
|
7
5
|
port: number;
|
|
8
6
|
host?: string;
|
|
@@ -16,3 +14,28 @@ export interface WebSocketGatewayOptions {
|
|
|
16
14
|
port: number;
|
|
17
15
|
knxContext: KNXService;
|
|
18
16
|
}
|
|
17
|
+
export interface WSClientPayload {
|
|
18
|
+
action: "read" | "query" | "write" | "config_dpt" | "subscribe" | "unsubscribe";
|
|
19
|
+
groupAddress?: string;
|
|
20
|
+
dpt?: string | number;
|
|
21
|
+
value?: any;
|
|
22
|
+
onlyLatest?: boolean;
|
|
23
|
+
startDate?: string | Date;
|
|
24
|
+
endDate?: string | Date;
|
|
25
|
+
}
|
|
26
|
+
export interface WSServerPayload {
|
|
27
|
+
action: "connected" | "error" | "event" | "config_dpt_ack" | "write_ack" | "subscribe_ack" | "unsubscribe_ack" | "read_result" | "query_result";
|
|
28
|
+
message?: string;
|
|
29
|
+
groupAddress?: string;
|
|
30
|
+
dpt?: string | number;
|
|
31
|
+
decodedValue?: any;
|
|
32
|
+
data?: any;
|
|
33
|
+
results?: any[];
|
|
34
|
+
}
|
|
35
|
+
export interface MQTTCommandPayload {
|
|
36
|
+
value?: any;
|
|
37
|
+
dpt?: string | number;
|
|
38
|
+
}
|
|
39
|
+
export interface MQTTStatePayload {
|
|
40
|
+
decodedValue: any;
|
|
41
|
+
}
|
|
@@ -12,6 +12,8 @@ export declare abstract class KNXService<TOptions extends AllConnectionOptions =
|
|
|
12
12
|
protected _transport: "UDP" | "TCP";
|
|
13
13
|
protected logger: Logger;
|
|
14
14
|
individualAddress: string;
|
|
15
|
+
isCacheDelegated: boolean;
|
|
16
|
+
isEventsDelegated: boolean;
|
|
15
17
|
constructor(options?: TOptions);
|
|
16
18
|
/**
|
|
17
19
|
* Start the connection
|
|
@@ -12,6 +12,7 @@ const KNXnetIPStructures_1 = require("../core/KNXnetIPStructures");
|
|
|
12
12
|
const KNXnetIPEnum_1 = require("../core/enum/KNXnetIPEnum");
|
|
13
13
|
const CEMI_1 = require("../core/CEMI");
|
|
14
14
|
const KNXHelper_1 = require("../utils/KNXHelper");
|
|
15
|
+
const GroupAddressCache_1 = require("../core/cache/GroupAddressCache");
|
|
15
16
|
/**
|
|
16
17
|
* Handles KNXnet/IP Tunneling connections for point-to-point communication with a KNX gateway.
|
|
17
18
|
* This class manages the connection state, sequence numbering for reliable delivery,
|
|
@@ -172,6 +173,31 @@ class KNXTunneling extends KNXService_1.KNXService {
|
|
|
172
173
|
if (this.msgQueue.length >= this.MAX_QUEUE_SIZE) {
|
|
173
174
|
throw new Error("Outgoing queue full");
|
|
174
175
|
}
|
|
176
|
+
let cemiObj = undefined;
|
|
177
|
+
if (Buffer.isBuffer(cemi)) {
|
|
178
|
+
try {
|
|
179
|
+
cemiObj = CEMI_1.CEMI.fromBuffer(cemi);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
/* empty */
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
cemiObj = cemi;
|
|
187
|
+
}
|
|
188
|
+
if (cemiObj && "destinationAddress" in cemiObj && "sourceAddress" in cemiObj) {
|
|
189
|
+
if (!this.isCacheDelegated) {
|
|
190
|
+
try {
|
|
191
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().processCEMI(cemiObj);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* empty */
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!this.isEventsDelegated && cemiObj.destinationAddress) {
|
|
198
|
+
this.emit(cemiObj.destinationAddress, cemiObj);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
175
201
|
this.emit("send", cemi);
|
|
176
202
|
const cemiBuffer = Buffer.isBuffer(cemi) ? cemi : cemi.toBuffer();
|
|
177
203
|
const isDeviceMgmt = this.options.connectionType === KNXnetIPEnum_1.ConnectionType.DEVICE_MGMT_CONNECTION;
|
|
@@ -397,13 +423,22 @@ class KNXTunneling extends KNXService_1.KNXService {
|
|
|
397
423
|
const data = body.subarray(len);
|
|
398
424
|
const cemi = CEMI_1.CEMI.fromBuffer(data);
|
|
399
425
|
this.emit("indication", cemi);
|
|
426
|
+
if (!this.isCacheDelegated && "destinationAddress" in cemi && "sourceAddress" in cemi) {
|
|
427
|
+
try {
|
|
428
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().processCEMI(cemi);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
/* empty */
|
|
432
|
+
}
|
|
433
|
+
}
|
|
400
434
|
if (!("destinationAddress" in cemi))
|
|
401
435
|
return;
|
|
402
|
-
this.
|
|
436
|
+
if (!this.isEventsDelegated) {
|
|
437
|
+
this.emit(cemi.destinationAddress, cemi);
|
|
438
|
+
}
|
|
403
439
|
this.emit("raw_indication", data);
|
|
404
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
405
440
|
}
|
|
406
|
-
catch
|
|
441
|
+
catch {
|
|
407
442
|
/* empty */
|
|
408
443
|
}
|
|
409
444
|
}
|
|
@@ -38,6 +38,7 @@ const hid = __importStar(require("node-hid"));
|
|
|
38
38
|
const KNXService_1 = require("./KNXService");
|
|
39
39
|
const CEMIAdapter_1 = require("../utils/CEMIAdapter");
|
|
40
40
|
const CEMI_1 = require("../core/CEMI");
|
|
41
|
+
const GroupAddressCache_1 = require("../core/cache/GroupAddressCache");
|
|
41
42
|
class KNXUSBConnection extends KNXService_1.KNXService {
|
|
42
43
|
device = null;
|
|
43
44
|
isConnected = false;
|
|
@@ -239,6 +240,31 @@ class KNXUSBConnection extends KNXService_1.KNXService {
|
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
242
|
if (frame) {
|
|
243
|
+
let cemiObj = undefined;
|
|
244
|
+
try {
|
|
245
|
+
if (this.supportedEmiType === 0x03) {
|
|
246
|
+
cemiObj = Buffer.isBuffer(data) ? CEMI_1.CEMI.fromBuffer(data) : data;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
cemiObj = Buffer.isBuffer(data) ? CEMIAdapter_1.CEMIAdapter.emiToCemi(data) || undefined : undefined;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
/* empty */
|
|
254
|
+
}
|
|
255
|
+
if (cemiObj && "destinationAddress" in cemiObj && "sourceAddress" in cemiObj) {
|
|
256
|
+
if (!this.isCacheDelegated) {
|
|
257
|
+
try {
|
|
258
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().processCEMI(cemiObj);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
/* empty */
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!this.isEventsDelegated && cemiObj.destinationAddress) {
|
|
265
|
+
this.emit(cemiObj.destinationAddress, cemiObj);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
242
268
|
this.emit("send", frame);
|
|
243
269
|
await this.sendUSBTransfer(0x01, this.supportedEmiType, frame);
|
|
244
270
|
}
|
|
@@ -316,6 +342,17 @@ class KNXUSBConnection extends KNXService_1.KNXService {
|
|
|
316
342
|
const cemiMsg = CEMI_1.CEMI.fromBuffer(payload);
|
|
317
343
|
if (cemiMsg) {
|
|
318
344
|
this.emit("indication", cemiMsg);
|
|
345
|
+
if (!this.isCacheDelegated && "destinationAddress" in cemiMsg && "sourceAddress" in cemiMsg) {
|
|
346
|
+
try {
|
|
347
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().processCEMI(cemiMsg);
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
/* empty */
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (!this.isEventsDelegated && "destinationAddress" in cemiMsg) {
|
|
354
|
+
this.emit(cemiMsg.destinationAddress, cemiMsg);
|
|
355
|
+
}
|
|
319
356
|
this.emit("raw_indication", payload);
|
|
320
357
|
try {
|
|
321
358
|
const emiMsg = CEMIAdapter_1.CEMIAdapter.cemiToEmi(cemiMsg);
|
|
@@ -338,6 +375,17 @@ class KNXUSBConnection extends KNXService_1.KNXService {
|
|
|
338
375
|
const cemiMsg = CEMIAdapter_1.CEMIAdapter.emiToCemi(payload);
|
|
339
376
|
if (cemiMsg) {
|
|
340
377
|
this.emit("indication", cemiMsg);
|
|
378
|
+
if (!this.isCacheDelegated && "destinationAddress" in cemiMsg && "sourceAddress" in cemiMsg) {
|
|
379
|
+
try {
|
|
380
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().processCEMI(cemiMsg);
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
/* empty */
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (!this.isEventsDelegated && "destinationAddress" in cemiMsg) {
|
|
387
|
+
this.emit(cemiMsg.destinationAddress, cemiMsg);
|
|
388
|
+
}
|
|
341
389
|
this.emit("raw_indication", payload);
|
|
342
390
|
}
|
|
343
391
|
}
|
|
@@ -20,8 +20,6 @@ export declare class KNXnetIPServer extends KNXService<KNXnetIPServerOptions> {
|
|
|
20
20
|
private decrementInterval;
|
|
21
21
|
private serverIAInt;
|
|
22
22
|
private _tunnelConnections;
|
|
23
|
-
isCacheDelegated: boolean;
|
|
24
|
-
isEventsDelegated: boolean;
|
|
25
23
|
private readonly MAX_QUEUE_SIZE;
|
|
26
24
|
private readonly BUSY_THRESHOLD;
|
|
27
25
|
private readonly HEARTBEAT_TIMEOUT;
|
|
@@ -37,8 +37,6 @@ class KNXnetIPServer extends KNXService_1.KNXService {
|
|
|
37
37
|
// [MEJORA] Almacenamos la IA en formato entero para el filtro anti-eco rรกpido
|
|
38
38
|
serverIAInt;
|
|
39
39
|
_tunnelConnections = new Map();
|
|
40
|
-
isCacheDelegated = false;
|
|
41
|
-
isEventsDelegated = false;
|
|
42
40
|
MAX_QUEUE_SIZE = 100;
|
|
43
41
|
BUSY_THRESHOLD = 15;
|
|
44
42
|
HEARTBEAT_TIMEOUT = KNXnetIPEnum_1.KNXTimeoutConstants.CONNECTION_ALIVE_TIME * 1000;
|
|
@@ -51,8 +51,6 @@ class Router extends events_1.EventEmitter {
|
|
|
51
51
|
if (options.knxNetIpServer) {
|
|
52
52
|
options.knxNetIpServer.individualAddress = this.routerAddress;
|
|
53
53
|
const ipServer = new KNXnetIPServer_1.KNXnetIPServer(options.knxNetIpServer);
|
|
54
|
-
ipServer.isCacheDelegated = true;
|
|
55
|
-
ipServer.isEventsDelegated = true;
|
|
56
54
|
this.registerLink(`IP KNXnet/IP Server: ${ipServer.options.localIp}:${ipServer.options.port}`, ipServer);
|
|
57
55
|
}
|
|
58
56
|
if (options.tpuart) {
|
|
@@ -77,6 +75,8 @@ class Router extends events_1.EventEmitter {
|
|
|
77
75
|
registerLink(key, link) {
|
|
78
76
|
if (this.links.has(key))
|
|
79
77
|
return;
|
|
78
|
+
link.isCacheDelegated = true;
|
|
79
|
+
link.isEventsDelegated = true;
|
|
80
80
|
this.links.set(key, link);
|
|
81
81
|
this.logger.info(`Link registered: ${key}`);
|
|
82
82
|
link.on("indication", (cemi) => {
|
|
@@ -6,6 +6,7 @@ const KNXService_1 = require("./KNXService");
|
|
|
6
6
|
const CEMIAdapter_1 = require("../utils/CEMIAdapter");
|
|
7
7
|
const KNXHelper_1 = require("../utils/KNXHelper");
|
|
8
8
|
const CEMI_1 = require("../core/CEMI");
|
|
9
|
+
const GroupAddressCache_1 = require("../core/cache/GroupAddressCache");
|
|
9
10
|
const UART_SERVICES = {
|
|
10
11
|
RESET_REQ: 0x01,
|
|
11
12
|
RESET_IND: 0x03,
|
|
@@ -94,6 +95,17 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
94
95
|
const cemi = CEMIAdapter_1.CEMIAdapter.emiToCemi(emiBuffer);
|
|
95
96
|
if (cemi) {
|
|
96
97
|
this.emit("indication", cemi);
|
|
98
|
+
if (!this.isCacheDelegated && "destinationAddress" in cemi && "sourceAddress" in cemi) {
|
|
99
|
+
try {
|
|
100
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().processCEMI(cemi);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* empty */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!this.isEventsDelegated && "destinationAddress" in cemi) {
|
|
107
|
+
this.emit(cemi.destinationAddress, cemi);
|
|
108
|
+
}
|
|
97
109
|
this.emit("raw_indication", cemi.toBuffer());
|
|
98
110
|
}
|
|
99
111
|
}
|
|
@@ -228,6 +240,31 @@ class TPUARTConnection extends KNXService_1.KNXService {
|
|
|
228
240
|
async send(data) {
|
|
229
241
|
if (this.connectionState < TPUARTState.ONLINE)
|
|
230
242
|
throw new Error("TPUART offline");
|
|
243
|
+
let cemiObj = undefined;
|
|
244
|
+
if (Buffer.isBuffer(data)) {
|
|
245
|
+
try {
|
|
246
|
+
cemiObj = CEMI_1.CEMI.fromBuffer(data);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
/* empty */
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
cemiObj = data;
|
|
254
|
+
}
|
|
255
|
+
if (cemiObj && "destinationAddress" in cemiObj && "sourceAddress" in cemiObj) {
|
|
256
|
+
if (!this.isCacheDelegated) {
|
|
257
|
+
try {
|
|
258
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().processCEMI(cemiObj);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
/* empty */
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!this.isEventsDelegated && cemiObj.destinationAddress) {
|
|
265
|
+
this.emit(cemiObj.destinationAddress, cemiObj);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
231
268
|
const frame = Buffer.isBuffer(data) ? data : CEMIAdapter_1.CEMIAdapter.cemiToEmi(data)?.toBuffer();
|
|
232
269
|
if (!frame)
|
|
233
270
|
throw new Error("Invalid data");
|
package/dist/index.d.ts
CHANGED
|
@@ -22,3 +22,8 @@ export * from "./core/layers/data/NPDU";
|
|
|
22
22
|
export * from "./core/layers/data/TPDU";
|
|
23
23
|
export * from "./core/layers/interfaces/APCI";
|
|
24
24
|
export * from "./core/layers/interfaces/TPCI";
|
|
25
|
+
export * from "./core/cache/GroupAddressCache";
|
|
26
|
+
export * from "./@types/interfaces/servers";
|
|
27
|
+
export type { CEMIInstance } from "./core/CEMI";
|
|
28
|
+
export type { EMIInstance } from "./core/EMI";
|
|
29
|
+
export type { ServiceMessage } from "./@types/interfaces/ServiceMessage";
|
package/dist/index.js
CHANGED
|
@@ -51,3 +51,5 @@ __exportStar(require("./core/layers/data/NPDU"), exports);
|
|
|
51
51
|
__exportStar(require("./core/layers/data/TPDU"), exports);
|
|
52
52
|
__exportStar(require("./core/layers/interfaces/APCI"), exports);
|
|
53
53
|
__exportStar(require("./core/layers/interfaces/TPCI"), exports);
|
|
54
|
+
__exportStar(require("./core/cache/GroupAddressCache"), exports);
|
|
55
|
+
__exportStar(require("./@types/interfaces/servers"), exports);
|
|
@@ -47,6 +47,8 @@ class KNXMQTTGateway {
|
|
|
47
47
|
constructor(options) {
|
|
48
48
|
this.options = options;
|
|
49
49
|
this.topicPrefix = options.topicPrefix || "knx";
|
|
50
|
+
// Enable the cache singleton
|
|
51
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().setEnabled(true);
|
|
50
52
|
}
|
|
51
53
|
async start() {
|
|
52
54
|
// 1. Setup embedded broker if requested
|
|
@@ -124,6 +126,8 @@ class KNXMQTTGateway {
|
|
|
124
126
|
// simple value fallback
|
|
125
127
|
payload = { value: message.toString() };
|
|
126
128
|
}
|
|
129
|
+
if (!("value" in payload) || !("dpt" in payload))
|
|
130
|
+
return;
|
|
127
131
|
const { value, dpt } = payload;
|
|
128
132
|
if (action === "config_dpt" && dpt) {
|
|
129
133
|
GroupAddressCache_1.GroupAddressCache.getInstance().setAddressDPT(groupAddress, dpt);
|
|
@@ -10,6 +10,8 @@ class KNXWebSocketGateway {
|
|
|
10
10
|
activeSubscriptions = new Set();
|
|
11
11
|
constructor(options) {
|
|
12
12
|
this.options = options;
|
|
13
|
+
// Enable the cache singleton
|
|
14
|
+
GroupAddressCache_1.GroupAddressCache.getInstance().setEnabled(true);
|
|
13
15
|
}
|
|
14
16
|
start() {
|
|
15
17
|
this.wss = new ws_1.WebSocketServer({ port: this.options.port });
|
|
@@ -46,6 +48,8 @@ class KNXWebSocketGateway {
|
|
|
46
48
|
});
|
|
47
49
|
}
|
|
48
50
|
handleClientMessage(ws, payload) {
|
|
51
|
+
if (!("action" in payload) || !("groupAddress" in payload) || !("value" in payload) || !("dpt" in payload))
|
|
52
|
+
return;
|
|
49
53
|
const { action, groupAddress, value, dpt } = payload;
|
|
50
54
|
if (action === "config_dpt" && groupAddress && dpt) {
|
|
51
55
|
GroupAddressCache_1.GroupAddressCache.getInstance().setAddressDPT(groupAddress, dpt);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knx.ts",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"typescript"
|
|
45
45
|
],
|
|
46
46
|
"author": "Arnold Steven Beleรฑo Zuletta (Wesxt)",
|
|
47
|
-
"license": "
|
|
47
|
+
"license": "MIT",
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@eslint/js": "^10.0.1",
|
|
50
50
|
"@types/jest": "^30.0.0",
|