Compare commits
2 Commits
bf23cf0b79
...
1b8753636f
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b8753636f | |||
| e94945fc0f |
@@ -1,3 +1,7 @@
|
|||||||
[submodule "bacnet_stack"]
|
[submodule "bacnet_stack"]
|
||||||
path = bacnet_stack
|
path = bacnet_stack
|
||||||
url = https://git.tonycloud.org/dali/bacnet_stack.git
|
url = https://git.tonycloud.org/dali/bacnet_stack.git
|
||||||
|
[submodule "knx"]
|
||||||
|
path = knx
|
||||||
|
url = https://git.tonycloud.org/knx/knx.git
|
||||||
|
branch = v1
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{};
|
||||||
|
|||||||
Submodule
+1
Submodule knx added at 7124a6435d
Reference in New Issue
Block a user