Skip to content

Implement KNX/IP Connector for Building Automation #54

@lxsaah

Description

@lxsaah

Summary

Implement aimdb-knx-connector to enable real-time monitoring and control of KNX building automation networks. The connector will support bidirectional communication with KNX/IP gateways using the proven ConnectorBuilder + Router pattern from the MQTT connector.

Motivation

KNX is the leading open standard for building automation, controlling lighting, HVAC, security, and energy management in commercial and residential installations worldwide. Integrating KNX with AimDB enables:

  • Edge Analytics: Real-time monitoring on Raspberry Pi or embedded devices
  • Cloud Integration: Seamless bridging between KNX and MQTT/HTTP/Kafka
  • Type Safety: KNX telegrams become strongly-typed Rust records
  • Unified Runtime: Same code runs on MCU (Embassy) and servers (Tokio)

Scope

Phase 1: Core Implementation (4-6 weeks)

Deliverables:

  • ✅ KNXnet/IP Tunneling protocol support (UDP port 3671)
  • ✅ Inbound monitoring: KNX bus → AimDB records (via Router)
  • ✅ Outbound control: AimDB records → KNX bus (via publish)
  • ✅ Group address parsing (3-level format: main/middle/sub)
  • ✅ Basic DPT type parsing (bool, u8, u16, f32)
  • Tokio runtime support (tokio-runtime feature)
  • Embassy runtime support (embassy-runtime feature)
  • ✅ Automatic reconnection on connection loss
  • ✅ Example: tokio-knx-demo (Tokio)
  • ✅ Example: embassy-knx-demo (Embassy)

Out of Scope (Future):

  • Extended DPT type library (1-20 full coverage)
  • Group address discovery
  • ETS project import
  • KNXnet/IP Secure

Technical Approach

Architecture Pattern

Follow the proven MQTT connector pattern exactly:

User Configuration:
  .with_connector(KnxConnectorBuilder::new("knx://192.168.1.19:3671"))
  .configure::<LightState>(|reg| {
      reg.link_from("knx://1/0/7")  // Inbound
      reg.link_to("knx://1/0/8")    // Outbound
  })

Build Phase:
  1. CollectorBuilder collects inbound routes
  2. RouterBuilder creates Router (group_address → producer)
  3. Spawn background task (UDP listener + router dispatch)
  4. Spawn outbound publishers (consumer → serialize → publish)

Runtime:
  Inbound:  KNX telegram → parse → router.route() → producer → buffer
  Outbound: Buffer → consumer → serializer → connector.publish() → GroupValueWrite

Protocol Implementation

Connection Sequence:

  1. UDP socket bind to ephemeral port
  2. Send CONNECT_REQUEST to gateway:3671
  3. Receive CONNECT_RESPONSE with channel_id
  4. Loop:
    • Receive TUNNELING_REQUEST (bus telegrams)
    • Parse cEMI frame (message code 0x29 = L_Data.ind)
    • Extract group address and payload
    • Dispatch via router.route(group_address, data)
    • Send TUNNELING_ACK (seq counter + channel_id)
  5. On error: reconnect with exponential backoff

Frame Parsing:

  • Header: 6 bytes (length, version, service type, total length)
  • Connection header: 4 bytes (length, channel_id, seq_counter, reserved)
  • cEMI: variable (control fields, addresses, NPDU, payload)

Code Structure

aimdb-knx-connector/
├── Cargo.toml
├── src/
│   ├── lib.rs              # GroupAddress, KnxValue, URL parsing
│   ├── tokio_client.rs     # Tokio implementation (std)
│   └── embassy_client.rs   # Embassy implementation (no_std)
└── examples/
    ├── tokio-knx-demo/         # Tokio demo
    └── embassy-knx-demo/       # Embassy demo

Implementation Checklist

Core Types (lib.rs)

  • GroupAddress struct with 3-level parsing and encoding
  • KnxValue struct with DPT type helpers
  • parse_knx_url() function for knx://host:port format
  • KnxConfig struct for gateway connection info
  • Error types (KnxError enum)

ConnectorBuilder (tokio_client.rs + embassy_client.rs)

  • KnxConnectorBuilder struct (both runtimes)
  • Implement ConnectorBuilder<R> trait:
    • build(): collect routes, create router, spawn tasks
    • scheme(): return "knx"
  • Collect inbound routes via db.collect_inbound_routes("knx")
  • Build Router from inbound routes
  • Collect outbound routes via db.collect_outbound_routes("knx")
  • Call spawn_outbound_publishers()

Connector Implementation (both runtimes)

  • KnxConnectorImpl struct with router
  • new(): spawn background listener task
  • spawn_outbound_publishers(): spawn consumer tasks
  • Implement Connector trait:
    • publish(): send GroupValueWrite telegram
  • Tokio: Use tokio::net::UdpSocket and tokio::spawn
  • Embassy: Use embassy_net::udp::UdpSocket and SendFutureWrapper

Protocol Implementation (shared logic)

  • KnxConnection struct (socket, channel_id, seq_counter)
  • connect_and_listen(): full connection lifecycle
  • build_connect_request(): construct CONNECT_REQUEST frame
  • parse_connect_response(): extract channel_id and status
  • build_tunneling_ack(): construct ACK frame
  • parse_telegram(): extract group address and payload from cEMI
  • is_tunneling_request(): frame type detection
  • send_group_write(): construct GroupValueWrite frame (outbound)

