knx-rs
A platform-independent KNX protocol stack in Rust — for embedded devices, servers, and everything in between.
Crates
| Crate | Description | no_std |
|---|---|---|
| knx-rs-core | Protocol types, CEMI frames, DPT conversions, KNXnet/IP frame types | ✅ |
| knx-rs-ip | Async KNXnet/IP tunnel, router, discovery, and device server (tokio) | ❌ |
| knx-rs-device | KNX device stack — group objects, ETS programming, BAU | ✅ |
| knx-rs-tp | TP-UART data link layer for embedded targets (WIP) | ✅ |
| knx-rs-prod | .knxprod generator — hash, sign, and package ETS product databases |
❌ |
⚠️ Migrating from 0.2 to 0.3
0.3.0 contains breaking API changes. Cargo treats each 0.x minor as a breaking
boundary, so a "0.2" dependency will not pick up 0.3 automatically — bump your
version requirement deliberately and apply the changes below.
knx-rs-core
KnxIpError→KnxIpParseError. The frame-parse error was renamed to free the nameKnxIpErrorfor the (unrelated) connection error inknx-rs-ip. A#[deprecated]alias keeps the old name compiling, so existing code still builds (with a warning) — migrate references, including variant paths, at your leisure.Apdu::to_byteswire change (correctness): a single group-value byte> 0x3F(e.g. a DPT 5 value such as 200) now uses the long form instead of being masked to 6 bits and losing data. Golden byte-vector expectations for such values change; decoding still round-trips both forms.
knx-rs-ip
- New
KnxIpErrorvariants —Frame,Cemi,Dpt,Multicast,InvalidConfig. Exhaustivematches must add arms (or a_ =>). Failures that previously surfaced asProtocol(String)(multicast-join, non-multicast target, frame/DPT errors) now use these typed variants, so string-matching onProtocolno longer catches them. - A
pub type Result<T> = core::result::Result<T, KnxIpError>alias is now exported from the crate root; existingResult<_, KnxIpError>signatures keep compiling.
knx-rs-tp (WIP)
TpFrame::from_ceminow returnsOption<Self>(Nonewhen the APDU exceeds the frame buffer). Handle theNonecase instead of binding the value directly.TpIndicationgained anOverrunvariant — add a match arm; treat it as a recoverable resync event.
knx-rs-device
DataProperty::access() -> u8deprecated (still available) — preferaccess_level()(typedAccessLevel), oraccess_level() as u8for the raw byte.SystemNetworkParameterRead.test_infois now sliced from the correct offset (was off by one), so consumers receive the corrected payload.
knx-rs-prod
- The pipeline modules (
archive,parse,sign,split,hash,error) remain public;KnxprodErrorandKnxMetadataare additionally re-exported at the crate root, so bothknx_rs_prod::KnxprodErrorandknx_rs_prod::error::KnxprodErrorwork. - Two unused helpers were removed:
sign::signed_filenameandparse::extract_metadata(the&Pathwrapper). Useparse::extract_metadata_from_strorgenerate_knxprod(which returns theKnxMetadata) instead. - Malformed input now errors instead of producing a wrong-but-valid hash:
unparseable numeric attributes and invalid base64 program images return
KnxprodError::InvalidStructure. Well-formed product XML is unaffected.
Features
knx-rs-core
- Addresses —
IndividualAddress(1.1.1),GroupAddress(1/0/1), withDisplay,FromStr, optionalserde - CEMI frames — parse and serialize with full read/write access to all control fields
- TPDU / APDU — structured PDU types with all ~60 APCI service codes
- DPT conversions — 34 main groups, 100% parity with the C++ reference implementation
- KNXnet/IP types — frame header, service types, connection header, HPAI
no_std+alloc— runs on embedded targets (ARM Cortex-M, RISC-V)
knx-rs-ip
- Tunnel connection — connect handshake, 3× retry, heartbeat, auto-reconnect
- Router connection — multicast routing with rate limiting (50 pkt/s per KNX spec)
- Device server — accept incoming tunnel connections from ETS on port 3671, simultaneous multicast routing and unicast tunneling
- Discovery — search request/response for finding gateways on the local network
- Multiplexer — fan out one connection into multiple independent handles
- URL parsing —
udp://,tunnel://,router://with multicast auto-detection
knx-rs-device
- Property system — data-backed and callback-backed properties with
constmetadata - Interface objects — device object, application program, with unified indexed access
- Table objects — address table, association table, group object table (ETS-loadable)
- Group objects —
ComFlagstate machine, DPT-aware values, update callbacks - Bus Access Unit (BAU) — processes CEMI frames, handles all KNX application-layer services including connected-mode transport
- Memory management —
MemoryBackendtrait, RAM backend, C++-compatible persistence format no_std+alloc— runs on embedded targets
knx-rs-prod
- Hash — clean-room Rust reimplementation of the ETS
Knx.Ets.XmlSigning.dllhashing algorithm, verified byte-exact against 28 test files from 5 manufacturers - Sign — compute registration-relevant MD5 hash, patch fingerprint into application IDs
- Split — split monolithic XML into Catalog.xml, Hardware.xml, Application.xml with per-category translation filtering
- Package — ZIP into
.knxprodimportable by ETS - No C# dependency — replaces the Windows-only
OpenKNXproducersigning step entirely
Quick Start
Client: read from a KNX gateway
use ;
use ;
async
Device: ETS-programmable KNX IP device
use Ipv4Addr;
use ;
use ;
use DPT_VALUE_TEMP;
async
Generating .knxprod Files
knx-rs-prod replaces the entire Windows-only C# toolchain (OpenKNXproducer + Knx.Ets.XmlSigning.dll) with pure Rust. No .NET, no Wine, no Windows VM required.
Two workflows
Option A: Rust-native (recommended) — generate the product XML from your Rust code, then sign and package. No external tools at all.
Rust source code (GO definitions, parameters)
↓ cargo xtask generate-xml
MyDevice.xml (generated, not hand-written)
↓ cargo xtask knxprod (or: knx-rs-prod CLI)
MyDevice.knxprod
↓
ETS Import
This is the approach used by SnapDog: a Rust xtask reads the group object definitions from the device firmware (SSOT — the same constants that configure the BAU at runtime) and generates the complete ETS product XML. Then knx-rs-prod signs and packages it. The XML is a build artifact, never hand-edited.
Option B: OpenKNXproducer + knx-rs-prod — use OpenKNXproducer for XML authoring, replace only the signing step.
OpenKNXproducer (XML authoring, GUI)
↓
MyDevice.xml (hand-authored)
↓ knx-rs-prod
MyDevice.knxprod
↓
ETS Import
Writing an xtask for XML generation
Create a xtask/ crate in your workspace that imports your device's GO definitions and generates the XML:
// xtask/src/main.rs
use Path;
use ;
The key insight: your GO definitions, parameter memory layout, and DPT mappings are const data in your firmware crate. The xtask reads them at build time to generate the XML — no duplication, no drift between firmware and ETS configuration.
Local usage (CLI)
# Install from crates.io
# Or build from source
# Generate .knxprod from product XML
As a library
Add knx-rs-prod to your Cargo.toml (without the cli feature) and call knx_rs_prod::generate_knxprod() — see the xtask example above.
CI Integration
Add .knxprod generation to your GitHub Actions workflow — runs on Linux, no Windows runner needed:
jobs:
knxprod:
name: Generate .knxprod
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# Option A: xtask generates XML + knxprod in one step
- run: cargo xtask knxprod
# Option B: knx-rs-prod CLI on existing XML
# - run: cargo run --release -p knx-rs-prod -- firmware/MyDevice.xml -o MyDevice.knxprod
- uses: actions/upload-artifact@v4
with:
name: knxprod
path: "*.knxprod"
For release workflows, attach the .knxprod as a release asset alongside your firmware binary.
How the hash works
The Hash attribute on <ApplicationProgram> is computed by a clean-room Rust reimplementation of the closed-source Knx.Ets.XmlSigning.dll. The algorithm was reconstructed through analysis of the ETS signing process and verified byte-exact against 28 test files from 5 manufacturers (MDT, Gira, ABB, Siemens, OpenKNX).
Key aspects: forward-only XML reader with recursively sorted children, .NET InvariantCulture string comparison, 89 registration-relevant element types, IEEE 754 double serialization for float attributes, parent-conditional ordering for ParameterRefRef elements.
Full documentation: knx-rs-prod/HASHING.md
DPT Coverage
All 34 main groups from the C++ reference are supported:
| DPT | Type | DPT | Type |
|---|---|---|---|
| 1 | Boolean | 17 | Scene number |
| 2 | Controlled boolean | 18 | Scene control |
| 3 | Controlled step | 19 | Date and time |
| 4 | Character | 26 | Scene info |
| 5 | Unsigned 8-bit | 27 | 32-bit field |
| 6 | Signed 8-bit | 28 | Unicode string |
| 7 | Unsigned 16-bit | 29 | Signed 64-bit |
| 8 | Signed 16-bit | 217 | Version |
| 9 | 16-bit float | 219 | Alarm info |
| 10 | Time of day | 221 | Serial number |
| 11 | Date | 225 | Scaling speed |
| 12 | Unsigned 32-bit | 231 | Locale |
| 13 | Signed 32-bit | 232 | RGB |
| 14 | IEEE 754 float | 234 | Language code |
| 15 | Access data | 235 | Active energy |
| 16 | String (ASCII/Latin-1) | 238/239/251 | Scene config / Flagged scaling / RGBW |
Testing
Validated against the OpenKNX/knx C++ reference stack:
- Golden test vectors — C++ harness (
test-vectors/generate.cpp) generates JSON fixtures for CEMI frames, CEMI setters, and DPT conversions, verified byte-for-byte in Rust - Integration tests — tunnel server ↔ client on real UDP loopback (connect, heartbeat, frame exchange, disconnect)
- Unit tests — extensive coverage across all crates: protocol types, DPT conversions, parsers, the load state machine, and the BAU service handlers
- knxprod hash verification — 28 test files from 5 manufacturers, byte-exact match with ETS DLL output
# Run all tests
# Run with all features
# Verify no_std
# knxprod hash tests
Architecture
Application code ←→ GroupObjects ←→ BAU ←→ DeviceServer (port 3671)
↕ ↕ ↕
InterfaceObjects Multicast Tunnel
↕ (routing) (ETS)
DeviceMemory
Rust xtask / OpenKNXproducer ──→ Product XML ──→ knx-rs-prod ──→ .knxprod ──→ ETS
Development
# Build everything
# Run all tests (integration tests need single-threaded)
# Clippy (pedantic + nursery)
# Format
# Generate docs
# Check no_std targets
Acknowledgements
This project builds on the work of the OpenKNX community and the original thelsing/knx C++ stack by Thomas Kunze. The DPT conversion logic, CEMI frame layout, and protocol constants are derived from the OpenKNX/knx fork (v2.3.1), which is maintained by the OpenKNX team.
The .knxprod hashing algorithm was reconstructed through analysis of the Knx.Ets.XmlSigning.dll from the ETS distribution. No ETS source code was used — the implementation is a clean-room reimplementation verified against the DLL's output.
We are grateful for the OpenKNX community's work in creating and maintaining an open-source KNX device stack that made this Rust reimplementation possible.
License
GPL-3.0-only — see LICENSE.