Control your KNX intallation via Node.js. It includes also KNX IP Secure and Data Secure!
This is the official engine of Node-Red's node node-red-contrib-knx-ultimate
Many of you asked for a node.js release of that engine, so i decided to create this package.
If you enjoy my work developing this package, do today a kind thing for someone too. This will be the reward for my work.
npm i knxultimate
Authorized KNX logo by KNX Association*
| Technology | Supported |
|---|---|
| KNX IP Full Stack | |
| KNX IP Secure Full stack | |
| KNX TP Serial FT1.2/KBERRY/BAOS | |
| KNX TP Secure Full stack |
Please subscribe to my channel, to learn how to use it
These are the properties you can pass to KNXClient (see examples for full usage):
| Property | Applies to | Description |
|---|---|---|
hostProtocol (string) |
all | One of: "TunnelUDP" (plain tunnelling via UDP), "Multicast" (plain routing via multicast 224.0.23.12), "TunnelTCP" (KNX/IP Secure tunnelling over TCP), "SerialFT12" (direct TP/FT1.2 serial interface such as /dev/ttyAMA0). |
ipAddr (string) |
all | KNX/IP peer address. Use "224.0.23.12" for routing (multicast), or the interface/router IP for tunnelling. |
ipPort (number/string) |
all | KNX/IP port. Default 3671. |
physAddr (string) Optional |
all | Source IA on bus (e.g. "1.1.200"). Multicast: required. TunnelUDP: used as cEMI source. TunnelTCP (secure): ignored as bus source — the gateway assigns the tunnel IA; Data Secure uses the interface IA from ETS for authentication. |
loglevel (string) |
all | One of: disable, error, warn, info, debug, trace. |
localIPAddress (string) |
all | Optional. Binds the local UDP/TCP socket to a specific local interface IP. Useful with multiple NICs. |
interface (string) |
all | Optional. Local interface name to select the NIC (alternative to localIPAddress). |
serialInterface (object) |
SerialFT12 | Serial port settings used when hostProtocol === 'SerialFT12'. Supports path (default /dev/ttyAMA0), baudRate (default 19200), dataBits, stopBits, parity ('even' by default), rtscts, dtr, timeoutMs, lock (forwarded to serialport), and isKBERRY (default true, enable Weinzierl KBerry/BAOS initialisation). |
KNXQueueSendIntervalMilliseconds (number) |
all | Optional. Inter‑telegram delay in ms. Default ~25ms. Don’t go below 20ms. |
suppress_ack_ldatareq (bool) |
tunnelling (UDP/TCP) | Optional. Avoid requesting/handling L_DATA_REQ bus ACK in tunnelling. Leave false unless your interface needs it. |
theGatewayIsKNXVirtual (bool) |
tunnelling | Optional. Special handling for ETS KNX Virtual (adds localIPAddress to tunnel endpoint). Default false. |
isSecureKNXEnabled (bool) |
IP secure, serial Data Secure | Enable KNX/IP Secure. With TunnelTCP: session handshake + Secure Wrapper. With Multicast: secure routing (Secure Wrapper, timer sync). With SerialFT12: enable KNX Data Secure on TP (group keys only, no IP wrapper). |
secureTunnelConfig (object) |
secure tunnelling, routing, serial | KNX Secure configuration. For TunnelTCP/routing it drives both tunnel auth and Data Secure; for SerialFT12 only the keyring fields (knxkeys_file_path, knxkeys_password) are used to load group keys for Data Secure on TP. |
secureRoutingWaitForTimer (bool) |
secure routing (multicast) | Optional. Wait for first timer sync (0955/0950) before sending. Default true. |
Choose hostProtocol: 'SerialFT12' to connect directly to KNX TP via a serial FT1.2 interface. This mode is primarily designed and tested for Weinzierl KBerry / BAOS modules running in Link Layer (cEMI) mode over FT1.2. Configure the serial line via the serialInterface option; by default /dev/ttyAMA0, 19200 baud, 8E1, DTR on, RTS/CTS off are used.
This transport supports KNX Data Secure on TP: set isSecureKNXEnabled: true and provide secureTunnelConfig.knxkeys_file_path / secureTunnelConfig.knxkeys_password with your ETS keyring. Verified with Weinzierl KBerry / BAOS modules; leave serialInterface.isKBERRY at its default true to run their FT1.2 init sequence automatically.
List all available serial devices before connecting:
import KNXClient from 'knxultimate'
async function connectSerial() {
const ports = await KNXClient.listSerialInterfaces()
ports.forEach((p) => console.log('Serial port:', p.path))
const client = new KNXClient({
hostProtocol: 'SerialFT12',
serialInterface: {
path: '/dev/ttyAMA0',
baudRate: 19200,
dataBits: 8,
stopBits: 1,
parity: 'even',
// KBerry / BAOS helpers:
isKBERRY: true, // default
lock: false, // optional, useful on macOS
},
physAddr: '15.15.255', // source IA on the bus
loglevel: 'info',
// Optional: enable KNX Data Secure on TP using an ETS keyring
isSecureKNXEnabled: true,
secureTunnelConfig: {
knxkeys_file_path: '/path/to/Project.knxkeys',
knxkeys_password: 'your-ets-password',
// tunnel* fields are ignored in SerialFT12 mode (no IP tunnel auth); only group keys are used.
},
})
client.on('connected', () => console.log('Serial FT1.2 ready'))
client.on('indication', (packet) => console.log('Telegram:', packet))
client.Connect()
}Remember to configure the physical address (physAddr) that ETS assigns to your TP interface; the serial transport uses that IA on the bus. In KBerry mode (isKBERRY: true) the driver automatically:
- Sends an FT1.2 reset
- Switches the BAOS communication mode to Link Layer (
cEMI) - Enables indication sending
- Sets the Address Table length to 0 (no GA filter, all group telegrams are forwarded)
- If
isSecureKNXEnabled: trueand a.knxkeysfile is configured insecureTunnelConfig, loads group keys and applies KNX Data Secure on TP for those Group Addresses (cEMIL_Data.req/L_Data.indare encrypted/decrypted end‑to‑end). No IP Secure wrapper is used on the serial line; only the APDU is protected.
For each Datapoint, there is a sample on how to format the payload (telegram) to be passed.
For example, pass a true for datapoint "1.001", or { red: 125, green: 0, blue: 0 } for datapoint "232.600".
It support a massive number of Datapoints. Please run the examples/showDatapoints.ts file to view all datapoints in the output console.
Be aware, that the descriptions you'll see, are taken from Node-Red KNX-Ultimate node, so there is more code than you need here. Please take only the msg.payload part in consideration.
You should see something like this in the console window (the msg.payload is what you need to pass as payload):
| Method | Description |
|---|---|
| .Connect() | Connects to the KNX Gateway |
| .Disconnect() | Gracefully disconnects from the KNX Gateway |
| .write (GA, payload, datapoint) | Sends a WRITE telegram to the BUS. GA is the group address (for example "0/0/1"), payload is the value you want to send (for example true), datapoint is a string representing the datapoint (for example "5.001") |
| .writeRaw (GA, payload, datapoint) | Sends a WRITE telegram to the BUS. GA is the group address (for example "0/0/1"), payload is the buffer you want to send, datapoint is a string representing the datapoint (for example "5.001") |
| .respond (GA, payload, datapoint) | Sends a RESPONSE telegram to the BUS. GA is the group address (for example "0/0/1"), payload is the value you want to send (for example true), datapoint is a string representing the datapoint (for example "5.001") |
| .read (GA) | Sends a READ telegram to the BUS. GA is the group address (for example "0/0/1"). |
| . discover() | Sends a discover request on the KNX default multicast port and returns the results as an array. This is an async method. See the example in the examples folder |
| .getGatewayDescription() | Sends a gateway description request. It works after an established connection. The async results will be sent to the descriptionResponse event. There is an example in the examples folder named gatewaydescription.ts . |
| Property | Description |
|---|---|
| .isConnected() | Returns true if you the client is connected to the KNX Gateway Router/Interface, false if not connected. |
| .clearToSend | true if you can send a telegram, false if the client is still waiting for the last telegram's ACK or whenever the client cannot temporary send the telegram. In tunneling mode, you could also refer to the event KNXClientEvents.ackReceived, that is fired everytime a telegram has been succesfully acknowledge or not acknowledge. See the sample.js file. |
| .channelID | The actual Channel ID. Only defined after a successfull connection |
List of events raised by KNXultimate, in proper order. For the signatures, please see the examples folder.
| Event | Description |
|---|---|
| connecting | KNXUltimate is connecting to the KNX/IP Gateway. Please wait for the connected event to start sending KNX telegrams. |
| connected | KNXUltimate has successfully connected with the KNX/IP Gateway. |
| indication | KNXUltimate has received a KNX telegram, that's avaiable in te the datagram variable. Please see the examples. |
| ackReceived | Ack telegram from KNX/IP Gateway has been received. This confirms that the telegram sent by KNXUltimate has reached the KNX/IP Gateway successfully. |
| disconnected | The KNX connection has been disconnected. |
| close | The main KNXUltimate socket has been closed. |
| error | KNXUltimate has raised an error. The error description is provided as well. |
| descriptionResponse | Gather the getGatewayDescription responses. There is an example in the examples folder named gatewaydescription.ts . |
KNXUltimate logging is managed by Winston logger. In case you want to intercept library logs you can use our logStram exported from default entrypoint. Example:
import { logStream } from 'knxultimate'
logStream.on('data', (log) => {
// handle log
if(log.level === 'ERROR)
console.log(`${log.timestamp} ${log.message}`)
}) | Field | Description |
|---|---|
| timestamp | ISO formatted date (YYYY-MM-DD HH:mm:ss.SSS) |
| level | Log level in uppercase (ERROR, WARN, INFO, DEBUG) |
| label | Module name in uppercase (specified when creating logger) |
Below are short, progressively richer examples to connect in plain (non‑secure) KNX and listen or interact with the bus. Copy/paste inline; no extra files are created.
import KNXClient from 'knxultimate'
const client = new KNXClient({
hostProtocol: 'TunnelUDP',
ipAddr: '192.168.1.117', // your KNX/IP interface IP
ipPort: 3671,
loglevel: 'info',
})
client.on('connected', () => console.log('✓ Plain tunnelling connected'))
client.on('error', (e) => console.error('Error:', e.message))
client.on('disconnected', (reason) => console.log('Disconnected:', reason))
client.on('indication', (packet) => {
const cemi = packet?.cEMIMessage
if (!cemi) return
const src = cemi.srcAddress?.toString?.()
const dst = cemi.dstAddress?.toString?.()
const isWrite = cemi.npdu?.isGroupWrite
const isResp = cemi.npdu?.isGroupResponse
const raw: Buffer | undefined = cemi.npdu?.dataValue
console.log('indication', { src, dst, isWrite, isResp, raw: raw?.toString('hex') })
})
client.Connect()import KNXClient, { KNXClientEvents } from 'knxultimate'
import { dptlib } from 'knxultimate'
const client = new KNXClient({ hostProtocol: 'TunnelUDP', ipAddr: '192.168.1.117', ipPort: 3671 })
client.on(KNXClientEvents.indication, (packet) => {
const cemi = packet?.cEMIMessage
if (!cemi?.npdu) return
const dst = cemi.dstAddress?.toString?.()
const raw: Buffer | undefined = cemi.npdu?.dataValue
if (!dst || !raw) return
if (dst === '0/1/25') { // for example: a status GA known to be 1.001
const cfg = dptlib.resolve('1.001')
const value = dptlib.fromBuffer(raw, cfg)
console.log(`dst=${dst} ->`, value)
}
})
client.Connect()import KNXClient from 'knxultimate'
const client = new KNXClient({
hostProtocol: 'Multicast',
ipAddr: '224.0.23.12',
ipPort: 3671,
physAddr: '1.1.200', // set your device IA for routing
loglevel: 'info',
})
client.on('connected', () => console.log('✓ Plain multicast ready'))
client.on('error', (e) => console.error('Error:', e.message))
client.on('indication', (packet) => {
const cemi = packet?.cEMIMessage
if (!cemi?.npdu) return
const dst = cemi.dstAddress?.toString?.()
const raw: Buffer | undefined = cemi.npdu?.dataValue
console.log('routing ind', { dst, raw: raw?.toString('hex') })
})
client.Connect()import KNXClient from 'knxultimate'
import { dptlib } from 'knxultimate'
const client = new KNXClient({ hostProtocol: 'TunnelUDP', ipAddr: '192.168.1.117', ipPort: 3671 })
function waitForStatus(ga: string, timeoutMs = 3000): Promise<number> {
return new Promise((resolve, reject) => {
const t = setTimeout(() => { client.off('indication', onInd); reject(new Error('Timeout')) }, timeoutMs)
const onInd = (packet: any) => {
const cemi = packet?.cEMIMessage
if (!cemi || cemi.dstAddress?.toString?.() !== ga) return
const npdu = cemi.npdu
if (!(npdu?.isGroupResponse || npdu?.isGroupWrite)) return
const raw: Buffer = npdu?.dataValue ?? Buffer.alloc(1, 0)
const bit = (raw.readUInt8(0) ?? 0) & 0x01
clearTimeout(t)
client.off('indication', onInd)
resolve(bit)
}
client.on('indication', onInd)
})
}
async function main() {
client.Connect()
await new Promise<void>((res) => client.once('connected', () => res()))
// Write ON n
client.write('0/1/1', true, '1.001')
// Read status
client.read('0/1/25')
const val = await waitForStatus('0/1/25', 3000)
console.log('Status:', val ? 'ON' : 'OFF')
}
main().catch(console.error)Below are progressively richer examples to connect to a KNX/IP Secure gateway and listen for telegrams with decrypted payloads. These are meant to be copy‑pasted inline (no files are added under examples/). All snippets assume TypeScript/Node 18+.
Important notes
- The client emits the full datagram on
indication, and itscEMIMessageis already plain (decrypted) when keys are available in your ETS keyring. - Never commit your
.knxkeysfile. Keep its path/password in environment variables or local config. - Secure TCP tunnel auto‑select: if
secureTunnelConfig.tunnelInterfaceIndividualAddressis omitted or empty, the client tries all interfaces found in the ETS keyring until authentication and connect succeed, then proceeds and keeps the tunnel open. The chosen IA is exposed onclient._options.secureTunnelConfig.tunnelInterfaceIndividualAddressafter connect.
import KNXClient, { SecureConfig } from 'knxultimate'
// 1) Configure ETS keyring + the interface IA used in your project
const secureCfg: SecureConfig = {
// tunnelInterfaceIndividualAddress: '1.1.254', // Optional: omit to auto‑select a free tunnel
knxkeys_file_path: process.env.KNX_KEYS_PATH || '/path/to/Project.knxkeys',
knxkeys_password: process.env.KNX_KEYS_PASSWORD || 'your-ets-password',
}
// 2) Create a KNX/IP Secure TCP client
const client = new KNXClient({
hostProtocol: 'TunnelTCP',
ipAddr: '192.168.1.4',
ipPort: 3671,
isSecureKNXEnabled: true,
secureTunnelConfig: secureCfg,
loglevel: 'info',
})
client.on('connected', () => console.log('✓ Secure tunnel connected'))
client.on('error', (e) => console.error('Error:', e.message))
client.on('disconnected', (reason) => console.log('Disconnected:', reason))
// 3) Listen: cEMI payload is already decrypted when Data Secure is used
client.on('indication', (packet) => {
const cemi = packet?.cEMIMessage
if (!cemi) return
const dst = cemi.dstAddress?.toString?.()
const src = cemi.srcAddress?.toString?.()
const isWrite = cemi.npdu?.isGroupWrite
const isResp = cemi.npdu?.isGroupResponse
const raw: Buffer | undefined = cemi.npdu?.dataValue
console.log('indication', { src, dst, isWrite, isResp, raw: raw?.toString('hex') })
})
async function main() {
client.Connect()
await new Promise<void>((res) => client.once('connected', () => res()))
console.log('Listening… Press Ctrl+C to exit')
}
main().catch(console.error)When you do not have access to the ETS keyring you can still negotiate KNX/IP Secure by providing the tunnel IA and password directly. This gives you encrypted tunnelling, while Data Secure and secure multicast stay disabled. The user ID defaults to 2, which is the standard KNX Secure tunnel account.
import KNXClient, { SecureConfig } from 'knxultimate'
const tunnelPassword = process.env.KNX_TUNNEL_PASSWORD
if (!tunnelPassword) {
throw new Error('Set KNX_TUNNEL_PASSWORD with your secure tunnel password')
}
const secureCfg: SecureConfig = {
tunnelInterfaceIndividualAddress: '1.1.254',
tunnelUserPassword: tunnelPassword,
tunnelUserId: 2, // Replace with your tunnel user ID from ETS (number or numeric string)
}
const client = new KNXClient({
hostProtocol: 'TunnelTCP',
ipAddr: '192.168.1.4',
ipPort: 3671,
isSecureKNXEnabled: true,
secureTunnelConfig: secureCfg,
})
client.on('connected', () => console.log('✓ Secure tunnel connected (manual password)'))
client.on('error', (e) => console.error('Error:', e.message))
client.Connect()Note: ETS assigns a dedicated
tunnelUserIdto each secure tunnel. SetsecureTunnelConfig.tunnelUserId(number or numeric string) together with the password, otherwise the gateway replies withSecure Session Status = 1and the connection is closed.
- KNX IP Tunnelling Secure protects the TCP channel. The gateway authenticates a tunnel user by ID + password during the
Secure Session Authenticate (0x0953)step. Provide the password either from the keyring (secureTunnelConfig.knxkeys_*) or manually viasecureTunnelConfig.tunnelUserPassword; in both cases thetunnelUserIdmust match the ETS commissioning data. - KNX Data Secure protects group-address telegrams. It relies on group keys stored in the ETS keyring, so a
.knxkeysfile plus its password are mandatory whenever you need encrypted group communication. - Supported combinations
- Keyring only: set
knxkeys_file_path/knxkeys_passwordand omittunnelUserPassword. Both the tunnel password and the group keys are loaded from the keyring.
- Keyring only: set
- Manual tunnel password only: set both
tunnelUserPasswordandtunnelUserIdwithout a keyring. The IP channel is secured, but Data Secure stays disabled because no group keys are present.- Keyring + manual password: provide the keyring for Data Secure and override the tunnelling password with
tunnelUserPassword(useful when the ETS export does not include the tunnel password). Ensureknxkeys_*andtunnelUserPasswordare both configured.
- Keyring + manual password: provide the keyring for Data Secure and override the tunnelling password with
- To retrieve the tunnel user ID/password pair, open the secure tunnelling interface in ETS (or inspect the
.knxkeysentry). Default ETS IDs are typically small integers (e.g.2,3, …) but may differ per installation.
import KNXClient, { SecureConfig } from 'knxultimate'
import { dptlib } from 'knxultimate'
const secureCfg: SecureConfig = {
// tunnelInterfaceIndividualAddress: '1.1.254', // Optional (auto‑select if omitted)
knxkeys_file_path: process.env.KNX_KEYS_PATH || '/path/to/Project.knxkeys',
knxkeys_password: process.env.KNX_KEYS_PASSWORD || 'your-ets-password',
}
const client = new KNXClient({
hostProtocol: 'TunnelTCP',
ipAddr: '192.168.1.4',
ipPort: 3671,
isSecureKNXEnabled: true,
secureTunnelConfig: secureCfg,
loglevel: 'info',
})
client.on('indication', (packet) => {
const cemi = packet?.cEMIMessage
if (!cemi?.npdu) return
const dst = cemi.dstAddress?.toString?.()
const raw: Buffer | undefined = cemi.npdu?.dataValue
if (!dst || !raw) return
// Example: decode boolean status for GA 1/1/2 as DPT 1.001
if (dst === '1/1/2') {
const cfg = dptlib.resolve('1.001')
const value = dptlib.fromBuffer(raw, cfg)
console.log(`dst=${dst} ->`, value)
}
})
client.Connect()For routers supporting KNX Secure routing (multicast 224.0.23.12). The ETS keyring must include the Backbone key; the client will automatically use it to decrypt SecureWrapper frames and present decrypted cEMI payloads.
import KNXClient, { SecureConfig } from 'knxultimate'
const secureCfg: SecureConfig = {
knxkeys_file_path: process.env.KNX_KEYS_PATH || '/path/to/Project.knxkeys',
knxkeys_password: process.env.KNX_KEYS_PASSWORD || 'your-ets-password',
}
const client = new KNXClient({
hostProtocol: 'Multicast',
ipAddr: '224.0.23.12',
ipPort: 3671,
physAddr: '1.1.250', // your device IA used as source on bus
isSecureKNXEnabled: true,
secureTunnelConfig: secureCfg,
loglevel: 'info',
})
client.on('connected', () => console.log('✓ Secure multicast ready'))
client.on('error', (e) => console.error('Error:', e.message))
client.on('indication', (packet) => {
const cemi = packet?.cEMIMessage
if (!cemi?.npdu) return
const dst = cemi.dstAddress?.toString?.()
const raw: Buffer | undefined = cemi.npdu?.dataValue
console.log('routing ind', { dst, raw: raw?.toString('hex') })
})
client.Connect()import KNXClient, { SecureConfig } from 'knxultimate'
import { dptlib } from 'knxultimate'
const secureCfg: SecureConfig = {
tunnelInterfaceIndividualAddress: '1.1.254',
knxkeys_file_path: process.env.KNX_KEYS_PATH || '/path/to/Project.knxkeys',
knxkeys_password: process.env.KNX_KEYS_PASSWORD || 'your-ets-password',
}
const client = new KNXClient({
hostProtocol: 'TunnelTCP',
ipAddr: '192.168.1.4',
ipPort: 3671,
isSecureKNXEnabled: true,
secureTunnelConfig: secureCfg,
})
function waitForStatus(ga: string, timeoutMs = 5000): Promise<number> {
return new Promise((resolve, reject) => {
const t = setTimeout(() => {
client.off('indication', onInd)
reject(new Error('Timeout waiting for status'))
}, timeoutMs)
const onInd = (packet: any) => {
const cemi = packet?.cEMIMessage
if (!cemi || cemi.dstAddress?.toString?.() !== ga) return
const npdu = cemi.npdu
const isResp = npdu?.isGroupResponse
const isWrite = npdu?.isGroupWrite
if (!(isResp || isWrite)) return
const raw: Buffer = npdu?.dataValue ?? Buffer.alloc(1, 0)
const bit = (raw.readUInt8(0) ?? 0) & 0x01
clearTimeout(t)
client.off('indication', onInd)
resolve(bit)
}
client.on('indication', onInd)
})
}
async function main() {
client.Connect()
await new Promise<void>((res) => client.once('connected', () => res()))
// Example: query a status GA and decode as boolean 1.001
const statusGA = '1/1/2'
client.read(statusGA)
const val = await waitForStatus(statusGA, 5000)
console.log(`Status on ${statusGA}:`, val ? 'ON' : 'OFF')
}
main().catch(console.error)Tips
- For tunnelling (TCP), the source IA is assigned by the gateway. For routing (multicast), set
physAddrin options. - If you see decrypted payloads as null, verify that the GA has a Data Secure key in your
.knxkeysand that the ETS keyring and password are correct. | message | Log message content | | stack | Error stack trace (only present for errors) |
| Level | Description |
|---|---|
| disable | No logging |
| error | Only logs errors |
| warn | Logs errors and warnings |
| info | Logs normal operations |
| debug | Logs detailed information |
| trace | Most verbose logging level |
For a complete example of logging usage, see logging.ts in the examples folder.
Decoding is very simple.
Just require the dptlib and use it to decode the RAW telegram
import { dptlib } from "knxultimate";
let dpt = dptlib.resolve("1.001");
let jsValue = dptlib.fromBuffer(RAW VALUE (SEE SAMPLES), dpt); // THIS IS THE DECODED VALUEAll examples live in the examples folder. You can run them directly with TypeScript via esbuild-register (no build step needed).
- Generic command:
node -r esbuild-register -e "require('./examples/<file>.ts')" - For secure samples, npm scripts are available:
npm run example:secure:tunnel(secure tunnelling TCP)npm run example:secure:multicast(secure routing multicast)
Examples overview:
-
Recommended starting point → template: well-commented skeleton showing how to configure plain vs secure connections, wire up core events, and encode/decode datapoint payloads. Use this file as a base for new scripts.
-
sample: Full walkthrough — connect, read/write, decode values. Warning: sends telegrams to your KNX BUS.
-
simpleSample: Minimal connect + single write. Warning: sends telegrams.
-
test-toggle: Interactive ON/OFF toggle from CLI. Warning: sends telegrams.
-
disconnection: Demonstrates clean disconnects and error handling.
-
logging: Shows how to attach to the log stream and change log levels.
-
monitorBusMinimal: Minimal listener that connects and prints incoming telegrams. Warning: connects to the bus but does not send telegrams.
-
showDatapoints: Lists supported datapoints and shows how payloads are built. Safe: does not send to the bus.
-
datapointBasics: Demonstrates encoding and decoding a few datapoint values locally. Safe.
-
discovery: Discovers KNX/IP interfaces/routers (SEARCH_REQUEST). Safe.
-
discoverInterfacesSimple: Uses
discoverInterfaces()to print a concise summary of each gateway. Safe. -
Run:
node -r esbuild-register -e "require('./examples/discoverInterfacesSimple.ts')" [iface] [timeoutMs] -
gatewaydescription: Requests and prints extended gateway information. Safe.
-
samplePlainTunnelUPD: Plain KNX/IP tunnelling over UDP (
hostProtocol: 'TunnelUDP'). ON/OFF + status read via a KNX interface.- Run:
node -r esbuild-register -e "require('./examples/samplePlainTunnelUPD.ts')"
- Run:
-
samplePlainMulticast: Plain KNX routing over multicast (
hostProtocol: 'Multicast'). Uses RoutingIndication with cEMI L_DATA_REQ. ON/OFF + status read via a KNX router (no secure).- Run:
node -r esbuild-register -e "require('./examples/samplePlainMulticast.ts')"
- Run:
-
sampleSecureTunnelTCP: KNX/IP Secure tunnelling over TCP (
hostProtocol: 'TunnelTCP'+isSecureKNXEnabled: true). Performs session handshake + Secure Wrapper. Applies Data Secure for GA present in the ETS keyring. ON/OFF + status read.- Requires: set
.knxkeyspath + password in the example file. Optionally omittunnelInterfaceIndividualAddressto auto‑select a free tunnel from the keyring. - Run:
npm run example:secure:tunnelornode -r esbuild-register -e "require('./examples/sampleSecureTunnelTCP.ts')"
- Requires: set
-
sampleSecureTunnelTCPNoDataSecure: KNX/IP Secure tunnelling over TCP using only the manual tunnel password/ID (no keyring, Data Secure disabled). Demonstrates secure channel establishment when group keys are unavailable.
- Configure: set
tunnelInterfaceIndividualAddress,tunnelUserPassword, andtunnelUserIdin the file. - Run:
node -r esbuild-register -e "require('./examples/sampleSecureTunnelTCPNoDataSecure.ts')"
- Configure: set
-
sampleSecureMulticast: KNX/IP Secure routing over multicast (
hostProtocol: 'Multicast'+isSecureKNXEnabled: true). Synchronizes timer via 0x0955, wraps frames in Secure Wrapper, and applies Data Secure per GA. ON/OFF + status read.- Requires: set
.knxkeyspath + password in the example file. - Run:
npm run example:secure:multicastornode -r esbuild-register -e "require('./examples/sampleSecureMulticast.ts')"
- Requires: set
-
dumpKeyringCredentials: Loads a
.knxkeysfile, decrypts all stored tunnel passwords, authentication codes, device credentials, group keys, and backbone keys, and prints them to the console. Safe.- Run:
node -r esbuild-register -e "require('./examples/dumpKeyringCredentials.ts')" [path/to/keyring.knxkeys] [ets-password]
- Run:
- Functions:
KNXClient.discover(),KNXClient.discoverDetailed(),KNXClient.discoverInterfaces(). - Default port: if a KNX interface does not advertise a port in the
SEARCH_RESPONSEHPAI (missing or zero), discovery uses3671as the port. - Each helper now fires both multicast and unicast search bursts concurrently on every selected NIC so interfaces that only answer one transport are still found.
- Return formats:
discover()→ strings formatted asip:port:name:ia:Security:Transport; highlights whether the result is plain/secure and which transport to use.- Example:
192.168.1.4:3671:MyGW:1.1.1:Secure KNX:TCP,224.0.23.12:3671:MyRouter:1.1.0:Plain KNX:Multicast.
- Example:
discoverDetailed()→ strings formatted asip:port:name:ia:service1,service2:type; focuses on human-readable service families (routing,tunnelling, etc.) and whether the entry corresponds to tunnelling or routing.discoverInterfaces()→ array of objects{ ip, port, name, ia, services, type, transport }, giving parsed fields and an inferred transport, withservicesexposed as an array of service names.
Example usage
import KNXClient from 'knxultimate'
// Simple list with security + transport
const list = await KNXClient.discover(5000)
for (const entry of list) {
const [ip, port, name, ia, security, transport] = entry.split(':')
console.log({ ip, port, name, ia, security, transport })
}
// Detailed strings
const detailed = await KNXClient.discoverDetailed(5000)
// ip:port:name:ia:service1,service2:type
// Structured objects
const objects = await KNXClient.discoverInterfaces(5000)
// [{ ip, port, name, ia, services, type, transport }, ...]- TunnelUDP: uses
physAddras the source IA on the bus. - TunnelTCP (secure): after a successful connect, uses the tunnel-assigned IA as the cEMI source on the bus; the Data Secure authentication, however, uses the interface IA from the ETS keyring (not the dynamic tunnel IA).
- Tips for UDP tunnelling:
- If your interface times out on L_DATA_REQ ACK, try
suppress_ack_ldatareq: true. - With multiple NICs, set
localIPAddress(orinterface) to bind the correct local interface.
- If your interface times out on L_DATA_REQ ACK, try
KNXClient supports KNX/IP Secure and Data Secure.
- Requirements: KNX Secure router/interface and ETS keyring (
.knxkeys). ForTunnelTCP, the interface IA is optional — if omitted, the client auto‑selects a usable tunnel from the keyring. - Data Secure: Group Addresses present in the keyring are encrypted end‑to‑end; GA not present remain plain.
- Modes:
TunnelTCP+isSecureKNXEnabled: true→ secure session (Secure Wrapper) + Data Secure per GA.Multicast+isSecureKNXEnabled: true→ secure routing (Secure Wrapper over multicast with timer synchronization via 0x0955) + Data Secure per GA.
Routing (Multicast) specifics
- Outgoing frames are injected as
L_DATA_INDwhen using routing (both plain and secure). This mirrors ETS and xKNX behavior and ensures devices react correctly to injected telegrams. Do not useL_DATA_REQfor routing injection. - Secure routing needs a synchronized timer. By default the client waits until the timer is authenticated before sending. Additionally, on secure multicast startup the client proactively sends a
TimerNotify (0x0955)once, so the timer can authenticate immediately even if the router doesn’t broadcast it right away. Plain multicast is unaffected. - Option:
secureRoutingWaitForTimer(defaulttrue) controls gating of outgoing frames until the timer is authenticated.
Authorized senders (Data Secure, secure routing)
- When sending to a Data Secure Group Address over routing, KNX routers enforce an “authorized senders” list per GA. If your current
physAddris not authorized for that GA, the router will drop the telegram. - The client automatically avoids this problem by selecting an allowed sender IA for each secure GA based on the ETS keyring. If the configured
physAddris not in the GA’s Senders list, the library overrides the source IA with one that is authorized for that GA (taken from the keyring’s Senders for that GA) before building the Secure APDU. - Prerequisites: your
.knxkeys(ETS Project Keyring) must include the target GA and its Senders list. If no Senders are present for that GA in the keyring, the client keepsphysAddrand the router may still drop the frame as unauthorized. - This behavior is automatic whenever
hostProtocol: 'Multicast'andisSecureKNXEnabled: trueand the GA is protected by a Data Secure key in the keyring. No additional option is required.
Mixed secure/plain on one instance
- A single
KNXClientinstance can handle Data Secure and plain Group Addresses at the same time. Data Secure is applied per‑GA: if a GA has a key in the ETS keyring, the APDU is encrypted; if not, the APDU stays plain. - Secure routing (multicast): when
isSecureKNXEnabled: true, all routing frames are transported inside the KNX/IP Secure Wrapper (0x0950). “Plain” APDUs still travel inside that wrapper. This allows mixing secure and plain GA on the same instance. If you also need to emit non‑wrapped plain routing frames simultaneously, run a secondKNXClientwithisSecureKNXEnabled: false. - Secure tunnelling (TCP): the tunnel is always wrapped (Secure Wrapper). APDU encryption (Data Secure) remains per‑GA; GA without keys remain plain.
- Receive path: with secure enabled, the client decrypts
0x0950frames and forwards the inner plain cEMI; it also accepts non‑wrapped routing frames if present on the bus. Theindicationevent always exposes a plain (decrypted) cEMI when keys are available.
Troubleshooting (routing)
- If an actuator doesn’t react to writes sent over routing, verify that your app injects
L_DATA_IND(notL_DATA_REQ). From version 5.0.0‑beta the library automatically usesL_DATA_INDfor multicast writes/reads/responses. - For secure routing, ensure your ETS keyring contains a Backbone key and that the target Group Addresses are present with keys (Data Secure). You can list them with the example
examples/listSecureGroups.ts.
Behavior when fields are unset
- secureTunnelConfig.tunnelInterfaceIndividualAddress (TunnelTCP): if omitted/empty, the client auto‑selects a tunnel from the ETS keyring and retries interfaces until authentication and connect succeed. The chosen IA is exposed runtime in
client._options.secureTunnelConfig.tunnelInterfaceIndividualAddressafter connect. - physAddr:
- Multicast: must be provided; it’s your node’s source IA on the bus.
- TunnelUDP: optional; used as cEMI source if set.
- TunnelTCP: optional and ignored for the bus source (gateway provides the tunnel IA); Data Secure signs as the interface IA from the keyring.
Secure TCP auto‑selection details
- IA selection: the client runs discovery for
ipAddr:ipPort, reads the gateway “Host” IA, and selects keyring interfaces whoseHostmatches it. Candidates are sorted descending (e.g. …255, …254, …253). If no match byHost, it falls back to all keyring interfaces of typeTunneling. - Single‑NIC discovery: when
options.interfaceis empty/undefined, discovery is executed only on the local NIC that shares the same subnet ofipAddr(no scan across all OS interfaces). - Timeouts: detailed discovery runs ~3s on the chosen NIC; if nothing is found, a lightweight simple discovery runs ~5s on the same NIC and is adapted to the detailed format.
- Logging: look for lines starting with “Secure TCP:” to follow selection steps (discovered Host IA, candidates, chosen IA).
- Override: set
secureTunnelConfig.tunnelInterfaceIndividualAddressand/orinterfaceexplicitly to skip the auto‑selection.
This guide explains how to connect a kBerry KNX interface directly
to a Raspberry Pi 3 and use it with KNX Ultimate over the
hardware UART (ttyAMA0) using the FT1.2 (TPUART) protocol.
This procedure is tested with Raspberry Pi OS Bookworm on a
Raspberry Pi 3 and has been written on November, 25, 2025.
- Raspberry Pi 3 (Model B or B+)
- Raspberry Pi OS (Bookworm recommended)
- kBerry KNX interface mounted on the GPIO header
- Node-RED with KNX Ultimate installed
- Basic terminal access (SSH or local console)
The kBerry uses the Raspberry Pi's primary UART:
- TX / RX: GPIO14 (TXD) and GPIO15 (RXD)
- GND: A common ground between Raspberry Pi and kBerry
- Power: Provided via the GPIO header
Make sure the kBerry is properly seated on the Raspberry Pi GPIO header and that no other HAT is conflicting with those pins.
sudo nano /boot/firmware/config.txtAdd:
enable_uart=1
dtoverlay=pi3-disable-btsudo systemctl disable --now ModemManagersudo systemctl disable --now bluetooth.servicesudo raspi-config- Disable login shell on serial → No
- Enable serial hardware → Yes
Reboot.
ls -l /dev/serial0
ls -l /dev/ttyAMA0
dmesg | grep ttyExpected:
/dev/serial0 -> ttyAMA0
/dev/ttyAMA0 exists
sudo usermod -aG dialout nodered
sudo reboot- Interface type: Serial FT1.2 / TPUART
- Serial port:
/dev/ttyAMA0 - Baud rate: 19200
- Data bits: 8
- Parity: Even
- Stop bits: 1
- Check
/boot/firmware/config.txtentries - Reboot
- Re-check
dmesg
dtoverlay=pi3-disable-btnot applied- Re-check config file path
- Reboot
- Ensure user is in
dialout - Check that no other program uses
/dev/ttyAMA0
Done.
If you want to help us in this project, you're wellcome!
Please refer to the development page.
Why not to try Node-Red https://nodered.org and the awesome KNX-Ultimate node https://flows.nodered.org/node/node-red-contrib-knx-ultimate ?