Testing

  • Unit tests: GroupAddress parsing, encoding, URL parsing
  • Integration test: Connect to real gateway (manual, Tokio)
  • Integration test: Connect to real gateway (manual, Embassy on hardware)
  • Example: tokio-knx-demo with tap() observer (Tokio)
  • Example: embassy-knx-demo (Embassy on STM32/ESP32)
  • Documentation: README with usage examples for both runtimes

Documentation

  • API documentation (rustdoc)
  • README with quick start guide
  • CHANGELOG entry
  • Update main README with KNX connector mention

Testing Strategy

Manual Testing Requirements

Hardware:

  • KNX/IP gateway (e.g., SCN-IP000.03, Gira X1, ABB IP Interface)
  • Active KNX bus with devices (lights, switches, sensors)

Test Procedure:

# 1. Run monitor example
cd examples/tokio-knx-demo
cargo run

# Expected output:
# ✅ KNX connected, channel_id: 42
# Collected 1 inbound KNX routes
# KNX router has 1 group addresses

# 2. Trigger KNX bus activity (press switch, send telegram)
# Expected output:
# 💡 Light 1/0/7: ON
# 💡 Light 1/0/7: OFF

# 3. Test reconnection (disconnect gateway, reconnect)
# Expected output:
# ❌ Connection failed: ..., retrying in 5s
# ✅ KNX connected, channel_id: 43

Success Criteria

  • Connects to KNX/IP gateway successfully
  • Receives and parses TUNNELING_REQUEST frames
  • Router dispatches to correct producers based on group address
  • Records receive parsed KNX values
  • Automatic reconnection works after network disruption
  • Outbound publishing sends GroupValueWrite (if implemented)
  • No memory leaks over 24-hour run
  • Logs connection status and errors appropriately

Reference Materials

Implementation Reference:

  • aimdb-mqtt-connector/ - Complete pattern reference (follow exactly)
  • aimdb-core/src/connector.rs - Connector trait
  • aimdb-core/src/router.rs - Router implementation
  • examples/tokio-mqtt-connector-demo/ - Tokio patterns

Protocol Specifications:

  • KNXnet/IP Core v1.0 specification
  • cEMI specification
  • DPT (Data Point Types) reference

Design Documents:

  • docs/design/aimdb-knx-connector-task.md - Complete implementation guide
  • docs/design/012-M5-connector-development-guide.md - Generic connector patterns

Hardware Documentation:

  • SCN-IP000.03 (MDT) - Primary test device

Dependencies

New Dependencies:

[features]
default = ["std"]
std = ["aimdb-core/std"]
tokio-runtime = ["std", "tokio", "aimdb-tokio-adapter"]
embassy-runtime = ["aimdb-core/alloc", "embassy-net", "embassy-sync", "aimdb-embassy-adapter"]

[dependencies]
aimdb-core = { path = "../aimdb-core" }
tokio = { workspace = true, optional = true }
embassy-net = { workspace = true, optional = true }
embassy-sync = { workspace = true, optional = true }
static-cell = { version = "2.0", optional = true }

[dev-dependencies]
aimdb-tokio-adapter = { path = "../aimdb-tokio-adapter" }
tokio = { workspace = true, features = ["full"] }
tracing-subscriber = "0.3"

No External KNX Crates:

  • Rationale: Limited options with GPL licensing or poor maintenance
  • Direct UDP implementation gives full control and no_std compatibility
  • Protocol is well-documented and validated against hardware

Risks and Mitigations

Risk Impact Mitigation
Protocol complexity High Reference implementation validated against hardware
Gateway compatibility Medium Test with multiple gateway models (SCN-IP000.03, Gira X1)
Connection stability Medium Robust reconnection logic in background task
Embassy network stack integration Medium Follow MQTT connector pattern, use SendFutureWrapper, test on real hardware

Timeline Estimate

Phase 1: Core Implementation (4-6 weeks)

  • Week 1: Core types, URL parsing, protocol frames (shared)
  • Week 2: Tokio implementation (ConnectorBuilder, background task, connection)
  • Week 3: Router integration, telegram parsing, dispatch (Tokio)
  • Week 4: Embassy implementation (network stack, SendFutureWrapper patterns)
  • Week 5: Outbound publishing (both runtimes), testing, refinement
  • Week 6: Documentation, examples (both runtimes), polish

Acceptance Criteria

  • Code compiles with cargo build --features tokio-runtime
  • Code compiles with cargo build --features embassy-runtime
  • Code cross-compiles for embedded: cargo build --target thumbv7em-none-eabihf --features embassy-runtime
  • All unit tests pass: cargo test --all-features
  • Clippy clean: cargo clippy --all-targets --all-features -- -D warnings
  • Formatted: cargo fmt --all -- --check
  • Tokio: Connects to real KNX/IP gateway
  • Tokio: Receives and dispatches KNX telegrams correctly
  • Embassy: Connects to real KNX/IP gateway (on hardware)
  • Embassy: Receives and dispatches KNX telegrams correctly (on hardware)
  • Examples demonstrate bidirectional flow (both runtimes)
  • Documentation complete (README, rustdoc, CHANGELOG)
  • Follows MQTT connector pattern exactly (dual runtime support)

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions