feat(gateway): enhance UART configuration validation for Modbus and KNX

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-11 03:44:14 +08:00
parent bf23cf0b79
commit e94945fc0f
4 changed files with 286 additions and 36 deletions
+10 -7
View File
@@ -504,6 +504,10 @@ bool ValidateChannelBindings() {
ESP_LOGE(kTag, "Modbus serial is configured on UART0, but UART0 is reserved for console"); ESP_LOGE(kTag, "Modbus serial is configured on UART0, but UART0 is reserved for console");
return false; return false;
} }
if (modbus_uart == 0 && kConsoleOnUart0) {
ESP_LOGE(kTag, "Modbus serial UART0 requires moving the ESP-IDF console off UART0");
return false;
}
for (int i = 0; i < CONFIG_GATEWAY_CHANNEL_COUNT; ++i) { for (int i = 0; i < CONFIG_GATEWAY_CHANNEL_COUNT; ++i) {
if (channels[i].enabled && channels[i].serial_phy && channels[i].uart_port == modbus_uart) { if (channels[i].enabled && channels[i].serial_phy && channels[i].uart_port == modbus_uart) {
ESP_LOGE(kTag, "Modbus serial UART%d conflicts with DALI channel %d serial PHY", ESP_LOGE(kTag, "Modbus serial UART%d conflicts with DALI channel %d serial PHY",
@@ -519,6 +523,10 @@ bool ValidateChannelBindings() {
ESP_LOGE(kTag, "KNX TP UART0 conflicts with the UART0 control bridge"); ESP_LOGE(kTag, "KNX TP UART0 conflicts with the UART0 control bridge");
return false; return false;
} }
if (knx_uart == 0 && kConsoleOnUart0) {
ESP_LOGE(kTag, "KNX TP-UART on UART0 requires moving the ESP-IDF console off UART0");
return false;
}
if (kModbusBridgeSupported && kModbusDefaultSerialTransport && if (kModbusBridgeSupported && kModbusDefaultSerialTransport &&
knx_uart == CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT) { knx_uart == CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT) {
ESP_LOGE(kTag, "KNX TP UART%d conflicts with default Modbus serial UART", knx_uart); ESP_LOGE(kTag, "KNX TP UART%d conflicts with default Modbus serial UART", knx_uart);
@@ -720,10 +728,8 @@ extern "C" void app_main(void) {
static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE); static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE);
bridge_config.modbus_task_priority = bridge_config.modbus_task_priority =
static_cast<UBaseType_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY); static_cast<UBaseType_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY);
bridge_config.allow_modbus_uart0 = kModbusAllowUart0 && !k485ControlEnabled; bridge_config.allow_modbus_uart0 = kModbusAllowUart0 && !kConsoleOnUart0 && !k485ControlEnabled;
if (!bridge_config.allow_modbus_uart0) { bridge_config.allow_knx_uart0 = !kConsoleOnUart0 && !k485ControlEnabled;
bridge_config.reserved_uart_ports.push_back(0);
}
#if CONFIG_GATEWAY_CHANNEL1_PHY_UART1 #if CONFIG_GATEWAY_CHANNEL1_PHY_UART1
bridge_config.reserved_uart_ports.push_back(1); bridge_config.reserved_uart_ports.push_back(1);
#elif CONFIG_GATEWAY_CHANNEL1_PHY_UART2 #elif CONFIG_GATEWAY_CHANNEL1_PHY_UART2
@@ -736,9 +742,6 @@ extern "C" void app_main(void) {
bridge_config.reserved_uart_ports.push_back(2); bridge_config.reserved_uart_ports.push_back(2);
#endif #endif
#endif #endif
if (kKnxBridgeSupported) {
bridge_config.reserved_uart_ports.push_back(CONFIG_GATEWAY_KNX_TP_UART_PORT);
}
if (kModbusBridgeSupported) { if (kModbusBridgeSupported) {
gateway::GatewayModbusConfig default_modbus; gateway::GatewayModbusConfig default_modbus;
#if defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU) #if defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU)
@@ -3,6 +3,7 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <set>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -31,6 +32,7 @@ struct GatewayBridgeServiceConfig {
UBaseType_t modbus_task_priority{4}; UBaseType_t modbus_task_priority{4};
std::optional<GatewayModbusConfig> default_modbus_config; std::optional<GatewayModbusConfig> default_modbus_config;
bool allow_modbus_uart0{false}; bool allow_modbus_uart0{false};
bool allow_knx_uart0{false};
std::vector<int> reserved_uart_ports; std::vector<int> reserved_uart_ports;
uint32_t bacnet_task_stack_size{8192}; uint32_t bacnet_task_stack_size{8192};
UBaseType_t bacnet_task_priority{5}; UBaseType_t bacnet_task_priority{5};
@@ -63,6 +65,10 @@ class GatewayBridgeService {
ChannelRuntime* findRuntime(uint8_t gateway_id); ChannelRuntime* findRuntime(uint8_t gateway_id);
const ChannelRuntime* findRuntime(uint8_t gateway_id) const; const ChannelRuntime* findRuntime(uint8_t gateway_id) const;
void collectUsedRuntimeResources(uint8_t except_gateway_id,
std::set<uint16_t>* modbus_tcp_ports,
std::set<uint16_t>* knx_udp_ports,
std::set<int>* serial_uarts) const;
DaliDomainService& dali_domain_; DaliDomainService& dali_domain_;
GatewayCache& cache_; GatewayCache& cache_;
+257 -20
View File
@@ -1186,6 +1186,7 @@ struct GatewayBridgeService::ChannelRuntime {
int modbus_client_sock{-1}; int modbus_client_sock{-1};
int modbus_uart_port{-1}; int modbus_uart_port{-1};
std::string modbus_last_error; std::string modbus_last_error;
std::string knx_last_error;
struct DiagnosticSnapshotCacheEntry { struct DiagnosticSnapshotCacheEntry {
DaliDomainSnapshot snapshot; DaliDomainSnapshot snapshot;
@@ -1460,6 +1461,13 @@ struct GatewayBridgeService::ChannelRuntime {
if (!parsed.has_value()) { if (!parsed.has_value()) {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
{
LockGuard guard(lock);
const esp_err_t validation_err = validateStoredBridgeConfigLocked(parsed.value());
if (validation_err != ESP_OK) {
return validation_err;
}
}
BridgeProvisioningStore store(bridgeNamespace()); BridgeProvisioningStore store(bridgeNamespace());
const esp_err_t err = store.saveObject( const esp_err_t err = store.saveObject(
kBridgeConfigKey, kBridgeConfigKey,
@@ -1832,7 +1840,8 @@ struct GatewayBridgeService::ChannelRuntime {
} }
#endif #endif
esp_err_t startKnx() { esp_err_t startKnx(std::set<uint16_t>* used_ports = nullptr,
std::set<int>* used_uarts = nullptr) {
LockGuard guard(lock); LockGuard guard(lock);
if (!service_config.knx_enabled) { if (!service_config.knx_enabled) {
return ESP_ERR_NOT_SUPPORTED; return ESP_ERR_NOT_SUPPORTED;
@@ -1844,15 +1853,44 @@ struct GatewayBridgeService::ChannelRuntime {
if (!config.has_value()) { if (!config.has_value()) {
return ESP_ERR_NOT_FOUND; return ESP_ERR_NOT_FOUND;
} }
std::string validation_error;
const esp_err_t validation_err = validateKnxConfigLocked(
config.value(), activeModbusConfigLocked(), &validation_error);
if (validation_err != ESP_OK) {
knx_last_error = validation_error;
return validation_err;
}
if (config->ip_router_enabled && used_ports != nullptr) {
if (used_ports->find(config->udp_port) != used_ports->end()) {
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(config->udp_port);
return ESP_ERR_INVALID_STATE;
}
used_ports->insert(config->udp_port);
}
if (config->ip_router_enabled && used_uarts != nullptr) {
const int uart_port = config->tp_uart.uart_port;
if (used_uarts->find(uart_port) != used_uarts->end()) {
knx_last_error = "KNX TP-UART UART" + std::to_string(uart_port) +
" is already used by another runtime";
return ESP_ERR_INVALID_STATE;
}
used_uarts->insert(uart_port);
}
knx->setConfig(config.value()); knx->setConfig(config.value());
knx_router->setConfig(config.value()); knx_router->setConfig(config.value());
if (!config->ip_router_enabled) { if (!config->ip_router_enabled) {
knx_started = false; knx_started = false;
return ESP_ERR_NOT_SUPPORTED; return ESP_ERR_NOT_SUPPORTED;
} }
knx_last_error.clear();
const esp_err_t err = knx_router->start(service_config.knx_task_stack_size, const esp_err_t err = knx_router->start(service_config.knx_task_stack_size,
service_config.knx_task_priority); service_config.knx_task_priority);
knx_started = err == ESP_OK; knx_started = err == ESP_OK;
if (err != ESP_OK) {
knx_last_error = knx_router->lastError().empty()
? "failed to start KNX TP-UART router"
: knx_router->lastError();
}
return err; return err;
} }
@@ -1961,9 +1999,10 @@ struct GatewayBridgeService::ChannelRuntime {
cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled); cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled);
cJSON_AddBoolToObject(knx_json, "started", knx_started); cJSON_AddBoolToObject(knx_json, "started", knx_started);
cJSON_AddBoolToObject(knx_json, "routerReady", knx_router != nullptr && knx_router->started()); cJSON_AddBoolToObject(knx_json, "routerReady", knx_router != nullptr && knx_router->started());
if (knx_router != nullptr) { const std::string router_error = knx_router == nullptr ? "" : knx_router->lastError();
cJSON_AddStringToObject(knx_json, "lastError", knx_router->lastError().c_str()); cJSON_AddStringToObject(knx_json, "lastError",
} knx_last_error.empty() ? router_error.c_str()
: knx_last_error.c_str());
if (effective_knx.has_value()) { if (effective_knx.has_value()) {
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled", cJSON_AddBoolToObject(knx_json, "daliRouterEnabled",
effective_knx->dali_router_enabled); effective_knx->dali_router_enabled);
@@ -2713,22 +2752,120 @@ struct GatewayBridgeService::ChannelRuntime {
service_config.reserved_uart_ports.end(); service_config.reserved_uart_ports.end();
} }
esp_err_t validateSerialModbusConfigLocked(const GatewayModbusConfig& config) const { esp_err_t validateSerialModbusConfigLocked(
const GatewayModbusConfig& config,
const std::optional<GatewayKnxConfig>& candidate_knx,
std::string* error_message = nullptr) const {
const int uart_port = config.serial.uart_port; const int uart_port = config.serial.uart_port;
if (uart_port < 0 || uart_port > 2) { if (uart_port < 0 || uart_port > 2) {
if (error_message != nullptr) {
*error_message = "Modbus serial UART port must be 0, 1, or 2";
}
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
if (uart_port == 0 && !service_config.allow_modbus_uart0) { if (uart_port == 0 && !service_config.allow_modbus_uart0) {
if (error_message != nullptr) {
*error_message =
"Modbus serial on UART0 requires moving the ESP-IDF console and UART0 control off UART0";
}
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
if (isReservedUartLocked(uart_port)) { if (isReservedUartLocked(uart_port)) {
if (error_message != nullptr) {
*error_message = "Modbus serial UART" + std::to_string(uart_port) +
" is already reserved by a DALI serial PHY";
}
return ESP_ERR_INVALID_STATE;
}
if (service_config.knx_enabled && candidate_knx.has_value() &&
candidate_knx->ip_router_enabled && candidate_knx->tp_uart.uart_port == uart_port) {
if (error_message != nullptr) {
*error_message = "Modbus serial UART" + std::to_string(uart_port) +
" conflicts with KNX TP-UART; choose another free UART for RS485";
}
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
return ESP_OK; return ESP_OK;
} }
esp_err_t validateKnxConfigLocked(const GatewayKnxConfig& config,
const std::optional<GatewayModbusConfig>& candidate_modbus,
std::string* error_message = nullptr) const {
if (!config.ip_router_enabled) {
return ESP_OK;
}
const int uart_port = config.tp_uart.uart_port;
if (uart_port < 0 || uart_port > 2) {
if (error_message != nullptr) {
*error_message = "KNX TP-UART port must be 0, 1, or 2";
}
return ESP_ERR_INVALID_ARG;
}
if (uart_port == 0 && !service_config.allow_knx_uart0) {
if (error_message != nullptr) {
*error_message =
"KNX TP-UART on UART0 requires moving the ESP-IDF console and UART0 control off UART0";
}
return ESP_ERR_INVALID_STATE;
}
if (isReservedUartLocked(uart_port)) {
if (error_message != nullptr) {
*error_message = "KNX TP-UART UART" + std::to_string(uart_port) +
" is already reserved by a DALI serial PHY";
}
return ESP_ERR_INVALID_STATE;
}
if (service_config.modbus_enabled && candidate_modbus.has_value() &&
GatewayModbusTransportIsSerial(candidate_modbus->transport) &&
candidate_modbus->serial.uart_port == uart_port) {
if (error_message != nullptr) {
*error_message = "KNX TP-UART UART" + std::to_string(uart_port) +
" conflicts with Modbus serial UART";
}
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
esp_err_t validateStoredBridgeConfigLocked(const GatewayBridgeStoredConfig& config) {
const auto candidate_modbus = config.modbus.has_value() ? config.modbus
: service_config.default_modbus_config;
const auto candidate_knx = config.knx.has_value() ? config.knx
: service_config.default_knx_config;
std::string validation_error;
if (candidate_modbus.has_value() &&
GatewayModbusTransportIsSerial(candidate_modbus->transport)) {
const esp_err_t err = validateSerialModbusConfigLocked(
candidate_modbus.value(), candidate_knx, &validation_error);
if (err != ESP_OK) {
modbus_last_error = validation_error;
return err;
}
}
if (candidate_knx.has_value()) {
const esp_err_t err = validateKnxConfigLocked(candidate_knx.value(), candidate_modbus,
&validation_error);
if (err != ESP_OK) {
knx_last_error = validation_error;
return err;
}
}
modbus_last_error.clear();
knx_last_error.clear();
return ESP_OK;
}
esp_err_t saveModbusConfig(const GatewayModbusConfig& config) { esp_err_t saveModbusConfig(const GatewayModbusConfig& config) {
LockGuard guard(lock); LockGuard guard(lock);
if (GatewayModbusTransportIsSerial(config.transport)) {
std::string validation_error;
const esp_err_t validation_err = validateSerialModbusConfigLocked(
config, activeKnxConfigLocked(), &validation_error);
if (validation_err != ESP_OK) {
modbus_last_error = validation_error;
return validation_err;
}
}
BridgeProvisioningStore store(bridgeNamespace()); BridgeProvisioningStore store(bridgeNamespace());
const esp_err_t err = store.saveObject( const esp_err_t err = store.saveObject(
kBridgeConfigKey, kBridgeConfigKey,
@@ -2742,6 +2879,7 @@ struct GatewayBridgeService::ChannelRuntime {
if (modbus != nullptr) { if (modbus != nullptr) {
modbus->setConfig(config); modbus->setConfig(config);
} }
modbus_last_error.clear();
return ESP_OK; return ESP_OK;
} }
@@ -2752,8 +2890,33 @@ struct GatewayBridgeService::ChannelRuntime {
return service_config.default_knx_config; return service_config.default_knx_config;
} }
esp_err_t saveKnxConfig(const GatewayKnxConfig& config) { esp_err_t saveKnxConfig(const GatewayKnxConfig& config,
std::set<uint16_t>* used_ports = nullptr,
std::set<int>* used_uarts = nullptr) {
LockGuard guard(lock); LockGuard guard(lock);
std::string validation_error;
const esp_err_t validation_err = validateKnxConfigLocked(
config, activeModbusConfigLocked(), &validation_error);
if (validation_err != ESP_OK) {
knx_last_error = validation_error;
return validation_err;
}
const bool restart_router = knx_started || (knx_router != nullptr && knx_router->started());
if (restart_router && config.ip_router_enabled && used_ports != nullptr &&
used_ports->find(config.udp_port) != used_ports->end()) {
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(config.udp_port);
return ESP_ERR_INVALID_STATE;
}
if (restart_router && config.ip_router_enabled && used_uarts != nullptr &&
used_uarts->find(config.tp_uart.uart_port) != used_uarts->end()) {
knx_last_error = "KNX TP-UART UART" + std::to_string(config.tp_uart.uart_port) +
" is already used by another runtime";
return ESP_ERR_INVALID_STATE;
}
if (restart_router && knx_router != nullptr) {
knx_router->stop();
knx_started = false;
}
BridgeProvisioningStore store(bridgeNamespace()); BridgeProvisioningStore store(bridgeNamespace());
const esp_err_t err = store.saveObject( const esp_err_t err = store.saveObject(
kBridgeConfigKey, kBridgeConfigKey,
@@ -2770,6 +2933,10 @@ struct GatewayBridgeService::ChannelRuntime {
if (knx_router != nullptr) { if (knx_router != nullptr) {
knx_router->setConfig(config); knx_router->setConfig(config);
} }
if (restart_router) {
return startKnx(used_ports, used_uarts);
}
knx_last_error.clear();
return ESP_OK; return ESP_OK;
} }
@@ -2920,14 +3087,18 @@ struct GatewayBridgeService::ChannelRuntime {
return ESP_ERR_NOT_FOUND; return ESP_ERR_NOT_FOUND;
} }
if (GatewayModbusTransportIsSerial(config->transport)) { if (GatewayModbusTransportIsSerial(config->transport)) {
const esp_err_t serial_err = validateSerialModbusConfigLocked(config.value()); std::string validation_error;
const esp_err_t serial_err = validateSerialModbusConfigLocked(
config.value(), activeKnxConfigLocked(), &validation_error);
if (serial_err != ESP_OK) { if (serial_err != ESP_OK) {
modbus_last_error = "invalid or reserved Modbus serial UART"; modbus_last_error = validation_error;
return serial_err; return serial_err;
} }
if (used_uarts != nullptr) { if (used_uarts != nullptr) {
const int uart_port = config->serial.uart_port; const int uart_port = config->serial.uart_port;
if (used_uarts->find(uart_port) != used_uarts->end()) { if (used_uarts->find(uart_port) != used_uarts->end()) {
modbus_last_error = "Modbus serial UART" + std::to_string(uart_port) +
" is already used by another runtime";
ESP_LOGW(kTag, "gateway=%u skips duplicate Modbus serial UART%d", channel.gateway_id, ESP_LOGW(kTag, "gateway=%u skips duplicate Modbus serial UART%d", channel.gateway_id,
uart_port); uart_port);
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
@@ -2938,6 +3109,7 @@ struct GatewayBridgeService::ChannelRuntime {
const uint16_t port = config->port == 0 ? kGatewayModbusDefaultTcpPort : config->port; const uint16_t port = config->port == 0 ? kGatewayModbusDefaultTcpPort : config->port;
if (GatewayModbusTransportIsTcp(config->transport) && used_ports != nullptr) { if (GatewayModbusTransportIsTcp(config->transport) && used_ports != nullptr) {
if (used_ports->find(port) != used_ports->end()) { if (used_ports->find(port) != used_ports->end()) {
modbus_last_error = "duplicate Modbus TCP port " + std::to_string(port);
ESP_LOGW(kTag, "gateway=%u skips duplicate Modbus TCP port %u", channel.gateway_id, port); ESP_LOGW(kTag, "gateway=%u skips duplicate Modbus TCP port %u", channel.gateway_id, port);
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
@@ -3289,7 +3461,9 @@ struct GatewayBridgeService::ChannelRuntime {
const esp_err_t err = saveModbusConfig(parsed.value()); const esp_err_t err = saveModbusConfig(parsed.value());
cJSON_Delete(root); cJSON_Delete(root);
if (err != ESP_OK) { if (err != ESP_OK) {
writeModbusManagementResponse(uart_port, false, action.c_str(), esp_err_to_name(err)); writeModbusManagementResponse(
uart_port, false, action.c_str(),
modbus_last_error.empty() ? esp_err_to_name(err) : modbus_last_error.c_str());
return; return;
} }
writeModbusManagementResponse(uart_port, true, action.c_str(), nullptr); writeModbusManagementResponse(uart_port, true, action.c_str(), nullptr);
@@ -3367,11 +3541,11 @@ esp_err_t GatewayBridgeService::start() {
runtimes_.push_back(std::move(runtime)); runtimes_.push_back(std::move(runtime));
} }
std::set<int> used_serial_uarts;
if (config_.modbus_enabled && config_.modbus_startup_enabled) { if (config_.modbus_enabled && config_.modbus_startup_enabled) {
std::set<uint16_t> used_ports; std::set<uint16_t> used_modbus_ports;
std::set<int> used_uarts;
for (const auto& runtime : runtimes_) { for (const auto& runtime : runtimes_) {
const esp_err_t err = runtime->startModbus(&used_ports, &used_uarts); const esp_err_t err = runtime->startModbus(&used_modbus_ports, &used_serial_uarts);
if (err != ESP_OK && err != ESP_ERR_NOT_FOUND && err != ESP_ERR_NOT_SUPPORTED) { if (err != ESP_OK && err != ESP_ERR_NOT_FOUND && err != ESP_ERR_NOT_SUPPORTED) {
ESP_LOGW(kTag, "gateway=%u Modbus startup skipped: %s", runtime->channel.gateway_id, ESP_LOGW(kTag, "gateway=%u Modbus startup skipped: %s", runtime->channel.gateway_id,
esp_err_to_name(err)); esp_err_to_name(err));
@@ -3380,8 +3554,9 @@ esp_err_t GatewayBridgeService::start() {
} }
if (config_.knx_enabled && config_.knx_startup_enabled) { if (config_.knx_enabled && config_.knx_startup_enabled) {
std::set<uint16_t> used_knx_ports;
for (const auto& runtime : runtimes_) { for (const auto& runtime : runtimes_) {
const esp_err_t err = runtime->startKnx(); const esp_err_t err = runtime->startKnx(&used_knx_ports, &used_serial_uarts);
if (err != ESP_OK && err != ESP_ERR_NOT_FOUND && err != ESP_ERR_NOT_SUPPORTED) { if (err != ESP_OK && err != ESP_ERR_NOT_FOUND && err != ESP_ERR_NOT_SUPPORTED) {
ESP_LOGW(kTag, "gateway=%u KNX/IP startup skipped: %s", runtime->channel.gateway_id, ESP_LOGW(kTag, "gateway=%u KNX/IP startup skipped: %s", runtime->channel.gateway_id,
esp_err_to_name(err)); esp_err_to_name(err));
@@ -3422,6 +3597,44 @@ const GatewayBridgeService::ChannelRuntime* GatewayBridgeService::findRuntime(
return nullptr; return nullptr;
} }
void GatewayBridgeService::collectUsedRuntimeResources(
uint8_t except_gateway_id,
std::set<uint16_t>* modbus_tcp_ports,
std::set<uint16_t>* knx_udp_ports,
std::set<int>* serial_uarts) const {
for (const auto& runtime : runtimes_) {
if (runtime->channel.gateway_id == except_gateway_id) {
continue;
}
LockGuard guard(runtime->lock);
if (runtime->modbus_started) {
const auto modbus_config = runtime->activeModbusConfigLocked();
if (modbus_config.has_value()) {
if (GatewayModbusTransportIsSerial(modbus_config->transport) && serial_uarts != nullptr) {
serial_uarts->insert(modbus_config->serial.uart_port);
} else if (GatewayModbusTransportIsTcp(modbus_config->transport) &&
modbus_tcp_ports != nullptr) {
const uint16_t port = modbus_config->port == 0 ? kGatewayModbusDefaultTcpPort
: modbus_config->port;
modbus_tcp_ports->insert(port);
}
}
}
if (runtime->knx_started ||
(runtime->knx_router != nullptr && runtime->knx_router->started())) {
const auto knx_config = runtime->activeKnxConfigLocked();
if (knx_config.has_value() && knx_config->ip_router_enabled) {
if (knx_udp_ports != nullptr) {
knx_udp_ports->insert(knx_config->udp_port);
}
if (serial_uarts != nullptr) {
serial_uarts->insert(knx_config->tp_uart.uart_port);
}
}
}
}
}
GatewayBridgeHttpResponse GatewayBridgeService::handleGet( GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
const std::string& action_arg, int gateway_id_arg, const std::string& query_arg) { const std::string& action_arg, int gateway_id_arg, const std::string& query_arg) {
if (!config_.bridge_enabled) { if (!config_.bridge_enabled) {
@@ -3626,7 +3839,12 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
if (action == "config" || action == "save_config") { if (action == "config" || action == "save_config") {
const esp_err_t err = runtime->saveBridgeConfig(body); const esp_err_t err = runtime->saveBridgeConfig(body);
if (err != ESP_OK) { if (err != ESP_OK) {
return ErrorResponse(err, "failed to save bridge config"); const char* message = !runtime->knx_last_error.empty()
? runtime->knx_last_error.c_str()
: (!runtime->modbus_last_error.empty()
? runtime->modbus_last_error.c_str()
: "failed to save bridge config");
return ErrorResponse(err, message);
} }
return handleGet("config", gateway_id.value()); return handleGet("config", gateway_id.value());
} }
@@ -3666,9 +3884,15 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
return handleGet("cloud", gateway_id.value()); return handleGet("cloud", gateway_id.value());
} }
if (action == "modbus_start") { if (action == "modbus_start") {
const esp_err_t err = runtime->startModbus(); std::set<uint16_t> used_modbus_ports;
std::set<int> used_serial_uarts;
collectUsedRuntimeResources(gateway_id.value(), &used_modbus_ports, nullptr,
&used_serial_uarts);
const esp_err_t err = runtime->startModbus(&used_modbus_ports, &used_serial_uarts);
if (err != ESP_OK) { if (err != ESP_OK) {
return ErrorResponse(err, "failed to start Modbus bridge"); return ErrorResponse(err, runtime->modbus_last_error.empty()
? "failed to start Modbus bridge"
: runtime->modbus_last_error.c_str());
} }
return handleGet("modbus", gateway_id.value()); return handleGet("modbus", gateway_id.value());
} }
@@ -3694,16 +3918,29 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
if (!parsed.has_value()) { if (!parsed.has_value()) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "invalid KNX config"); return ErrorResponse(ESP_ERR_INVALID_ARG, "invalid KNX config");
} }
const esp_err_t err = runtime->saveKnxConfig(parsed.value()); std::set<uint16_t> used_knx_ports;
std::set<int> used_serial_uarts;
collectUsedRuntimeResources(gateway_id.value(), nullptr, &used_knx_ports,
&used_serial_uarts);
const esp_err_t err = runtime->saveKnxConfig(parsed.value(), &used_knx_ports,
&used_serial_uarts);
if (err != ESP_OK) { if (err != ESP_OK) {
return ErrorResponse(err, "failed to save KNX bridge config"); return ErrorResponse(err, runtime->knx_last_error.empty()
? "failed to save KNX bridge config"
: runtime->knx_last_error.c_str());
} }
return handleGet("knx", gateway_id.value()); return handleGet("knx", gateway_id.value());
} }
if (action == "knx_start") { if (action == "knx_start") {
const esp_err_t err = runtime->startKnx(); std::set<uint16_t> used_knx_ports;
std::set<int> used_serial_uarts;
collectUsedRuntimeResources(gateway_id.value(), nullptr, &used_knx_ports,
&used_serial_uarts);
const esp_err_t err = runtime->startKnx(&used_knx_ports, &used_serial_uarts);
if (err != ESP_OK) { if (err != ESP_OK) {
return ErrorResponse(err, "failed to start KNX/IP bridge"); return ErrorResponse(err, runtime->knx_last_error.empty()
? "failed to start KNX/IP bridge"
: runtime->knx_last_error.c_str());
} }
return handleGet("knx", gateway_id.value()); return handleGet("knx", gateway_id.value());
} }
+13 -9
View File
@@ -655,10 +655,18 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task
} }
stop_requested_ = false; stop_requested_ = false;
last_error_.clear(); last_error_.clear();
if (!configureSocket()) {
return ESP_FAIL;
}
if (!configureTpUart()) {
closeSockets();
return ESP_FAIL;
}
const BaseType_t created = xTaskCreate(&GatewayKnxTpIpRouter::TaskEntry, "gw_knx_ip", const BaseType_t created = xTaskCreate(&GatewayKnxTpIpRouter::TaskEntry, "gw_knx_ip",
task_stack_size, this, task_priority, &task_handle_); task_stack_size, this, task_priority, &task_handle_);
if (created != pdPASS) { if (created != pdPASS) {
task_handle_ = nullptr; task_handle_ = nullptr;
closeSockets();
return ESP_ERR_NO_MEM; return ESP_ERR_NO_MEM;
} }
started_ = true; started_ = true;
@@ -668,6 +676,11 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task
esp_err_t GatewayKnxTpIpRouter::stop() { esp_err_t GatewayKnxTpIpRouter::stop() {
stop_requested_ = true; stop_requested_ = true;
closeSockets(); closeSockets();
const TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
for (int attempt = 0; task_handle_ != nullptr && task_handle_ != current_task && attempt < 50;
++attempt) {
vTaskDelay(pdMS_TO_TICKS(10));
}
return ESP_OK; return ESP_OK;
} }
@@ -680,15 +693,6 @@ void GatewayKnxTpIpRouter::TaskEntry(void* arg) {
} }
void GatewayKnxTpIpRouter::taskLoop() { void GatewayKnxTpIpRouter::taskLoop() {
if (!configureSocket()) {
finishTask();
return;
}
if (!configureTpUart()) {
finishTask();
return;
}
std::array<uint8_t, 768> buffer{}; std::array<uint8_t, 768> buffer{};
while (!stop_requested_) { while (!stop_requested_) {
sockaddr_in remote{}; sockaddr_in remote{};