js-knx
v3.5.0
Published
KNX IP/Gateway client
Readme
js-knx
TypeScript/JavaScript client for KNX/IP gateways (tunneling connection, link layer).
Depends on @opentelemetry/api for optional tracing (no-op until the host process registers an OpenTelemetry SDK). Works on Node.js 20+.
See CHANGELOG for release notes. Recent versions:
- 3.5.0 — Removed OpenTelemetry metrics (cardinality); traces unchanged
- 3.4.1 — KNX group trace spans only when a parent span is active (no root spans from js-knx)
- 3.4.0 —
abortConnect()to cancel in-flightconnect()during retry;CONNECTION_ABORTEDerror code - 3.3.0 — OpenTelemetry spans for group read/write/request; optional
onReceivecallback - 3.2.0 — Per group address read timeout counters (
getReadTimeoutStats()) - 3.1.0 —
addResponseListener/ explicit write vs read-response listeners (replacesaddValueListener) - 3.0.0 —
KnxLink.group()(renamed fromgetDatapoint())
Installation
npm install js-knx
# or
yarn add js-knxThe package ships as a dual package: use require('js-knx') in CommonJS or import { KnxLink } from 'js-knx' in ESM. Entry points are defined in package.json exports.
Quick start
import { DPT_HVACMode, KnxLink } from 'js-knx'
const knx = new KnxLink('192.168.0.8', { readTimeout: 5000 })
knx.on('error', err => {
console.error(err.message, err.code)
})
await knx.connect()
const livingRoom = knx.group({
address: '2/0/4',
DataType: DPT_HVACMode
})
const reading = await livingRoom.read()
console.log(reading.text, reading.value)
await knx.disconnect()Group addresses use KNX notation (main/middle/sub, e.g. 2/0/4).
Listen for bus traffic
knx.on('cemi-frame', frame => {
console.log(frame.source, '->', frame.target, frame.value)
})Write and subscribe
import { DPT_Switch, KnxLink } from 'js-knx'
const knx = new KnxLink('192.168.0.8')
knx.on('error', err => console.error(err))
await knx.connect()
const light = knx.group({ address: '14/0/0', DataType: DPT_Switch })
await light.on()
light.onValue(reading => {
console.log('state:', reading.text)
})
await knx.disconnect()CLI
After installation, two commands are available globally:
| Command | Description |
| ----------- | -------------------------------- |
| knx-read | Read a group address once |
| knx-write | Write a value to a group address |
Gateway IP can be passed as the first argument or via the KNX_GATEWAY environment variable.
# read
knx-read 192.168.0.8 2/0/4 HVACMode
KNX_GATEWAY=192.168.0.8 knx-read 2/0/4 Switch
# write
knx-write 2/0/4 Switch 1
knx-write 192.168.0.8 2/0/4 Scaling 50DPT names accept short form (Switch, HVACMode) or full export name (DPT_Switch).
Numeric string values are coerced to numbers automatically ("1" → 1). Time/date strings are passed through as-is.
Supported DPT types
Each DPT is a class exported from js-knx. Instantiate via knx.group({ address, DataType }).
1.x — Boolean / binary
| Class | KNX DPT |
| ----------------- | ------- |
| DPT_Switch | 1.001 |
| DPT_Bool | 1.002 |
| DPT_Enable | 1.003 |
| DPT_Alarm | 1.005 |
| DPT_UpDown | 1.008 |
| DPT_OpenClose | 1.009 |
| DPT_StartStop | 1.010 |
| DPT_State | 1.011 |
| DPT_Reset | 1.015 |
| DPT_Ack | 1.016 |
| DPT_Trigger | 1.017 |
| DPT_Occupancy | 1.018 |
| DPT_Window_Door | 1.019 |
| DPT_DayNight | 1.024 |
| DPT_Generic_B1 | — |
5.x — Unsigned 8-bit
| Class | KNX DPT |
| -------------------- | ------- |
| DPT_Scaling | 5.001 |
| DPT_Angle | 5.003 |
| DPT_Percent_U8 | 5.004 |
| DPT_Tariff | 5.006 |
| DPT_Value_1_Ucount | 5.010 |
| DPT_Generic_U8 | — |
9.x — Float 16-bit
| Class | KNX DPT |
| ---------------------- | ------- |
| DPT_Value_Temp | 9.001 |
| DPT_Value_Humidity | 9.007 |
| DPT_Value_AirQuality | 9.008 |
| DPT_Generic_F16 | — |
10.x / 11.x / 19.x — Time and date
| Class | KNX DPT |
| -------------- | ------- |
| DPT_Time | 10.001 |
| DPT_Date | 11.001 |
| DPT_DateTime | 19.001 |
13.x / 14.x — Energy and power
| Class | KNX DPT |
| ------------------------------------- | ------- |
| DPT_ActiveEnergy | 13.010 |
| DPT_Value_Electric_Current | 14.019 |
| DPT_Value_Electric_Potential | 14.027 |
| DPT_Value_Frequency | 14.031 |
| DPT_Value_Power_Factor | 14.057 |
| DPT_Value_Power | 14.056 |
| DPT_Value_ApparentPower | 14.080 |
| DPT_Generic_V32 / DPT_Generic_F32 | — |
20.x / 21.x — HVAC and status
| Class | KNX DPT |
| ------------------- | ------- |
| DPT_HVACMode | 20.102 |
| DPT_HVACContrMode | 20.105 |
| DPT_StatusGen | 21.001 |
Several DPT classes expose convenience methods (e.g. DPT_Switch.on() / .off(), DPT_HVACMode mode constants).
KnxLinkConstructorOptions
All options are optional when calling new KnxLink(ip, options).
| Option | Default | Description |
| ----------------------- | ---------- | ------------------------------------------------------------------------------------------- |
| readTimeout | 10000 | Timeout (ms) for read() waiting for a group response |
| connectionTimeout | 10000 | Timeout (ms) for the initial KNX/IP tunnel handshake |
| maxRetry | Infinity | Retries when the gateway rejects connection (e.g. both tunnel slots busy) |
| retryPause | 3000 | Pause (ms) between connection retries; also used for automatic reconnect after network loss |
| maxConcurrentMessages | 16 | Max telegrams awaiting gateway ACK before back-pressure |
| maxTelegramsPerSecond | 24 | Send rate limit; lower if you see read timeouts on busy buses |
| port | 3671 | KNX/IP UDP port of the gateway |
| autoReconnect | true | Reconnect automatically after gateway or network loss (not after disconnect()) |
| onReceive | — | Optional callback after a push group write is decoded (see below) |
Subscribe to link events with knx.on('error', …) and knx.on('cemi-frame', …).
Push receive callback (onReceive)
Since 3.3.0, you can observe spontaneous bus writes without trace spans:
const knx = new KnxLink('192.168.0.8', {
onReceive: ({ groupAddress, dpt, source }) => {
console.log('push', source, '->', groupAddress, dpt)
}
})OpenTelemetry (3.3.0+; metrics removed in 3.5.0)
When the host application registers an OpenTelemetry SDK, js-knx emits traces only:
| Signal | What is instrumented |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Traces | read(), requestValue(), write() — span knx.group (child span only; requires an active parent span in the OTEL context) |
Trace attributes: knx.group_address, knx.operation (read | request_value | write), knx.dpt.
read()— one span for the full round-trip; span eventsknx.read_sentandknx.value_receivedrequestValue()— short span covering the group-read send only (no span events, no wait for response)- Push writes — no spans (use
onReceiveif you need a callback)
Child knx.group spans are created only when an active parent span exists. Without an SDK, tracing is a no-op.
Connection behaviour
- Initial connect retries with
maxRetry/retryPauseuntil the gateway accepts a tunnel (useful when both tunnel channels are occupied). abortConnect()(since 3.4.0) — cancel startup whileconnect()is still retrying. Rejects the pendingconnect()withCONNECTION_ABORTED, emitsdisconnectedwith reasonconnect-aborted, and closes any opened UDP transport. No-op when not connecting. Use on process shutdown instead of waiting for retries to finish; calldisconnect()after a successful connection.- Automatic reconnect runs after unexpected session loss (socket close, gateway disconnect). It does not run after an explicit
disconnect()or afterabortConnect(). - Second
connect()on the sameKnxConnectionthrowsCONNECTION_ALREADY_ESTABLISHEDorCONNECTION_IN_PROGRESS.
import { KnxLink, KnxLinkException } from 'js-knx'
const knx = new KnxLink('192.168.0.8')
const connectPromise = knx.connect()
process.on('SIGTERM', () => {
knx.abortConnect()
})
try {
await connectPromise
} catch (err) {
if (err instanceof KnxLinkException && err.code === 'CONNECTION_ABORTED') {
// startup cancelled — e.g. dev server restart
}
}Datapoint API
Every DPT class extends DataPointAbstract:
| Method | Description |
| ------------------------------ | ---------------------------------------------------------------- |
| read() | Send group read, wait for response (KnxReading<T>) |
| write(value) | Send group write (DPT-specific value type) |
| requestValue() | Send group read without waiting |
| addWriteListener(cb) | Subscribe to incoming group writes |
| addResponseListener(cb) | Subscribe to group-read responses (e.g. after requestValue) |
| onValue(cb) / offValue(cb) | Subscribe to both writes and read responses |
| getAddress() | Group address string |
| getReadTimeoutStats() | Cumulative and consecutive read() timeout counts (since 3.2.0) |
| resetReadTimeoutStats() | Reset read timeout counters to zero |
| getLink() | Parent link (KnxDatapointLink; implemented by KnxLink) |
| toString(value?) | Human-readable label |
KnxReading shape:
{
target: string // group address
source: string // individual address (e.g. "1.2.3")
text: string // formatted value
unit: string
value: T
}Error handling
Errors are thrown as KnxLinkException with a code field:
| Code | Typical cause |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| CONNECTION_ABORTED | abortConnect() called while connect() was retrying (since 3.4.0) |
| CONNECTION_TIMEOUT | Gateway did not respond during handshake |
| CONNECTION_ERROR | Gateway rejected connection (busy, wrong layer, …) |
| CONNECTION_ALREADY_ESTABLISHED | connect() called twice |
| READ_TIMEOUT | No bus response within readTimeout; details includes readTimeoutCount and consecutiveReadTimeouts (since 3.2.0) |
| DATA_LENGTH_MISMATCH | Received payload size does not match DPT |
| PROTOCOL_ERROR | Invalid KNX/IP or cEMI frame |
| NO_CONNECTION | Send attempted after disconnect |
| ACK_TIMEOUT | Gateway did not ACK a tunnel telegram (after retry) |
| NETWORK_ERROR | UDP socket connect or send failure |
Listen on knx.on('error', …) for non-fatal bus/protocol errors during operation.
Protocol references
KNX/IP tunneling implementation notes and further reading:
- KNX/IP documentation (eb-systeme.de) — referenced in the connection layer source
Development
git clone https://github.com/kodmax/jsKnx.git
cd jsKnx
yarn install
yarn build
yarn test
yarn lint:ciLocal demo (edit gateway IP in examples/demo.ts):
yarn build # required once so `js-knx` resolves for the example imports
yarn dev