feat(gateway): add support for KNX TP UART 9-bit mode and enhance UART pin configuration

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-14 22:39:14 +08:00
parent 39ef630608
commit 4553ed32e7
10 changed files with 144 additions and 23 deletions
@@ -2062,12 +2062,14 @@ struct GatewayBridgeService::ChannelRuntime {
const bool commissioning_only = !knx_config.has_value();
ESP_LOGI(kTag,
"gateway=%u KNX/IP start config namespace=%s storedConfig=%d udp=%u tunnel=%d "
"multicast=%d multicastGroup=%s mainGroup=%u tpUart=%d tx=%d rx=%d individual=0x%04x",
"multicast=%d multicastGroup=%s mainGroup=%u tpUart=%d tx=%d rx=%d nineBit=%d "
"individual=0x%04x",
channel.gateway_id, openKnxNamespace().c_str(), !commissioning_only,
static_cast<unsigned>(runtime_config.udp_port), runtime_config.tunnel_enabled,
runtime_config.multicast_enabled, runtime_config.multicast_address.c_str(),
static_cast<unsigned>(runtime_config.main_group), runtime_config.tp_uart.uart_port,
runtime_config.tp_uart.tx_pin, runtime_config.tp_uart.rx_pin,
runtime_config.tp_uart.nine_bit_mode,
runtime_config.individual_address);
knx->setConfig(runtime_config);
knx_router->setConfig(runtime_config);
@@ -2304,6 +2306,7 @@ struct GatewayBridgeService::ChannelRuntime {
cJSON_AddNumberToObject(serial_json, "txPin", effective_knx->tp_uart.tx_pin);
cJSON_AddNumberToObject(serial_json, "rxPin", effective_knx->tp_uart.rx_pin);
cJSON_AddNumberToObject(serial_json, "baudrate", effective_knx->tp_uart.baudrate);
cJSON_AddBoolToObject(serial_json, "nineBitMode", effective_knx->tp_uart.nine_bit_mode);
cJSON_AddItemToObject(knx_json, "tpUart", serial_json);
}
}
@@ -39,6 +39,7 @@ struct GatewayKnxTpUartConfig {
size_t rx_buffer_size{1024};
size_t tx_buffer_size{1024};
uint32_t read_timeout_ms{20};
bool nine_bit_mode{true};
};
enum class GatewayKnxMappingMode : uint8_t {
@@ -348,6 +349,8 @@ class GatewayKnxTpIpRouter {
int tcp_sock_{-1};
int active_tcp_sock_{-1};
int tp_uart_port_{-1};
int tp_uart_tx_pin_{-1};
int tp_uart_rx_pin_{-1};
std::vector<uint32_t> multicast_joined_interfaces_;
TickType_t network_refresh_tick_{0};
std::array<TcpClient, kMaxTcpClients> tcp_clients_{};
+76 -12
View File
@@ -9,6 +9,7 @@
#include "lwip/inet.h"
#include "lwip/sockets.h"
#include "openknx_idf/ets_device_runtime.h"
#include "soc/uart_periph.h"
#include <algorithm>
#include <array>
@@ -173,6 +174,38 @@ std::string ErrnoDetail(const std::string& message, int err) {
return std::string(message) + ": errno=" + std::to_string(err) + " (" + std::strerror(err) + ")";
}
bool ResolveUartIoPin(uart_port_t uart_port, int configured_pin, uint32_t pin_index,
int* resolved_pin) {
if (resolved_pin == nullptr) {
return false;
}
if (configured_pin >= 0) {
*resolved_pin = configured_pin;
return true;
}
if (uart_port < 0 || uart_port >= SOC_UART_NUM || pin_index >= SOC_UART_PINS_COUNT) {
*resolved_pin = UART_PIN_NO_CHANGE;
return false;
}
const int default_pin = uart_periph_signal[uart_port].pins[pin_index].default_gpio;
if (default_pin < 0) {
*resolved_pin = UART_PIN_NO_CHANGE;
return false;
}
*resolved_pin = default_pin;
return true;
}
std::string UartPinDescription(int configured_pin, int resolved_pin) {
if (configured_pin >= 0) {
return std::to_string(configured_pin);
}
if (resolved_pin >= 0) {
return std::to_string(resolved_pin) + " (default from -1)";
}
return "unrouted (-1 with no target default)";
}
std::string Ipv4String(uint32_t network_address) {
const uint32_t address = ntohl(network_address);
char buffer[16]{};
@@ -909,6 +942,9 @@ std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value
config.tp_uart.read_timeout_ms = static_cast<uint32_t>(std::max(
1, ObjectIntAny(serial, {"readTimeoutMs", "read_timeout_ms"})
.value_or(static_cast<int>(config.tp_uart.read_timeout_ms))));
config.tp_uart.nine_bit_mode = ObjectBoolAny(
serial, {"nineBitMode", "nine_bit_mode", "use9BitMode", "use_9_bit_mode"})
.value_or(config.tp_uart.nine_bit_mode);
}
return config;
}
@@ -939,6 +975,7 @@ DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config) {
serial["rxBufferSize"] = static_cast<int>(config.tp_uart.rx_buffer_size);
serial["txBufferSize"] = static_cast<int>(config.tp_uart.tx_buffer_size);
serial["readTimeoutMs"] = static_cast<int>(config.tp_uart.read_timeout_ms);
serial["nineBitMode"] = config.tp_uart.nine_bit_mode;
out["tpUart"] = std::move(serial);
DaliValue::Array ets_associations;
ets_associations.reserve(config.ets_associations.size());
@@ -1911,13 +1948,24 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task
}
stop_requested_ = false;
last_error_.clear();
int log_tp_uart_tx_pin = -1;
int log_tp_uart_rx_pin = -1;
if (config_.tp_uart.uart_port >= 0 && config_.tp_uart.uart_port < SOC_UART_NUM) {
const uart_port_t log_uart_port = static_cast<uart_port_t>(config_.tp_uart.uart_port);
ResolveUartIoPin(log_uart_port, config_.tp_uart.tx_pin, SOC_UART_TX_PIN_IDX,
&log_tp_uart_tx_pin);
ResolveUartIoPin(log_uart_port, config_.tp_uart.rx_pin, SOC_UART_RX_PIN_IDX,
&log_tp_uart_rx_pin);
}
ESP_LOGI(kTag,
"starting KNXnet/IP router namespace=%s udp=%u tunnel=%d multicast=%d group=%s "
"tpUart=%d tx=%d rx=%d commissioningOnly=%d",
"tpUart=%d tx=%s rx=%s nineBit=%d commissioningOnly=%d",
openknx_namespace_.c_str(), static_cast<unsigned>(config_.udp_port),
config_.tunnel_enabled, config_.multicast_enabled,
config_.multicast_address.c_str(), config_.tp_uart.uart_port,
config_.tp_uart.tx_pin, config_.tp_uart.rx_pin, commissioning_only_);
UartPinDescription(config_.tp_uart.tx_pin, log_tp_uart_tx_pin).c_str(),
UartPinDescription(config_.tp_uart.rx_pin, log_tp_uart_rx_pin).c_str(),
config_.tp_uart.nine_bit_mode, commissioning_only_);
if (!configureSocket()) {
return ESP_FAIL;
}
@@ -2447,11 +2495,24 @@ bool GatewayKnxTpIpRouter::configureTpUart() {
uart_config_t uart_config{};
uart_config.baud_rate = static_cast<int>(serial.baudrate);
uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = UART_PARITY_EVEN;
uart_config.parity = serial.nine_bit_mode ? UART_PARITY_EVEN : UART_PARITY_DISABLE;
uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
uart_config.source_clk = UART_SCLK_DEFAULT;
const uart_port_t uart_port = static_cast<uart_port_t>(serial.uart_port);
int tx_pin = UART_PIN_NO_CHANGE;
int rx_pin = UART_PIN_NO_CHANGE;
const bool tx_pin_ok = ResolveUartIoPin(uart_port, serial.tx_pin, SOC_UART_TX_PIN_IDX, &tx_pin);
const bool rx_pin_ok = ResolveUartIoPin(uart_port, serial.rx_pin, SOC_UART_RX_PIN_IDX, &rx_pin);
if (!tx_pin_ok || !rx_pin_ok) {
last_error_ = "KNX TP-UART UART" + std::to_string(serial.uart_port) +
" has no ESP-IDF default " + (!tx_pin_ok ? std::string("TX") : std::string("")) +
(!tx_pin_ok && !rx_pin_ok ? "/" : "") +
(!rx_pin_ok ? std::string("RX") : std::string("")) +
" pin; configure explicit txPin/rxPin values";
ESP_LOGE(kTag, "%s", last_error_.c_str());
return false;
}
esp_err_t err = uart_param_config(uart_port, &uart_config);
if (err != ESP_OK) {
last_error_ = EspErrDetail("failed to configure KNX TP-UART parameters on UART" +
@@ -2460,14 +2521,12 @@ bool GatewayKnxTpIpRouter::configureTpUart() {
ESP_LOGE(kTag, "%s", last_error_.c_str());
return false;
}
err = uart_set_pin(uart_port, serial.tx_pin < 0 ? UART_PIN_NO_CHANGE : serial.tx_pin,
serial.rx_pin < 0 ? UART_PIN_NO_CHANGE : serial.rx_pin,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
err = uart_set_pin(uart_port, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (err != ESP_OK) {
last_error_ = EspErrDetail("failed to configure KNX TP-UART pins uart=" +
std::to_string(serial.uart_port) + " tx=" +
std::to_string(serial.tx_pin) + " rx=" +
std::to_string(serial.rx_pin),
UartPinDescription(serial.tx_pin, tx_pin) + " rx=" +
UartPinDescription(serial.rx_pin, rx_pin),
err);
ESP_LOGE(kTag, "%s", last_error_.c_str());
return false;
@@ -2482,6 +2541,8 @@ bool GatewayKnxTpIpRouter::configureTpUart() {
return false;
}
tp_uart_port_ = serial.uart_port;
tp_uart_tx_pin_ = tx_pin;
tp_uart_rx_pin_ = rx_pin;
if (!initializeTpUart()) {
if (ets_device_ != nullptr && !ets_device_->configured()) {
ESP_LOGW(kTag,
@@ -2496,8 +2557,11 @@ bool GatewayKnxTpIpRouter::configureTpUart() {
ESP_LOGE(kTag, "%s", last_error_.c_str());
return false;
}
ESP_LOGI(kTag, "KNX TP-UART online uart=%d tx=%d rx=%d baud=%u", serial.uart_port,
serial.tx_pin, serial.rx_pin, static_cast<unsigned>(serial.baudrate));
ESP_LOGI(kTag, "KNX TP-UART online uart=%d tx=%s rx=%s baud=%u nineBit=%d",
serial.uart_port,
UartPinDescription(serial.tx_pin, tp_uart_tx_pin_).c_str(),
UartPinDescription(serial.rx_pin, tp_uart_rx_pin_).c_str(),
static_cast<unsigned>(serial.baudrate), serial.nine_bit_mode);
return true;
}
@@ -2600,8 +2664,8 @@ bool GatewayKnxTpIpRouter::initializeTpUart() {
last_error_ = (saw_reset ? "timed out waiting for KNX TP-UART state indication"
: "timed out waiting for KNX TP-UART reset indication") +
std::string(" uart=") + std::to_string(config_.tp_uart.uart_port) +
" tx=" + std::to_string(config_.tp_uart.tx_pin) +
" rx=" + std::to_string(config_.tp_uart.rx_pin) +
" tx=" + UartPinDescription(config_.tp_uart.tx_pin, tp_uart_tx_pin_) +
" rx=" + UartPinDescription(config_.tp_uart.rx_pin, tp_uart_rx_pin_) +
" timeoutMs=1500";
return false;
}
@@ -14,7 +14,8 @@ namespace gateway::openknx {
class TpuartUartInterface : public TPUart::Interface::Abstract {
public:
TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
size_t rx_buffer_size = 512, size_t tx_buffer_size = 512);
size_t rx_buffer_size = 512, size_t tx_buffer_size = 512,
bool nine_bit_mode = true);
~TpuartUartInterface();
void begin(int baud) override;
@@ -34,6 +35,7 @@ class TpuartUartInterface : public TPUart::Interface::Abstract {
int rx_pin_;
size_t rx_buffer_size_;
size_t tx_buffer_size_;
bool nine_bit_mode_;
std::atomic_bool overflow_{false};
std::function<bool()> callback_;
};
@@ -80,6 +80,7 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
? tunnel_client_address
: DefaultTunnelClientAddress(
device_.deviceObject().individualAddress()));
server->deviceAddressPropertiesTargetClient(false);
server->tunnelFrameCallback(&EtsDeviceRuntime::EmitTunnelFrame, this);
}
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
@@ -1,6 +1,7 @@
#include "openknx_idf/tpuart_uart_interface.h"
#include "esp_log.h"
#include "soc/uart_periph.h"
#include <utility>
@@ -9,15 +10,39 @@ namespace {
constexpr const char* kTag = "openknx_tpuart";
bool ResolveUartIoPin(uart_port_t uart_port, int configured_pin, uint32_t pin_index,
int* resolved_pin) {
if (resolved_pin == nullptr) {
return false;
}
if (configured_pin >= 0) {
*resolved_pin = configured_pin;
return true;
}
if (uart_port < 0 || uart_port >= SOC_UART_NUM || pin_index >= SOC_UART_PINS_COUNT) {
*resolved_pin = UART_PIN_NO_CHANGE;
return false;
}
const int default_pin = uart_periph_signal[uart_port].pins[pin_index].default_gpio;
if (default_pin < 0) {
*resolved_pin = UART_PIN_NO_CHANGE;
return false;
}
*resolved_pin = default_pin;
return true;
}
} // namespace
TpuartUartInterface::TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
size_t rx_buffer_size, size_t tx_buffer_size)
size_t rx_buffer_size, size_t tx_buffer_size,
bool nine_bit_mode)
: uart_port_(uart_port),
tx_pin_(tx_pin),
rx_pin_(rx_pin),
rx_buffer_size_(rx_buffer_size),
tx_buffer_size_(tx_buffer_size) {}
tx_buffer_size_(tx_buffer_size),
nine_bit_mode_(nine_bit_mode) {}
TpuartUartInterface::~TpuartUartInterface() { end(); }
@@ -29,22 +54,30 @@ void TpuartUartInterface::begin(int baud) {
uart_config_t config{};
config.baud_rate = baud;
config.data_bits = UART_DATA_8_BITS;
config.parity = UART_PARITY_EVEN;
config.parity = nine_bit_mode_ ? UART_PARITY_EVEN : UART_PARITY_DISABLE;
config.stop_bits = UART_STOP_BITS_1;
config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
config.source_clk = UART_SCLK_DEFAULT;
int tx_pin = UART_PIN_NO_CHANGE;
int rx_pin = UART_PIN_NO_CHANGE;
if (!ResolveUartIoPin(uart_port_, tx_pin_, SOC_UART_TX_PIN_IDX, &tx_pin) ||
!ResolveUartIoPin(uart_port_, rx_pin_, SOC_UART_RX_PIN_IDX, &rx_pin)) {
ESP_LOGE(kTag, "UART%d has no ESP-IDF default TX/RX pin; configure explicit pins",
uart_port_);
return;
}
esp_err_t err = uart_param_config(uart_port_, &config);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to configure UART%d: %s", uart_port_, esp_err_to_name(err));
return;
}
err = uart_set_pin(uart_port_, tx_pin_ < 0 ? UART_PIN_NO_CHANGE : tx_pin_,
rx_pin_ < 0 ? UART_PIN_NO_CHANGE : rx_pin_, UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE);
err = uart_set_pin(uart_port_, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to route UART%d pins: %s", uart_port_, esp_err_to_name(err));
ESP_LOGE(kTag, "failed to route UART%d pins tx=%d rx=%d: %s", uart_port_, tx_pin,
rx_pin, esp_err_to_name(err));
return;
}