Refactor GatewayModbus and GatewayNetwork components
- Updated GatewayModbusConfig to allow uart_port and pin values to be -1, indicating an unconfigured state. - Enhanced GatewayNetworkService to support an additional setup AP button with configurable GPIO and active low settings. - Refactored boot button configuration logic to reduce redundancy and improve clarity. - Introduced a new method for handling GPIO input configuration. - Improved boot button task loop to handle both boot and setup AP buttons more effectively. - Added programming mode functionality to EtsDeviceRuntime, allowing toggling and querying of the programming state. - Implemented memory checks to avoid unnecessary reads in EtsDeviceRuntime. - Enhanced security storage to derive factory FDSK from the device's serial number and store it in NVS. - Updated factory FDSK loading logic to ensure proper key generation and storage. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -15,7 +15,6 @@
|
||||
#include "gateway_knx.hpp"
|
||||
#include "gateway_modbus.hpp"
|
||||
#include "gateway_provisioning.hpp"
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
#include "openknx_idf/security_storage.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
@@ -1296,12 +1295,20 @@ uart_stop_bits_t UartStopBits(int bits) {
|
||||
} // namespace
|
||||
|
||||
struct GatewayBridgeService::ChannelRuntime {
|
||||
explicit ChannelRuntime(DaliDomainService& domain, GatewayCache& cache, DaliChannelInfo channel,
|
||||
explicit ChannelRuntime(GatewayBridgeService& service, DaliDomainService& domain,
|
||||
GatewayCache& cache, DaliChannelInfo channel,
|
||||
GatewayBridgeServiceConfig service_config)
|
||||
: domain(domain), cache(cache), channel(std::move(channel)), service_config(service_config),
|
||||
: service(service),
|
||||
domain(domain),
|
||||
cache(cache),
|
||||
channel(std::move(channel)),
|
||||
service_config(service_config),
|
||||
lock(xSemaphoreCreateRecursiveMutex()) {}
|
||||
|
||||
~ChannelRuntime() {
|
||||
if (knx_router != nullptr) {
|
||||
knx_router->stop();
|
||||
}
|
||||
if (cloud != nullptr) {
|
||||
cloud->stop();
|
||||
}
|
||||
@@ -1311,6 +1318,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
GatewayBridgeService& service;
|
||||
DaliDomainService& domain;
|
||||
GatewayCache& cache;
|
||||
DaliChannelInfo channel;
|
||||
@@ -1427,18 +1435,17 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
knx = std::make_unique<GatewayKnxBridge>(*engine);
|
||||
knx_router = std::make_unique<GatewayKnxTpIpRouter>(
|
||||
*knx, [this](const uint8_t* data, size_t len) {
|
||||
LockGuard guard(lock);
|
||||
if (knx == nullptr) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "KNX bridge is not ready";
|
||||
return result;
|
||||
}
|
||||
return knx->handleCemiFrame(data, len);
|
||||
return service.routeKnxCemiFrame(data, len);
|
||||
},
|
||||
openKnxNamespace());
|
||||
if (knx_config.has_value()) {
|
||||
knx->setConfig(knx_config.value());
|
||||
knx_router->setConfig(knx_config.value());
|
||||
knx_router->setGroupWriteHandler(
|
||||
[this](uint16_t group_address, const uint8_t* data, size_t len) {
|
||||
return service.routeKnxGroupWrite(group_address, data, len);
|
||||
});
|
||||
if (const auto active_knx = activeKnxConfigLocked(); active_knx.has_value()) {
|
||||
knx->setConfig(active_knx.value());
|
||||
knx_router->setConfig(active_knx.value());
|
||||
knx_router->setCommissioningOnly(!knx_config.has_value());
|
||||
}
|
||||
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
@@ -1459,33 +1466,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
|
||||
void refreshOpenKnxEtsAssociationsLocked() {
|
||||
if (!service_config.knx_enabled) {
|
||||
return;
|
||||
}
|
||||
const auto active_config = activeKnxConfigLocked();
|
||||
if (!active_config.has_value()) {
|
||||
return;
|
||||
}
|
||||
const auto snapshot = openknx::LoadEtsMemorySnapshot(openKnxNamespace());
|
||||
const bool has_downloaded_address = snapshot.individual_address != 0 &&
|
||||
snapshot.individual_address != 0xffff;
|
||||
if (!snapshot.configured && !has_downloaded_address && snapshot.associations.empty()) {
|
||||
return;
|
||||
}
|
||||
GatewayKnxConfig updated = active_config.value();
|
||||
if (has_downloaded_address) {
|
||||
updated.individual_address = snapshot.individual_address;
|
||||
}
|
||||
updated.ets_associations.clear();
|
||||
updated.ets_associations.reserve(snapshot.associations.size());
|
||||
for (const auto& association : snapshot.associations) {
|
||||
updated.ets_associations.push_back(GatewayKnxEtsAssociation{
|
||||
association.group_address, association.group_object_number});
|
||||
}
|
||||
knx_config = std::move(updated);
|
||||
ESP_LOGI(kTag, "gateway=%u loaded OpenKNX ETS address=0x%04x associations=%u from NVS namespace %s",
|
||||
channel.gateway_id, snapshot.individual_address,
|
||||
static_cast<unsigned>(snapshot.associations.size()), openKnxNamespace().c_str());
|
||||
// Bau07B0/OpenKNX memory restore is stack-heavy and owns TP-UART internals;
|
||||
// the live KNX task restores and syncs ETS associations after startup.
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> diagnosticSnapshotLocked(int short_address,
|
||||
@@ -2056,25 +2038,41 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
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);
|
||||
GatewayKnxConfig runtime_config = config.value();
|
||||
if (runtime_config.ip_router_enabled && used_ports != nullptr) {
|
||||
if (used_ports->find(runtime_config.udp_port) != used_ports->end()) {
|
||||
knx_last_error = "KNXnet/IP UDP port " + std::to_string(runtime_config.udp_port) +
|
||||
" is already owned by another runtime";
|
||||
ESP_LOGW(kTag, "gateway=%u skips duplicate KNXnet/IP UDP port %u",
|
||||
channel.gateway_id, static_cast<unsigned>(runtime_config.udp_port));
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
used_ports->insert(config->udp_port);
|
||||
used_ports->insert(runtime_config.udp_port);
|
||||
}
|
||||
if (config->ip_router_enabled && used_uarts != nullptr) {
|
||||
const int uart_port = config->tp_uart.uart_port;
|
||||
if (GatewayKnxConfigUsesTpUart(runtime_config) && used_uarts != nullptr) {
|
||||
const int uart_port = runtime_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;
|
||||
ESP_LOGW(kTag,
|
||||
"gateway=%u KNX TP-UART UART%d is already owned by another runtime; "
|
||||
"starting this KNX/IP runtime without opening a second UART driver",
|
||||
channel.gateway_id, uart_port);
|
||||
runtime_config.tp_uart.uart_port = -1;
|
||||
}
|
||||
used_uarts->insert(uart_port);
|
||||
}
|
||||
knx->setConfig(config.value());
|
||||
knx_router->setConfig(config.value());
|
||||
if (!config->ip_router_enabled) {
|
||||
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",
|
||||
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.individual_address);
|
||||
knx->setConfig(runtime_config);
|
||||
knx_router->setConfig(runtime_config);
|
||||
knx_router->setCommissioningOnly(commissioning_only);
|
||||
if (!runtime_config.ip_router_enabled) {
|
||||
knx_started = false;
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
@@ -2084,8 +2082,18 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
knx_started = err == ESP_OK;
|
||||
if (err != ESP_OK) {
|
||||
knx_last_error = knx_router->lastError().empty()
|
||||
? "failed to start KNX TP-UART router"
|
||||
? "failed to start KNXnet/IP router"
|
||||
: knx_router->lastError();
|
||||
ESP_LOGW(kTag, "gateway=%u KNX/IP start failed err=%s(%d) detail=%s",
|
||||
channel.gateway_id, esp_err_to_name(err), static_cast<int>(err),
|
||||
knx_last_error.c_str());
|
||||
} else {
|
||||
if (knx_router->tpUartOnline() && used_uarts != nullptr) {
|
||||
used_uarts->insert(runtime_config.tp_uart.uart_port);
|
||||
}
|
||||
ESP_LOGI(kTag, "gateway=%u KNX/IP started namespace=%s udp=%u detail=%s",
|
||||
channel.gateway_id, openKnxNamespace().c_str(),
|
||||
static_cast<unsigned>(runtime_config.udp_port), knx_router->lastError().c_str());
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@@ -3014,12 +3022,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
const std::optional<GatewayKnxConfig>& candidate_knx,
|
||||
std::string* error_message = nullptr) const {
|
||||
const int uart_port = config.serial.uart_port;
|
||||
if (uart_port < 0 || uart_port > 2) {
|
||||
if (uart_port < -1 || uart_port > 2) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "Modbus serial UART port must be 0, 1, or 2";
|
||||
*error_message = "Modbus serial UART port must be -1, 0, 1, or 2";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (uart_port < 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (uart_port == 0 && !service_config.allow_modbus_uart0) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message =
|
||||
@@ -3035,7 +3046,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
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) {
|
||||
GatewayKnxConfigUsesTpUart(candidate_knx.value()) &&
|
||||
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";
|
||||
@@ -3048,16 +3060,16 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
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 (config.tp_uart.uart_port < -1 || config.tp_uart.uart_port > 2) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "KNX TP-UART port must be 0, 1, or 2";
|
||||
*error_message = "KNX TP-UART port must be -1, 0, 1, or 2";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!config.ip_router_enabled || !GatewayKnxConfigUsesTpUart(config)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
const int uart_port = config.tp_uart.uart_port;
|
||||
if (uart_port == 0 && !service_config.allow_knx_uart0) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message =
|
||||
@@ -3144,7 +3156,18 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (knx_config.has_value()) {
|
||||
return knx_config;
|
||||
}
|
||||
return service_config.default_knx_config;
|
||||
if (!service_config.default_knx_config.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
GatewayKnxConfig config = service_config.default_knx_config.value();
|
||||
const uint8_t channel_index = channel.channel_index;
|
||||
config.main_group = static_cast<uint8_t>(std::min<int>(31, config.main_group + channel_index));
|
||||
const uint16_t device = config.individual_address & 0x00ff;
|
||||
if (device > 0 && device + channel_index <= 0x00ff) {
|
||||
config.individual_address = static_cast<uint16_t>((config.individual_address & 0xff00) |
|
||||
(device + channel_index));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
esp_err_t saveKnxConfig(const GatewayKnxConfig& config,
|
||||
@@ -3165,12 +3188,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return validation_err;
|
||||
}
|
||||
const bool restart_router = knx_started || (knx_router != nullptr && knx_router->started());
|
||||
if (restart_router && merged_config.ip_router_enabled && used_ports != nullptr &&
|
||||
used_ports->find(merged_config.udp_port) != used_ports->end()) {
|
||||
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(merged_config.udp_port);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (restart_router && merged_config.ip_router_enabled && used_uarts != nullptr &&
|
||||
if (restart_router && GatewayKnxConfigUsesTpUart(merged_config) && used_uarts != nullptr &&
|
||||
used_uarts->find(merged_config.tp_uart.uart_port) != used_uarts->end()) {
|
||||
knx_last_error = "KNX TP-UART UART" + std::to_string(merged_config.tp_uart.uart_port) +
|
||||
" is already used by another runtime";
|
||||
@@ -3195,6 +3213,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
if (knx_router != nullptr) {
|
||||
knx_router->setConfig(merged_config);
|
||||
knx_router->setCommissioningOnly(false);
|
||||
}
|
||||
if (restart_router) {
|
||||
return startKnx(used_ports, used_uarts);
|
||||
@@ -3350,6 +3369,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
if (GatewayModbusTransportIsSerial(config->transport)) {
|
||||
if (config->serial.uart_port < 0) {
|
||||
modbus_last_error = "Modbus serial UART disabled";
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
std::string validation_error;
|
||||
const esp_err_t serial_err = validateSerialModbusConfigLocked(
|
||||
config.value(), activeKnxConfigLocked(), &validation_error);
|
||||
@@ -3532,8 +3555,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
const int rts_pin = config.serial.rs485.enabled ? config.serial.rs485.de_pin
|
||||
: UART_PIN_NO_CHANGE;
|
||||
err = uart_set_pin(uart_port, config.serial.tx_pin, config.serial.rx_pin, rts_pin,
|
||||
UART_PIN_NO_CHANGE);
|
||||
err = uart_set_pin(uart_port,
|
||||
config.serial.tx_pin < 0 ? UART_PIN_NO_CHANGE : config.serial.tx_pin,
|
||||
config.serial.rx_pin < 0 ? UART_PIN_NO_CHANGE : config.serial.rx_pin,
|
||||
rts_pin < 0 ? UART_PIN_NO_CHANGE : rts_pin, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
@@ -3794,7 +3819,7 @@ esp_err_t GatewayBridgeService::start() {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
runtimes_.reserve(channels.size());
|
||||
for (const auto& channel : channels) {
|
||||
auto runtime = std::make_unique<ChannelRuntime>(dali_domain_, cache_, channel, config_);
|
||||
auto runtime = std::make_unique<ChannelRuntime>(*this, dali_domain_, cache_, channel, config_);
|
||||
const esp_err_t err = runtime->start();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to start bridge runtime gateway=%u: %s", channel.gateway_id,
|
||||
@@ -3820,13 +3845,18 @@ esp_err_t GatewayBridgeService::start() {
|
||||
}
|
||||
|
||||
if (config_.knx_enabled && config_.knx_startup_enabled) {
|
||||
std::set<uint16_t> used_knx_ports;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
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) {
|
||||
ESP_LOGW(kTag, "gateway=%u KNX/IP startup skipped: %s", runtime->channel.gateway_id,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
ChannelRuntime* owner = selectKnxEndpointRuntime();
|
||||
const esp_err_t err = startKnxEndpoint(owner, &used_serial_uarts);
|
||||
if (err != ESP_OK && err != ESP_ERR_NOT_FOUND && err != ESP_ERR_NOT_SUPPORTED) {
|
||||
const char* detail = owner == nullptr
|
||||
? "no KNX endpoint owner"
|
||||
: (!owner->knx_last_error.empty()
|
||||
? owner->knx_last_error.c_str()
|
||||
: (owner->knx_router == nullptr
|
||||
? "router unavailable"
|
||||
: owner->knx_router->lastError().c_str()));
|
||||
ESP_LOGW(kTag, "KNX/IP startup skipped: %s(%d) detail=%s",
|
||||
esp_err_to_name(err), static_cast<int>(err), detail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3863,20 +3893,170 @@ const GatewayBridgeService::ChannelRuntime* GatewayBridgeService::findRuntime(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GatewayBridgeService::ChannelRuntime* GatewayBridgeService::selectKnxEndpointRuntime() {
|
||||
auto eligible = [](ChannelRuntime* runtime) {
|
||||
if (runtime == nullptr) {
|
||||
return false;
|
||||
}
|
||||
LockGuard guard(runtime->lock);
|
||||
const auto config = runtime->activeKnxConfigLocked();
|
||||
return config.has_value() && config->ip_router_enabled;
|
||||
};
|
||||
|
||||
if (eligible(knx_endpoint_runtime_)) {
|
||||
return knx_endpoint_runtime_;
|
||||
}
|
||||
|
||||
ChannelRuntime* selected = nullptr;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
if (!eligible(runtime.get())) {
|
||||
continue;
|
||||
}
|
||||
if (selected == nullptr || runtime->channel.channel_index < selected->channel.channel_index) {
|
||||
selected = runtime.get();
|
||||
}
|
||||
}
|
||||
knx_endpoint_runtime_ = selected;
|
||||
if (selected != nullptr) {
|
||||
ESP_LOGI(kTag, "gateway=%u owns shared KNXnet/IP endpoint", selected->channel.gateway_id);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
bool GatewayBridgeService::isKnxEndpointRuntime(const ChannelRuntime* runtime) const {
|
||||
return runtime != nullptr && runtime == knx_endpoint_runtime_;
|
||||
}
|
||||
|
||||
esp_err_t GatewayBridgeService::startKnxEndpoint(ChannelRuntime* requested_runtime,
|
||||
std::set<int>* used_uarts) {
|
||||
if (!config_.knx_enabled) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
ChannelRuntime* owner = selectKnxEndpointRuntime();
|
||||
if (owner == nullptr) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
if (requested_runtime != nullptr && requested_runtime != owner) {
|
||||
ESP_LOGI(kTag, "gateway=%u requested KNX start; shared endpoint remains owned by gateway=%u",
|
||||
requested_runtime->channel.gateway_id, owner->channel.gateway_id);
|
||||
}
|
||||
|
||||
if (used_uarts != nullptr) {
|
||||
LockGuard guard(owner->lock);
|
||||
const auto owner_config = owner->activeKnxConfigLocked();
|
||||
if (owner_config.has_value() && GatewayKnxConfigUsesTpUart(owner_config.value())) {
|
||||
used_uarts->erase(owner_config->tp_uart.uart_port);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<uint16_t> used_knx_ports;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
if (runtime.get() == owner) {
|
||||
continue;
|
||||
}
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx_started ||
|
||||
(runtime->knx_router != nullptr && runtime->knx_router->started())) {
|
||||
const auto config = runtime->activeKnxConfigLocked();
|
||||
if (config.has_value()) {
|
||||
used_knx_ports.insert(config->udp_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
return owner->startKnx(&used_knx_ports, used_uarts);
|
||||
}
|
||||
|
||||
esp_err_t GatewayBridgeService::stopKnxEndpoint(ChannelRuntime* requested_runtime) {
|
||||
ChannelRuntime* owner = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_
|
||||
: selectKnxEndpointRuntime();
|
||||
if (owner == nullptr) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
if (requested_runtime != nullptr && requested_runtime != owner) {
|
||||
ESP_LOGI(kTag, "gateway=%u requested KNX stop; stopping shared endpoint owned by gateway=%u",
|
||||
requested_runtime->channel.gateway_id, owner->channel.gateway_id);
|
||||
}
|
||||
return owner->stopKnx();
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayBridgeService::routeKnxCemiFrame(const uint8_t* data, size_t len) {
|
||||
std::vector<ChannelRuntime*> matches;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx != nullptr && runtime->knx->matchesCemiFrame(data, len)) {
|
||||
matches.push_back(runtime.get());
|
||||
}
|
||||
}
|
||||
if (matches.empty()) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "No DALI bridge mapping matched KNX cEMI group write";
|
||||
return result;
|
||||
}
|
||||
if (matches.size() > 1) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "KNX cEMI group write matched multiple DALI bridge channels";
|
||||
ESP_LOGW(kTag, "%s", result.error.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
ChannelRuntime* runtime = matches.front();
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx == nullptr || !runtime->knx->matchesCemiFrame(data, len)) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "DALI bridge mapping changed before KNX cEMI dispatch";
|
||||
return result;
|
||||
}
|
||||
return runtime->knx->handleCemiFrame(data, len);
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayBridgeService::routeKnxGroupWrite(uint16_t group_address,
|
||||
const uint8_t* data, size_t len) {
|
||||
std::vector<ChannelRuntime*> matches;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx != nullptr && runtime->knx->matchesGroupAddress(group_address)) {
|
||||
matches.push_back(runtime.get());
|
||||
}
|
||||
}
|
||||
if (matches.empty()) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "No DALI bridge mapping matched KNX group " +
|
||||
GatewayKnxGroupAddressString(group_address);
|
||||
return result;
|
||||
}
|
||||
if (matches.size() > 1) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "KNX group " + GatewayKnxGroupAddressString(group_address) +
|
||||
" matched multiple DALI bridge channels";
|
||||
ESP_LOGW(kTag, "%s", result.error.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
ChannelRuntime* runtime = matches.front();
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx == nullptr || !runtime->knx->matchesGroupAddress(group_address)) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "DALI bridge mapping changed before KNX group dispatch";
|
||||
return result;
|
||||
}
|
||||
return runtime->knx->handleGroupWrite(group_address, data, len);
|
||||
}
|
||||
|
||||
void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
const auto update = DecodeDaliKnxStatusUpdate(frame);
|
||||
if (!update.has_value()) {
|
||||
return;
|
||||
}
|
||||
auto* runtime = findRuntime(frame.gateway_id);
|
||||
if (runtime == nullptr) {
|
||||
auto* owner = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_
|
||||
: selectKnxEndpointRuntime();
|
||||
if (owner == nullptr || owner->channel.gateway_id != frame.gateway_id) {
|
||||
return;
|
||||
}
|
||||
LockGuard guard(runtime->lock);
|
||||
if (!runtime->knx_started || runtime->knx_router == nullptr) {
|
||||
LockGuard guard(owner->lock);
|
||||
if (!owner->knx_started || owner->knx_router == nullptr) {
|
||||
return;
|
||||
}
|
||||
runtime->knx_router->publishDaliStatus(update->target, update->actual_level);
|
||||
owner->knx_router->publishDaliStatus(update->target, update->actual_level);
|
||||
}
|
||||
|
||||
void GatewayBridgeService::collectUsedRuntimeResources(
|
||||
@@ -3909,7 +4089,8 @@ void GatewayBridgeService::collectUsedRuntimeResources(
|
||||
if (knx_udp_ports != nullptr) {
|
||||
knx_udp_ports->insert(knx_config->udp_port);
|
||||
}
|
||||
if (serial_uarts != nullptr) {
|
||||
if (serial_uarts != nullptr && runtime->knx_router != nullptr &&
|
||||
runtime->knx_router->tpUartOnline()) {
|
||||
serial_uarts->insert(knx_config->tp_uart.uart_port);
|
||||
}
|
||||
}
|
||||
@@ -4214,20 +4395,21 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
return handleGet("knx", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_start") {
|
||||
std::set<uint16_t> used_knx_ports;
|
||||
ChannelRuntime* owner = selectKnxEndpointRuntime();
|
||||
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);
|
||||
collectUsedRuntimeResources(owner == nullptr ? gateway_id.value() : owner->channel.gateway_id,
|
||||
nullptr, nullptr, &used_serial_uarts);
|
||||
const esp_err_t err = startKnxEndpoint(runtime, &used_serial_uarts);
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, runtime->knx_last_error.empty()
|
||||
auto* owner = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_ : runtime;
|
||||
return ErrorResponse(err, owner->knx_last_error.empty()
|
||||
? "failed to start KNX/IP bridge"
|
||||
: runtime->knx_last_error.c_str());
|
||||
: owner->knx_last_error.c_str());
|
||||
}
|
||||
return handleGet("knx", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_stop") {
|
||||
const esp_err_t err = runtime->stopKnx();
|
||||
const esp_err_t err = stopKnxEndpoint(runtime);
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, "failed to stop KNX/IP bridge");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user