Chapter 209: KNX IP Gateway Implementation
Chapter Objectives
Upon completing this chapter, you will be able to:
- Understand the role and function of a KNX IP Gateway.
- Differentiate between KNX IP Routing and KNX IP Tunneling from a gateway perspective.
- Identify the hardware and software components needed to build an ESP32-based KNX IP Gateway.
- Develop C code for an ESP32 to act as a simple KNX IP Tunneling server, bridging KNX TP to KNX IP.
- Comprehend the data flow and translation required between KNX TP telegrams and KNXnet/IP messages.
- Recognize considerations for handling multiple clients and maintaining connection states in a tunneling gateway.
- Appreciate the specific requirements and suitability of different ESP32 variants for gateway applications.
- Troubleshoot common issues when developing and deploying an ESP32 KNX IP Gateway.
Introduction
In the previous chapter, we explored how an ESP32 can act as an endpoint device on either a KNX TP bus (via a TPUART module) or a KNX IP network. However, one of the powerful applications of KNX IP is its ability to bridge existing KNX TP installations to IP-based networks. This is where KNX IP Gateways come into play. A KNX IP Gateway serves as a bidirectional translator, allowing communication between devices on a KNX TP bus and devices or software on an IP network (like Ethernet or Wi-Fi).
The ESP32, with its combination of UART peripherals (for TPUART communication) and robust networking capabilities (Wi-Fi and, on some variants, Ethernet), is well-suited for creating custom KNX IP gateway solutions. Such a gateway can enable various scenarios, such as allowing ETS software on a PC to commission a KNX TP line over Wi-Fi, integrating KNX systems with IP-based building management systems (BMS), or enabling custom mobile applications to control KNX devices.
This chapter will delve into the principles of KNX IP gateways and guide you through the development of an ESP32-based solution that can act as a KNX IP Tunneling server, effectively bridging a KNX TP bus to an IP network.
Theory
What is a KNX IP Gateway?
A KNX IP Gateway is a device that connects a KNX Twisted Pair (TP) bus segment to an IP network. Its primary function is to forward KNX telegrams between these two different communication media, translating them into the appropriate formats for each network.
Key functions of a KNX IP Gateway:
- Media Conversion: Translates the electrical signals and protocol specifics of KNX TP to the packet-based communication of IP networks, and vice-versa.
- Addressing: Handles the mapping or forwarding of KNX addresses (Physical and Group Addresses) across the IP boundary.
- Protocol Encapsulation/Decapsulation: Wraps KNX TP telegrams (typically as cEMI frames) into KNXnet/IP UDP packets for transmission over IP, and extracts cEMI frames from incoming KNXnet/IP packets to send them onto the KNX TP bus.
KNX IP Gateway Modes
KNX IP Gateways typically operate in one of two main modes, as defined by the KNXnet/IP standard:
- KNX IP Routing (Router Mode):
- In this mode, the gateway acts as a KNX IP Router. It forwards KNX telegrams between the TP bus and the IP network using IP multicast.
- KNX telegrams from the TP bus are encapsulated into KNXnet/IP routing frames (cEMI frames within UDP multicast packets) and sent to a specific multicast IP address (default: 224.0.23.12) on UDP port 3671.
- Conversely, the gateway listens for KNXnet/IP routing frames on this multicast address. When received, it decapsulates the cEMI frame and transmits it onto the connected KNX TP line, if the telegram is relevant (e.g., not originating from the same line, routing counter > 0).
- This mode allows for many-to-many communication on the IP side, as any IP device joining the multicast group can send/receive these KNX telegrams. It’s suitable for creating an IP backbone connecting multiple KNX TP lines or areas.
- The gateway itself doesn’t maintain individual connection states with IP clients in this mode for data exchange; it simply forwards to/from the multicast group.
- KNX IP Tunneling (Interface/Tunneling Server Mode):
- In this mode, the gateway acts as a KNX IP Interface or Tunneling Server. It provides one or more point-to-point “tunnels” for IP-based clients (like ETS, visualization software, or custom applications) to communicate with the KNX TP bus.
- Clients establish a connection with the gateway using specific KNXnet/IP services (e.g.,
CONNECT_REQUEST
). Once a tunnel is established, the client can send KNX telegrams (encapsulated inTUNNELLING_REQUEST
messages) to the gateway, which then forwards them to the TP bus. - Telegrams from the TP bus destined for (or monitored by) the client are encapsulated by the gateway into
TUNNELLING_REQUEST
messages and sent to that specific client over its established tunnel. - The gateway needs to manage individual connection states for each active tunnel, including channel IDs, sequence counters, and often heartbeats (
CONNECTIONSTATE_REQUEST
) to ensure the tunnel is alive. - Commercial KNX IP Interfaces typically support multiple concurrent tunneling connections (e.g., 1 to 5 or more).
- This mode is commonly used for programming KNX devices with ETS, for connecting to visualization systems, or when a direct, stateful connection to the KNX bus from an IP application is required.
Feature | KNX IP Routing | KNX IP Tunneling |
---|---|---|
Communication Type | Many-to-Many (Multicast) | Point-to-Point (Unicast) |
Connection State | Stateless (for data exchange) | Stateful (manages connections/tunnels) |
Primary Use Case | Creating an IP backbone to connect different KNX TP lines or areas. Fast, efficient telegram forwarding between segments. | Connecting a specific client (like ETS or a visualization app) to the KNX bus. Programming, diagnostics, and control. |
Network Address | Uses multicast address 224.0.23.12 |
Uses the gateway’s specific unicast IP address. |
ESP32 Complexity | Simpler logic (join multicast, forward packets), but relies on a well-configured network that supports multicast. | More complex logic (manage client state, channels, sequence numbers, heartbeats), but more direct and reliable for client applications. |
For an ESP32-based custom gateway, implementing a Tunneling Server is often a practical starting point, as it allows direct interaction with tools like ETS. A simple router is also feasible.
Data Flow in a KNX IP Tunneling Gateway
Consider an ESP32 acting as a KNX IP Tunneling Server:
1. From KNX TP to KNX IP Client:
- The TPUART module connected to the ESP32 receives a telegram from the KNX TP bus.
- The ESP32’s UART task reads the telegram data from the TPUART. This data might be raw or partially decoded by the TPUART.
- The ESP32 parses this data to form a standard KNX cEMI (Common External Message Interface) frame. A cEMI frame typically includes the message code (e.g., L_Data.ind for an incoming data indication), control fields, source physical address, destination address (physical or group), and the actual data payload (APCI + data).
- If there’s an active tunneling client connected to the ESP32 gateway:
- The ESP32’s KNX IP task encapsulates this cEMI frame within a TUNNELLING_REQUEST message. This involves prepending a KNXnet/IP header and a Tunneling header (with channel ID, sequence counter).
- The complete KNXnet/IP UDP packet is sent to the connected client’s IP address and port.
2. From KNX IP Client to KNX TP:
- The ESP32’s KNX IP task receives a KNXnet/IP UDP packet from a connected tunneling client.
- It parses the packet, expecting a TUNNELLING_REQUEST (or other control messages).
- If it’s a TUNNELLING_REQUEST, it extracts the embedded cEMI frame. This frame usually represents a command to be sent on the KNX bus (e.g., L_Data.req for a write request).
- The ESP32 extracts the necessary information from the cEMI frame (destination group address, APCI, data).
- The ESP32’s UART task formats this information according to the specific serial protocol of the connected TPUART module.
- The formatted command is sent via UART to the TPUART module, which then transmits the corresponding telegram onto the KNX TP bus.
- The ESP32 gateway then typically sends a TUNNELLING_ACK back to the IP client to acknowledge receipt of its TUNNELLING_REQUEST.
sequenceDiagram participant Client as Client (ETS) participant IPTask as ESP32 GW (IP Task) participant UARTTask as ESP32 GW (UART Task) participant TPUART participant KNXBus as KNX TP Bus rect rgb(219, 234, 254) note over Client, KNXBus: Path 1: IP Client to KNX TP Bus Client->>+IPTask: TUNNELLING_REQUEST (contains cEMI frame) IPTask->>Client: TUNNELLING_ACK IPTask->>+UARTTask: Queue cEMI frame for sending UARTTask->>+TPUART: Format cEMI to TPUART serial command TPUART->>+KNXBus: Transmit KNX Telegram deactivate KNXBus deactivate TPUART deactivate UARTTask deactivate IPTask end rect rgb(209, 250, 229) note over Client, KNXBus: Path 2: KNX TP Bus to IP Client KNXBus->>+TPUART: KNX Telegram Received TPUART->>+UARTTask: Send serial data to ESP32 UARTTask->>+IPTask: Parse data into cEMI and Queue for IP IPTask->>+Client: Encapsulate cEMI and Send TUNNELLING_REQUEST Client->>IPTask: TUNNELLING_ACK deactivate Client deactivate IPTask deactivate UARTTask deactivate TPUART end
Key KNXnet/IP Services for a Tunneling Gateway
A minimal KNX IP Tunneling server (gateway) needs to handle:
CONNECT_REQUEST
(0x0205) /CONNECT_RESPONSE
(0x0206): To allow clients to establish a control connection and request a data/tunneling connection. The gateway responds with a channel ID and status.CONNECTIONSTATE_REQUEST
(0x0207) /CONNECTIONSTATE_RESPONSE
(0x0208): For clients to query the state of a connection or for the gateway to act as a heartbeat mechanism. Often, if a gateway doesn’t receive communication or heartbeats from a client for a certain timeout, it will close the tunnel.DISCONNECT_REQUEST
(0x0209) /DISCONNECT_RESPONSE
(0x020A): For clients to gracefully close a connection, or for the gateway to signal a disconnect. The gateway must free up the resources (e.g., tunnel slot) associated with the disconnected client.TUNNELLING_REQUEST
(0x0420): Used by both client and gateway to send actual KNX data (as cEMI frames) through the established tunnel.TUNNELLING_ACK
(0x0421): Sent by the receiver of aTUNNELLING_REQUEST
to acknowledge its receipt. This is important for reliable communication over UDP.
Each TUNNELLING_REQUEST
contains a sequence counter. The receiver must send a TUNNELLING_ACK
with the same sequence counter. Both client and gateway must track these sequence counters for each active tunnel.
ESP32 as a Gateway: Hardware Setup
- ESP32 Module: Any variant with Wi-Fi (e.g., ESP32, ESP32-S3, ESP32-C3, ESP32-C6) or Ethernet capability (ESP32 with external PHY, or variants like ESP32-H2 with potential for SPI-Ethernet).
- KNX TP Interface: A TPUART module (e.g., based on NCN5120, FZE1066, or Siemens TPUART2). This module connects to the ESP32 via UART pins.
- KNX Bus Power Supply: To power the KNX TP bus segment that the TPUART module is connected to.
- Network Connection: Wi-Fi access point or Ethernet switch for the IP side.
Practical Example: ESP32 as a Single-Client KNX IP Tunneling Server
This example demonstrates an ESP32 acting as a very basic KNX IP Tunneling server. It will:
- Connect to Wi-Fi.
- Listen for an incoming KNXnet/IP connection on UDP port 3671.
- Support one client connection at a time.
- Handle
CONNECT_REQUEST
, basicTUNNELLING_REQUEST
(from client to TP and from TP to client),TUNNELLING_ACK
, andDISCONNECT_REQUEST
. - Interface with a (conceptual) TPUART module to send/receive KNX TP telegrams.
Assumptions:
- ESP32 UART2 (TX: GPIO17, RX: GPIO16) is connected to a TPUART module.
- The TPUART module uses a baud rate of 19200, 8E1.
- The gateway will use a simplified handling of TPUART communication (conceptual, as in Chapter 208).
- The gateway itself might be assigned a physical address on the KNX bus (e.g., 1.1.250) by the TPUART or configuration, but this example focuses on tunneling general bus traffic.
main/knx_ip_gateway_example.c
:
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "driver/uart.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
// Wi-Fi Configuration
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
#define MAX_WIFI_RETRY 10
// TPUART Configuration (Conceptual - matches Chapter 208)
#define TPUART_NUM UART_NUM_2
#define TPUART_TXD_PIN 17
#define TPUART_RXD_PIN 16
#define BUF_SIZE 1024
// KNX IP Gateway Configuration
#define KNX_IP_PORT 3671
#define MAX_CLIENTS 1 // For simplicity, this example supports only one client
static const char *TAG = "KNX_GW";
// Event group for Wi-Fi
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
// Queues for message passing between TPUART task and KNX IP task
static QueueHandle_t xQueueKnxTpToIp; // cEMI frames from TP bus to IP client
static QueueHandle_t xQueueKnxIpToTp; // cEMI frames from IP client to TP bus
// KNXnet/IP Service Identifiers (from Chapter 208, abbreviated)
#define KNXNETIP_CORE_CONNECT_REQUEST 0x0205
#define KNXNETIP_CORE_CONNECT_RESPONSE 0x0206
#define KNXNETIP_CORE_CONNECTIONSTATE_REQUEST 0x0207
#define KNXNETIP_CORE_CONNECTIONSTATE_RESPONSE 0x0208
#define KNXNETIP_CORE_DISCONNECT_REQUEST 0x0209
#define KNXNETIP_CORE_DISCONNECT_RESPONSE 0x020A
#define KNXNETIP_TUNNEL_TUNNELLING_REQUEST 0x0420
#define KNXNETIP_TUNNEL_TUNNELLING_ACK 0x0421
// Basic KNXnet/IP Header
typedef struct __attribute__((packed)) {
uint8_t header_len;
uint8_t protocol_version;
uint16_t service_type_id;
uint16_t total_len;
} knx_ip_header_t;
// Simplified cEMI Frame structure (L_Data.ind / L_Data.req)
// Max cEMI frame size for tunneling is typically around 22 bytes for TP1
#define CEMI_FRAME_MAX_LEN 32
typedef struct {
uint8_t len; // Actual length of cEMI data
uint8_t data[CEMI_FRAME_MAX_LEN];
} cemi_frame_t;
// Client State Structure
typedef struct {
bool active;
uint8_t channel_id;
uint8_t seq_counter_rx; // Expected sequence counter from this client
uint8_t seq_counter_tx; // Next sequence counter to send to this client
struct sockaddr_in client_addr;
TickType_t last_seen;
} knx_client_state_t;
static knx_client_state_t clients[MAX_CLIENTS];
static int gateway_socket = -1;
// --- TPUART Task (Conceptual) ---
// This task would interface with a real TPUART module
// For this example, it simulates receiving a cEMI frame and allows sending one
void tpuart_init_conceptual() {
uart_config_t uart_config = {
.baud_rate = 19200, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_EVEN,
.stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT,
};
ESP_ERROR_CHECK(uart_driver_install(TPUART_NUM, BUF_SIZE * 2, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(TPUART_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(TPUART_NUM, TPUART_TXD_PIN, TPUART_RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_LOGI(TAG, "Conceptual TPUART initialized.");
}
// Simulate sending a cEMI frame to KNX TP bus via TPUART
void tpuart_send_cemi_to_knx_bus(const cemi_frame_t *cemi) {
ESP_LOGI(TAG, "TPUART: Sending cEMI frame to KNX TP bus (Length: %d)", cemi->len);
// Actual implementation:
// 1. Convert cEMI frame to TPUART specific serial protocol
// 2. uart_write_bytes(...)
// This is highly dependent on the TPUART module.
// For now, just log it.
esp_log_buffer_hex(TAG, cemi->data, cemi->len);
// Simulate sending to actual bus for loopback testing without real hardware
// If you had a real TPUART, you would not do this loopback.
// This part is for simplified testing of the gateway logic.
if (clients[0].active) { // If a client is connected, "loop back" this message as if it came from bus
cemi_frame_t received_cemi_for_ip_client;
memcpy(&received_cemi_for_ip_client, cemi, sizeof(cemi_frame_t));
// Modify if needed, e.g., change message code from L_Data.req to L_Data.ind
// For simplicity, we'll just forward it as is.
if (xQueueSend(xQueueKnxTpToIp, &received_cemi_for_ip_client, pdMS_TO_TICKS(100)) != pdPASS) {
ESP_LOGW(TAG, "TPUART: Failed to queue cEMI to IP task (simulated loopback)");
} else {
ESP_LOGI(TAG, "TPUART: Queued cEMI for IP client (simulated loopback)");
}
}
}
// Task to handle communication with TPUART (receiving from KNX TP, sending from IP)
static void tpuart_task(void *pvParameters) {
tpuart_init_conceptual();
cemi_frame_t cemi_from_ip;
// Simulate receiving a KNX TP telegram periodically for testing
// In a real system, this would come from uart_read_bytes and parsing TPUART protocol
cemi_frame_t simulated_tp_telegram;
simulated_tp_telegram.len = 11; // Example L_Data.ind, GA 1/1/1, DPT 1.001 ON
// L_Data.ind: MC=0x29, AddInfoLen=0x00
// Ctrl1=0xBC, Ctrl2=0xE0 (standard frame, group addr, hop 6)
// SrcPA=0x1101 (1.1.1), DestGA=0x0901 (1/1/1 in 3-level), Len=1
// APCI+Data = 0x81 (GroupValueWrite, ON)
uint8_t sample_cemi_data[] = {0x29, 0x00, 0xBC, 0xE0, 0x11, 0x01, 0x09, 0x01, 0x01, 0x00, 0x81}; // Note: APCI for L_Data.ind is just data_len (idx 8), then TPCI/APCI (idx 9), then data (idx 10)
// For L_Data.ind: APCI is 2bits (upper two of byte 9), Data is in byte 10
// Example: Byte 9 = 0b00000000 (TPCI, APCI=00 for GroupValueResponse/Read)
// Byte 10 = 0x01 for value
// For GroupValueWrite Indication it should be APCI=GroupValueWrite (0b10)
// Corrected sample cEMI for L_Data.ind, group write indication, value 1 (ON) to GA 1/1/1 from 1.1.1
// Message Code: 0x29 (L_Data.ind)
// Additional Info Length: 0x00
// Control Field 1: 0xBC (Standard Frame, Priority System, Repeat OK, System Broadcast)
// Control Field 2: 0xE0 (Group Address, Hop Count = 6)
// Source Address: 0x1101 (e.g. 1.1.1)
// Destination Address: 0x0901 (e.g. 1/1/1 for 3-level)
// Data Length: 0x01 (1 byte of payload: APCI_DATA)
// TPCI/APCI: (APCI = 0b10xx for GroupValue_Write in cEMI payload) -> 0x00 here in this position, APCI is part of data for L_DATA.req
// For L_Data.ind, the last 2 bytes are Data Service (1byte) + Data (1 byte for DPT 1.001)
// The actual encoding can be complex. For simplicity, let's assume the TPUART provides a cEMI-like structure.
// Payload for DPT 1.001 "ON", as a GroupValueWrite indication: [APCI (2bit) + Data (6bit)]
// APCI 0b10 (GroupValueWrite) + data 0b000001 (ON). So data byte = 0x81.
// Let's use a simpler cEMI for this simulation:
// This is what the IP client might send, and we echo it as if it came from bus after a bit
uint8_t sim_tp_payload[] = {
0x11, 0x00, // Message Code (L_Data.req), AddInfoLen=0
0xBC, 0xF0, // Control1, Control2 (dest group, hop count from 1.1.F0)
0x11, 0xF0, // Source Physical Address (e.g., 1.1.240, a simulated sensor)
0x08, 0x01, // Destination Group Address (e.g., 1/0/1)
0x01, // Data Length (1 byte for APCI+Data)
0x00, // TPCI=0, APCI=0 (GroupValueRead) - for a sensor value, this might be GroupValueResponse
// Or for a switch sending its state: APCI=GroupValueWrite
0x81 // Data: 1 for "ON" if GroupValueWrite (APCI needs to reflect write)
// if it's a GroupValueRead on GA 1/0/1, then this byte isn't used, Data length would be 0.
// For a GroupValueWrite from a sensor: APCI (e.g. 0b10 for write in upper 2 bits of data) + data (e.g. 01 for ON)
// Let's make it a GroupValueWrite from 1.1.240 to 1/0/1 with value ON (0x81)
};
memcpy(simulated_tp_telegram.data, sim_tp_payload, sizeof(sim_tp_payload));
simulated_tp_telegram.len = sizeof(sim_tp_payload);
// To make it an L_Data.ind (from bus to IP client), change message code
simulated_tp_telegram.data[0] = 0x29; // L_Data.ind
TickType_t last_simulated_send = xTaskGetTickCount();
while (1) {
// 1. Check for cEMI frames from KNX IP task to send to TP bus
if (xQueueReceive(xQueueKnxIpToTp, &cemi_from_ip, pdMS_TO_TICKS(10)) == pdPASS) {
tpuart_send_cemi_to_knx_bus(&cemi_from_ip);
}
// 2. Simulate receiving from TPUART (replace with actual uart_read_bytes and parsing)
if ((xTaskGetTickCount() - last_simulated_send) > pdMS_TO_TICKS(15000)) { // Every 15s
if (clients[0].active) { // Only if a client is connected
ESP_LOGI(TAG, "TPUART: Simulating incoming KNX TP telegram for IP client.");
// Modify the simulated telegram slightly if needed (e.g. source PA)
if (xQueueSend(xQueueKnxTpToIp, &simulated_tp_telegram, pdMS_TO_TICKS(100)) != pdPASS) {
ESP_LOGW(TAG, "TPUART: Failed to queue simulated cEMI to IP task");
}
}
last_simulated_send = xTaskGetTickCount();
}
// In a real system:
// int len = uart_read_bytes(TPUART_NUM, uart_rx_buffer, BUF_SIZE - 1, pdMS_TO_TICKS(20));
// if (len > 0) { parse_tpuart_data_to_cemi(uart_rx_buffer, len, &received_cemi);
// xQueueSend(xQueueKnxTpToIp, &received_cemi, ...); }
}
}
// --- Wi-Fi Functions (from Chapter 208) ---
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < MAX_WIFI_RETRY) { esp_wifi_connect(); s_retry_num++; ESP_LOGI(TAG, "Retrying Wi-Fi"); }
else { xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); }
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0; xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init_sta(void) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id, instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &instance_got_ip));
wifi_config_t wifi_config = { .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS }};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
if (xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT) ESP_LOGI(TAG, "Connected to AP"); else ESP_LOGE(TAG, "Failed to connect to AP");
}
// --- KNX IP Gateway Task ---
void send_knx_ip_response(int sock, const struct sockaddr_in *client_addr, const void *data, size_t len) {
int err = sendto(sock, data, len, 0, (struct sockaddr *)client_addr, sizeof(*client_addr));
if (err < 0) {
ESP_LOGE(TAG, "Error sending KNX IP response: errno %d", errno);
}
}
// Helper to build and send a TUNNELLING_REQUEST to the active client
void forward_cemi_to_ip_client(const cemi_frame_t *cemi) {
if (!clients[0].active || gateway_socket < 0) return;
uint8_t tx_buffer[sizeof(knx_ip_header_t) + 4 /*TunnelHdr*/ + CEMI_FRAME_MAX_LEN];
knx_ip_header_t *hdr = (knx_ip_header_t *)tx_buffer;
hdr->header_len = 0x06;
hdr->protocol_version = 0x10;
hdr->service_type_id = htons(KNXNETIP_TUNNEL_TUNNELLING_REQUEST);
// Tunneling Header (4 bytes: struct_len, channel_id, seq_counter, reserved)
tx_buffer[sizeof(knx_ip_header_t) + 0] = 0x04; // Structure length
tx_buffer[sizeof(knx_ip_header_t) + 1] = clients[0].channel_id;
tx_buffer[sizeof(knx_ip_header_t) + 2] = clients[0].seq_counter_tx++; // Use and increment
tx_buffer[sizeof(knx_ip_header_t) + 3] = 0x00; // Reserved
memcpy(tx_buffer + sizeof(knx_ip_header_t) + 4, cemi->data, cemi->len);
uint16_t total_len = sizeof(knx_ip_header_t) + 4 + cemi->len;
hdr->total_len = htons(total_len);
ESP_LOGI(TAG, "KNX_IP: Forwarding cEMI to client. ChID: %d, SeqTX: %d, Len: %d",
clients[0].channel_id, clients[0].seq_counter_tx -1, cemi->len);
send_knx_ip_response(gateway_socket, &clients[0].client_addr, tx_buffer, total_len);
}
static void knx_ip_gateway_task(void *pvParameters) {
char rx_buffer[256]; // Buffer for incoming UDP packets
struct sockaddr_in local_addr;
// Wait for Wi-Fi
xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
if (!(xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT)) {
ESP_LOGE(TAG, "Wi-Fi not connected. Gateway task exiting.");
vTaskDelete(NULL);
return;
}
// Create UDP socket
gateway_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (gateway_socket < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
vTaskDelete(NULL);
return;
}
// Bind to local address and KNX_IP_PORT
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
local_addr.sin_port = htons(KNX_IP_PORT);
if (bind(gateway_socket, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
ESP_LOGE(TAG, "Socket bind failed: errno %d", errno);
close(gateway_socket); gateway_socket = -1;
vTaskDelete(NULL);
return;
}
ESP_LOGI(TAG, "KNX IP Gateway socket bound, listening on port %d", KNX_IP_PORT);
clients[0].active = false; // Initialize client state
while (1) {
struct sockaddr_in source_addr_client; // To store client's address
socklen_t socklen = sizeof(source_addr_client);
cemi_frame_t cemi_from_tp;
// 1. Check for cEMI frames from TPUART task to forward to IP client
if (clients[0].active && xQueueReceive(xQueueKnxTpToIp, &cemi_from_tp, 0) == pdPASS) {
ESP_LOGI(TAG, "KNX_IP: Received cEMI from TP queue to forward.");
forward_cemi_to_ip_client(&cemi_from_tp);
}
// 2. Poll for incoming UDP packets (non-blocking with timeout)
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 10000; // 10ms timeout for select
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(gateway_socket, &rfds);
int s = select(gateway_socket + 1, &rfds, NULL, NULL, &tv);
if (s > 0 && FD_ISSET(gateway_socket, &rfds)) {
int len = recvfrom(gateway_socket, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr_client, &socklen);
if (len < 0) {
ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
continue;
}
rx_buffer[len] = 0; // Null-terminate (though not strictly needed for binary)
// ESP_LOGI(TAG, "Received %d bytes from %s:%d", len, inet_ntoa(source_addr_client.sin_addr), ntohs(source_addr_client.sin_port));
knx_ip_header_t *req_hdr = (knx_ip_header_t *)rx_buffer;
uint16_t service_type = ntohs(req_hdr->service_type_id);
if (service_type == KNXNETIP_CORE_CONNECT_REQUEST) {
ESP_LOGI(TAG, "KNX_IP: CONNECT_REQUEST received.");
if (!clients[0].active) {
clients[0].active = true;
clients[0].channel_id = 1; // Assign a channel ID
clients[0].seq_counter_rx = 0;
clients[0].seq_counter_tx = 0;
memcpy(&clients[0].client_addr, &source_addr_client, sizeof(struct sockaddr_in));
clients[0].last_seen = xTaskGetTickCount();
// Send CONNECT_RESPONSE
uint8_t resp_buffer[sizeof(knx_ip_header_t) + 8 + 8]; // Header + HPAI_Status + CRI
knx_ip_header_t *resp_hdr = (knx_ip_header_t *)resp_buffer;
resp_hdr->header_len = 0x06;
resp_hdr->protocol_version = 0x10;
resp_hdr->service_type_id = htons(KNXNETIP_CORE_CONNECT_RESPONSE);
// Body: ChannelID (1), Status (1), HPAI (8), CRI Data (variable)
resp_buffer[sizeof(knx_ip_header_t) + 0] = clients[0].channel_id; // Channel ID
resp_buffer[sizeof(knx_ip_header_t) + 1] = 0x00; // Status E_NO_ERROR
// HPAI Data Endpoint (where client should send tunneling requests to)
// For simplicity, we assume client sends to same IP/Port of gateway. Real gateway might specify.
resp_buffer[sizeof(knx_ip_header_t) + 2] = 0x08; // HPAI Structure len
resp_buffer[sizeof(knx_ip_header_t) + 3] = 0x01; // Host Proto Code UDP
// IP Addr and Port can be gateway's IP/Port or specific data endpoint.
// Let's put 0.0.0.0:0 indicating client should use control endpoint for data.
memset(&resp_buffer[sizeof(knx_ip_header_t) + 4], 0, 4); // IP Address
memset(&resp_buffer[sizeof(knx_ip_header_t) + 8], 0, 2); // Port
// CRI (Connection Response Information) - includes assigned PA, etc.
// For simplicity, just provide minimal CRI: type = tunneling
// Actual CRI would have more info like max APDU length.
// This example CRI is very basic.
uint8_t cri_data[] = {0x04, 0x04, 0x02, 0x00}; // Len 4, Type Tunneling, KnxLayer Link, Reserved
memcpy(&resp_buffer[sizeof(knx_ip_header_t) + 10], cri_data, sizeof(cri_data));
resp_hdr->total_len = htons(sizeof(knx_ip_header_t) + 2 + 8 + sizeof(cri_data)); // Minimal length
send_knx_ip_response(gateway_socket, &source_addr_client, resp_buffer, ntohs(resp_hdr->total_len));
ESP_LOGI(TAG, "KNX_IP: Client connected. Channel ID: %d", clients[0].channel_id);
} else {
ESP_LOGW(TAG, "KNX_IP: CONNECT_REQUEST received, but gateway busy (max clients reached).");
// Send CONNECT_RESPONSE with error E_NO_MORE_CONNECTIONS (0x24)
// ... (implementation omitted for brevity)
}
} else if (clients[0].active && memcmp(&clients[0].client_addr, &source_addr_client, sizeof(struct sockaddr_in)) == 0) {
// Process only if client is the one we know and is active
clients[0].last_seen = xTaskGetTickCount();
uint8_t client_channel_id = rx_buffer[sizeof(knx_ip_header_t) + 1];
uint8_t client_seq_counter = rx_buffer[sizeof(knx_ip_header_t) + 2];
if (client_channel_id != clients[0].channel_id) {
ESP_LOGW(TAG, "KNX_IP: Received msg with invalid Channel ID %d, expected %d", client_channel_id, clients[0].channel_id);
continue;
}
// Basic sequence counter check (simple wrap-around, no out-of-order handling)
// KNX Spec: "If the received sequence number is less than the stored one (considering 8-bit wrap-around)
// then the frame is an old one and shall be acknowledged and discarded."
// This check is simplified:
// if (client_seq_counter != clients[0].seq_counter_rx) {
// ESP_LOGW(TAG, "KNX_IP: Seq counter mismatch. Got %d, Exp %d. Sending ACK for %d, ignoring data.",
// client_seq_counter, clients[0].seq_counter_rx, client_seq_counter);
// // Send ACK for the received sequence counter anyway
// }
if (service_type == KNXNETIP_TUNNEL_TUNNELLING_REQUEST) {
ESP_LOGI(TAG, "KNX_IP: TUNNELLING_REQUEST from client. SeqRX: %d", client_seq_counter);
// Extract cEMI
cemi_frame_t cemi_to_tp;
int cemi_offset = sizeof(knx_ip_header_t) + 4; // Skip KNX IP Hdr + Tunnel Hdr
cemi_to_tp.len = ntohs(req_hdr->total_len) - cemi_offset;
if (cemi_to_tp.len > 0 && cemi_to_tp.len <= CEMI_FRAME_MAX_LEN) {
memcpy(cemi_to_tp.data, rx_buffer + cemi_offset, cemi_to_tp.len);
// Queue cEMI for TPUART task
if (xQueueSend(xQueueKnxIpToTp, &cemi_to_tp, pdMS_TO_TICKS(100)) != pdPASS) {
ESP_LOGE(TAG, "KNX_IP: Failed to queue cEMI for TP bus.");
} else {
ESP_LOGI(TAG, "KNX_IP: Queued cEMI for TP bus. Len: %d", cemi_to_tp.len);
}
} else {
ESP_LOGE(TAG, "KNX_IP: Invalid cEMI length in Tunnelling_Request: %d", cemi_to_tp.len);
}
clients[0].seq_counter_rx = client_seq_counter + 1; // Update expected next
// Send TUNNELLING_ACK
uint8_t ack_buffer[sizeof(knx_ip_header_t) + 4 + 1]; // Header + TunnelHdr + Status
knx_ip_header_t *ack_hdr = (knx_ip_header_t *)ack_buffer;
ack_hdr->header_len = 0x06;
ack_hdr->protocol_version = 0x10;
ack_hdr->service_type_id = htons(KNXNETIP_TUNNEL_TUNNELLING_ACK);
ack_hdr->total_len = htons(sizeof(knx_ip_header_t) + 4 + 1);
ack_buffer[sizeof(knx_ip_header_t) + 0] = 0x04; // Tunnel Header len
ack_buffer[sizeof(knx_ip_header_t) + 1] = clients[0].channel_id;
ack_buffer[sizeof(knx_ip_header_t) + 2] = client_seq_counter; // ACK the received sequence
ack_buffer[sizeof(knx_ip_header_t) + 3] = 0x00; // Reserved
ack_buffer[sizeof(knx_ip_header_t) + 4] = 0x00; // Status E_NO_ERROR
send_knx_ip_response(gateway_socket, &clients[0].client_addr, ack_buffer, sizeof(ack_buffer));
ESP_LOGI(TAG, "KNX_IP: Sent TUNNELLING_ACK for seq %d", client_seq_counter);
} else if (service_type == KNXNETIP_CORE_DISCONNECT_REQUEST) {
ESP_LOGI(TAG, "KNX_IP: DISCONNECT_REQUEST received. ChID: %d", client_channel_id);
clients[0].active = false;
// Send DISCONNECT_RESPONSE
uint8_t dresp_buffer[sizeof(knx_ip_header_t) + 2]; // Header + ChID + Status
knx_ip_header_t *dresp_hdr = (knx_ip_header_t *)dresp_buffer;
dresp_hdr->header_len = 0x06;
dresp_hdr->protocol_version = 0x10;
dresp_hdr->service_type_id = htons(KNXNETIP_CORE_DISCONNECT_RESPONSE);
dresp_hdr->total_len = htons(sizeof(knx_ip_header_t) + 2);
dresp_buffer[sizeof(knx_ip_header_t) + 0] = client_channel_id;
dresp_buffer[sizeof(knx_ip_header_t) + 1] = 0x00; // Status E_NO_ERROR
send_knx_ip_response(gateway_socket, &clients[0].client_addr, dresp_buffer, sizeof(dresp_buffer));
ESP_LOGI(TAG, "KNX_IP: Client disconnected.");
} else if (service_type == KNXNETIP_CORE_CONNECTIONSTATE_REQUEST) {
ESP_LOGI(TAG, "KNX_IP: CONNECTIONSTATE_REQUEST received. ChID: %d", client_channel_id);
// Send CONNECTIONSTATE_RESPONSE
uint8_t cs_resp_buffer[sizeof(knx_ip_header_t) + 2]; // Header + ChID + Status
knx_ip_header_t *cs_resp_hdr = (knx_ip_header_t *)cs_resp_buffer;
cs_resp_hdr->header_len = 0x06;
cs_resp_hdr->protocol_version = 0x10;
cs_resp_hdr->service_type_id = htons(KNXNETIP_CORE_CONNECTIONSTATE_RESPONSE);
cs_resp_hdr->total_len = htons(sizeof(knx_ip_header_t) + 2);
cs_resp_buffer[sizeof(knx_ip_header_t) + 0] = client_channel_id;
cs_resp_buffer[sizeof(knx_ip_header_t) + 1] = 0x00; // Status E_NO_ERROR (tunnel is active)
send_knx_ip_response(gateway_socket, &clients[0].client_addr, cs_resp_buffer, sizeof(cs_resp_buffer));
} else {
ESP_LOGW(TAG, "KNX_IP: Received unhandled service type 0x%04X from client.", service_type);
}
} else if (len > 0) { // Packet from an unknown/inactive client
ESP_LOGW(TAG, "KNX_IP: Received %d bytes from unknown or inactive client %s:%d", len,
inet_ntoa(source_addr_client.sin_addr), ntohs(source_addr_client.sin_port));
}
} // end if(select > 0)
// Simple client timeout check (e.g., 60 seconds)
if (clients[0].active && (xTaskGetTickCount() - clients[0].last_seen) > pdMS_TO_TICKS(60000)) {
ESP_LOGW(TAG, "KNX_IP: Client timeout. Channel ID: %d. Disconnecting.", clients[0].channel_id);
clients[0].active = false;
// Optionally send DISCONNECT_REQUEST to client if it has a known structure
}
} // end while(1)
ESP_LOGI(TAG, "KNX IP Gateway task_terminating.");
if (gateway_socket != -1) { close(gateway_socket); gateway_socket = -1; }
vTaskDelete(NULL);
}
void app_main(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
wifi_init_sta();
// Create queues for inter-task communication
// Max 10 cEMI frames in each queue, each frame can be up to CEMI_FRAME_MAX_LEN
xQueueKnxTpToIp = xQueueCreate(10, sizeof(cemi_frame_t));
xQueueKnxIpToTp = xQueueCreate(10, sizeof(cemi_frame_t));
if (!xQueueKnxTpToIp || !xQueueKnxIpToTp) {
ESP_LOGE(TAG, "Failed to create queues. Halting.");
return;
}
xTaskCreate(tpuart_task, "tpuart_task", 4096, NULL, 5, NULL);
xTaskCreate(knx_ip_gateway_task, "knx_ip_gw_task", 4096 * 2, NULL, 5, NULL);
}
CMakeLists.txt (in main directory):
Ensure idf_component_register includes SRCS “knx_ip_gateway_example.c” and REQUIRES FREERTOS esp_wifi esp_event esp_netif nvs_flash lwip driver esp_log.
sdkconfig.defaults (or use idf.py menuconfig
):
CONFIG_ESP_WIFI_SSID="YOUR_WIFI_SSID"
CONFIG_ESP_WIFI_PASSWORD="YOUR_WIFI_PASSWORD"
CONFIG_ESP_MAXIMUM_RETRY=10
CONFIG_LWIP_UDP_RECVMBOX_SIZE=12 # Increase if many small UDP packets are expected
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
Replace Wi-Fi credentials as needed. Increasing UDP receive mailbox size might be beneficial for network-intensive applications.
Build Instructions:
- Save the code and update
CMakeLists.txt
andsdkconfig.defaults
. - Configure Wi-Fi SSID and Password.
idf.py set-target esp32
(or your variant).idf.py build
.
Run/Flash/Observe:
- Flash the firmware:
idf.py -p /dev/ttyUSB0 flash
. - Monitor the serial output:
idf.py -p /dev/ttyUSB0 monitor
. - Prerequisites:
- Connect ESP32 UART2 (GPIO17/16) to a KNX TPUART module.
- Power the KNX TP bus connected to the TPUART module.
- Have a KNX IP client application (like ETS, or a KNX visualization software configured for IP tunneling) on the same Wi-Fi network as the ESP32.
- Observation with ETS (Example):
- The ESP32 will connect to Wi-Fi and start the gateway tasks. It will log “KNX IP Gateway socket bound, listening on port 3671”.
- In ETS, try to create a new KNX IP Interface. Configure it to use the ESP32’s IP address and port 3671.
- When ETS attempts to connect (e.g., by clicking “Test” or trying to use it for programming/monitoring):
- The ESP32 should log “CONNECT_REQUEST received.”
- It should respond, and ETS should indicate a successful connection.
- If ETS sends a command (e.g., reads a value, writes a value to a Group Address):
- The ESP32 should log “TUNNELLING_REQUEST from client.”
- The
tpuart_send_cemi_to_knx_bus
function will log the cEMI frame intended for the TP bus. (With a real TPUART, this would go to the bus). - The ESP32 will send a
TUNNELLING_ACK
back to ETS.
- The
tpuart_task
includes a simulation that periodically queues a “telegram from TP” to the IP client. When this happens:- The ESP32 will log “Forwarding cEMI to client.”
- ETS (if in group monitor mode for the relevant GA) should display this incoming telegram.
- Disconnecting the interface in ETS should trigger a
DISCONNECT_REQUEST
on the ESP32.
Warning: This example is a simplified KNX IP Tunneling server. It supports only one client, has basic sequence number handling, and minimal error checking. A production-grade gateway would require robust handling of multiple clients, sophisticated sequence number validation (including wrap-around and out-of-order packets), more detailed CRI data, reliable heartbeat mechanisms (e.g.,
CONNECTIONSTATE_REQUEST
handling for client timeouts), and comprehensive error management according to KNXnet/IP specifications. The TPUART interaction is also conceptual; a real implementation needs to parse the specific serial protocol of the chosen TPUART module.
Variant Notes
Building a KNX IP Gateway involves both KNX TP interfacing (via UART) and KNX IP networking.
- ESP32, ESP32-S3:
- Excellent choices due to multiple UARTs, robust Wi-Fi, and good processing power. ESP32 also offers an Ethernet MAC (requires external PHY).
- Sufficient RAM and Flash for the gateway logic, KNXnet/IP stack subset, and potentially a small web interface for configuration. PSRAM variants are beneficial for more complex gateways supporting many clients or extensive logging.
- ESP32-S2:
- Good Wi-Fi capabilities and sufficient UARTs. Suitable for Wi-Fi based gateways. Lacks Ethernet MAC.
- ESP32-C3, ESP32-C6:
- RISC-V based, with Wi-Fi and multiple UARTs. Can serve as simpler gateways, especially if the number of concurrent clients or features is limited due to potentially less memory/processing power compared to ESP32/S3.
- ESP32-C6 offers Wi-Fi 6 and 802.15.4 radio, though the latter is not directly used for standard KNX TP/IP.
- ESP32-H2:
- Primarily an 802.15.4 (Thread/Zigbee) + Bluetooth LE MCU. Does not have built-in Wi-Fi.
- To act as a KNX IP Gateway, it would require an external Ethernet PHY connected via SPI (using
esp_eth
driver with an SPI-Ethernet module like W5500 or KSZ8851SNL) or a Wi-Fi module connected via SPI/SDIO if feasible. This adds complexity. - It has UARTs for TPUART connection. If IP connectivity is added externally, it could function as a gateway.
Key Considerations for Gateways:
- Network Stability: Reliable Wi-Fi or Ethernet connection is paramount.
- Processing Power: Handling multiple client sessions, parsing/constructing telegrams, and managing network traffic requires adequate CPU resources.
- Memory (RAM/Flash): Storing client states, buffering telegrams, and the network stack itself consumes memory. For multiple clients, RAM usage increases.
- Concurrent Operations: FreeRTOS tasks are essential for managing the TPUART interface, IP network communication, and client sessions concurrently.
Common Mistakes & Troubleshooting Tips
Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Client (ETS) Cannot Connect | ETS shows “Interface not found” or a connection timeout. ESP32 log shows no incoming UDP packets. | Check Network: Ensure the ESP32 and the PC are on the same Wi-Fi network. Check IP/Port: Verify the ESP32’s IP address from logs and ensure it’s correctly entered in ETS. Check Firewall: Disable the PC’s firewall temporarily to rule out blocked UDP port 3671. |
Client Connects, but No Data | ETS connects successfully, but the group monitor shows no traffic, and sending commands does nothing. ESP32 log may show TUNNELLING_REQUESTS being received. | Check cEMI-TPUART Logic: The error is likely in the translation from the cEMI frame (from IP) to the serial command for your TPUART module. Log the exact bytes you are sending to the TPUART and verify them against its datasheet. Check Queues: Ensure the FreeRTOS queues between your IP task and UART task are not full or blocked. |
Client Disconnects Randomly | The connection works for a short time (e.g., 30-90 seconds) then drops. | Implement Heartbeats: The client (e.g., ETS) is likely sending CONNECTIONSTATE_REQUEST packets as a heartbeat. If your gateway doesn’t respond with a CONNECTIONSTATE_RESPONSE, the client will assume the gateway is down and disconnect. Implement this service. |
Gateway Crashes or Behaves Oddly | The ESP32 crashes with a Guru Meditation Error or hangs when a client connects or sends data. | Check Task Stacks: Network tasks, especially with logging, require larger stack sizes. Increase stack allocation for your gateway tasks (e.g., to 4096*2 bytes). Check for Race Conditions: Ensure data shared between the IP and UART tasks is protected (use queues, which are thread-safe). Avoid accessing shared variables without mutexes. |
Exercises
- Implement CONNECTIONSTATE_REQUEST Handling:Extend the knx_ip_gateway_task in the practical example to fully handle incoming CONNECTIONSTATE_REQUEST messages from the client. The gateway should respond with a CONNECTIONSTATE_RESPONSE indicating the status of the tunnel (e.g., E_NO_ERROR if active). Also, use the client’s last_seen timestamp to disconnect a client if no messages (including heartbeats like CONNECTIONSTATE_REQUEST) are received for a defined timeout period (e.g., 90 seconds).
- Basic KNX IP Router Functionality (Conceptual):Outline the design and then try to implement a very simple KNX IP Router functionality.
- The ESP32 should join the standard KNX IP multicast group (224.0.23.12, port 3671).
- TP to IP: When a (simulated or real) cEMI frame is received from the TPUART task, encapsulate it into a KNXnet/IP Routing frame (UDP multicast packet) and send it to the multicast group.
- IP to TP: When a KNXnet/IP Routing frame is received from the multicast group, decapsulate the cEMI frame and queue it for the TPUART task to send to the KNX TP bus.
- Note: This exercise would not involve managing client connections like tunneling. Focus on the multicast send/receive and cEMI encapsulation/decapsulation for routing frames. Keep routing logic simple (e.g., basic hop count decrement, don’t forward back to originating bus if that info is available).
- Add Filtering to Gateway:Modify the KNX IP Tunneling Gateway example. Add a simple filtering mechanism. For instance, allow the user to define (e.g., via menuconfig or hardcoded array) a list of Group Addresses.
- Only forward KNX TP telegrams to the IP client if their Destination Group Address is in the allowed list.
- Only forward KNX IP TUNNELLING_REQUESTS (cEMI frames) to the TP bus if their Destination Group Address is in the allowed list.Log any filtered-out messages.
Summary
- A KNX IP Gateway bridges a KNX TP bus to an IP network, enabling communication between devices on different media.
- Gateways typically operate in KNX IP Routing mode (multicast, many-to-many) or KNX IP Tunneling mode (point-to-point client connections, e.g., for ETS).
- An ESP32 can act as a custom gateway by using a TPUART module for KNX TP communication and its Wi-Fi/Ethernet for KNX IP communication.
- Implementing a Tunneling Server on ESP32 involves managing UDP sockets, client connection states (channel ID, sequence counters), and translating between cEMI frames and the TPUART’s serial protocol.
- Key KNXnet/IP services for tunneling include
CONNECT
,DISCONNECT
,CONNECTIONSTATE
,TUNNELLING_REQUEST
, andTUNNELLING_ACK
. - Careful handling of KNXnet/IP protocol details, TPUART specifics, and concurrent task operations is crucial for a stable gateway.
- ESP32 variants with robust networking (Wi-Fi/Ethernet) and sufficient processing capabilities (like ESP32, ESP32-S3) are well-suited for gateway applications.
Further Reading
- KNXnet/IP Specifications (KNX Standard Vol 3, Part 8: Communication – KNXnet IP): Essential for detailed understanding of services, frame structures, and sequences. (Available from KNX Association).
- cEMI – Common External Message Interface (KNX Standard Vol 3, Part 4: System Specifications – Communication – Common EMI): Defines the cEMI message format used within KNXnet/IP.
- Your specific TPUART module datasheet: Critical for understanding its serial protocol to interface with the ESP32’s UART.
- Wireshark (with KNXnet/IP dissector): Invaluable tool for analyzing KNX IP network traffic when developing and troubleshooting your gateway. (https://www.wireshark.org)
- ESP-IDF Documentation (v5.x): For Wi-Fi, Sockets, UART, FreeRTOS APIs. (https://docs.espressif.com/projects/esp-idf/en/v5.x/)
- Open Source KNX Stacks/Libraries: Exploring existing open-source KNX projects (e.g., Calimero, KNXDaemon, or libraries for other microcontrollers) can provide insights into KNXnet/IP implementation, even if not directly portable to ESP-IDF.