Add OpenKNX IDF component with TPUart integration

- Created CMakeLists.txt for the OpenKNX IDF component, ensuring dependencies on OpenKNX and TPUart submodules.
- Implemented Arduino compatibility header for basic functions like millis, delay, pinMode, and digitalRead.
- Developed EspIdfPlatform class for network interface management and multicast communication.
- Added EtsMemoryLoader for loading ETS memory snapshots and managing associations.
- Introduced TpuartUartInterface for UART communication with methods for reading, writing, and managing callbacks.
- Implemented arduino_compat.cpp for Arduino-like functionality on ESP-IDF.
- Created source files for platform and memory loader implementations.
- Updated submodules for knx, knx_dali_gw, and tpuart.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-11 07:05:40 +08:00
parent 1b8753636f
commit 70367f53ca
20 changed files with 1359 additions and 20 deletions
+1
View File
@@ -10,6 +10,7 @@ set(GATEWAY_BRIDGE_REQUIRES
log
lwip
nvs_flash
openknx_idf
)
idf_component_register(
@@ -15,6 +15,7 @@
#include "gateway_knx.hpp"
#include "gateway_modbus.hpp"
#include "gateway_provisioning.hpp"
#include "openknx_idf/ets_memory_loader.h"
#include "cJSON.h"
#include "driver/uart.h"
@@ -1206,6 +1207,10 @@ struct GatewayBridgeService::ChannelRuntime {
return "dali_cloud_" + std::to_string(channel.gateway_id);
}
std::string openKnxNamespace() const {
return "openknx_" + std::to_string(channel.gateway_id);
}
esp_err_t start() {
comm = std::make_unique<DaliComm>(
[this](const uint8_t* data, size_t len) {
@@ -1254,6 +1259,8 @@ struct GatewayBridgeService::ChannelRuntime {
engine->upsertModel(model);
}
refreshOpenKnxEtsAssociationsLocked();
modbus = std::make_unique<GatewayModbusBridge>(*engine);
if (modbus_config.has_value()) {
modbus->setConfig(modbus_config.value());
@@ -1292,6 +1299,31 @@ struct GatewayBridgeService::ChannelRuntime {
diagnostic_snapshot_cache.clear();
}
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());
if (snapshot.associations.empty()) {
return;
}
GatewayKnxConfig updated = active_config.value();
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 %u OpenKNX ETS associations from NVS namespace %s",
channel.gateway_id, static_cast<unsigned>(snapshot.associations.size()),
openKnxNamespace().c_str());
}
std::optional<DaliDomainSnapshot> diagnosticSnapshotLocked(int short_address,
std::string_view kind) {
if (!ValidShortAddress(short_address) || kind.empty()) {
@@ -2004,13 +2036,15 @@ struct GatewayBridgeService::ChannelRuntime {
knx_last_error.empty() ? router_error.c_str()
: knx_last_error.c_str());
if (effective_knx.has_value()) {
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled",
effective_knx->dali_router_enabled);
cJSON_AddBoolToObject(knx_json, "ipRouterEnabled",
effective_knx->ip_router_enabled);
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled", effective_knx->dali_router_enabled);
cJSON_AddBoolToObject(knx_json, "ipRouterEnabled", effective_knx->ip_router_enabled);
cJSON_AddBoolToObject(knx_json, "tunnelEnabled", effective_knx->tunnel_enabled);
cJSON_AddBoolToObject(knx_json, "multicastEnabled",
effective_knx->multicast_enabled);
cJSON_AddBoolToObject(knx_json, "multicastEnabled", effective_knx->multicast_enabled);
cJSON_AddBoolToObject(knx_json, "etsDatabaseEnabled", effective_knx->ets_database_enabled);
cJSON_AddNumberToObject(knx_json, "etsBindingCount",
knx == nullptr ? 0 : knx->etsBindingCount());
cJSON_AddStringToObject(knx_json, "mappingMode",
GatewayKnxMappingModeToString(effective_knx->mapping_mode));
cJSON_AddNumberToObject(knx_json, "mainGroup", effective_knx->main_group);
cJSON_AddNumberToObject(knx_json, "udpPort", effective_knx->udp_port);
cJSON_AddStringToObject(knx_json, "multicastAddress",
@@ -2262,6 +2296,17 @@ struct GatewayBridgeService::ChannelRuntime {
cJSON_AddNumberToObject(item, "mainGroup", binding.main_group);
cJSON_AddNumberToObject(item, "middleGroup", binding.middle_group);
cJSON_AddNumberToObject(item, "subGroup", binding.sub_group);
cJSON_AddStringToObject(item, "mappingMode",
GatewayKnxMappingModeToString(binding.mapping_mode));
if (binding.group_object_number >= 0) {
cJSON_AddNumberToObject(item, "objectNumber", binding.group_object_number);
}
if (binding.channel_index >= 0) {
cJSON_AddNumberToObject(item, "channelIndex", binding.channel_index);
}
if (!binding.object_role.empty()) {
cJSON_AddStringToObject(item, "objectRole", binding.object_role.c_str());
}
cJSON_AddStringToObject(item, "name", binding.name.c_str());
cJSON_AddStringToObject(item, "datapointType", binding.datapoint_type.c_str());
cJSON_AddStringToObject(item, "dataType",
@@ -2894,22 +2939,28 @@ struct GatewayBridgeService::ChannelRuntime {
std::set<uint16_t>* used_ports = nullptr,
std::set<int>* used_uarts = nullptr) {
LockGuard guard(lock);
GatewayKnxConfig merged_config = config;
const auto previous_knx = activeKnxConfigLocked();
if (merged_config.ets_associations.empty() && previous_knx.has_value() &&
!previous_knx->ets_associations.empty()) {
merged_config.ets_associations = previous_knx->ets_associations;
}
std::string validation_error;
const esp_err_t validation_err = validateKnxConfigLocked(
config, activeModbusConfigLocked(), &validation_error);
merged_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);
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 && 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) +
if (restart_router && merged_config.ip_router_enabled && 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";
return ESP_ERR_INVALID_STATE;
}
@@ -2920,18 +2971,18 @@ struct GatewayBridgeService::ChannelRuntime {
BridgeProvisioningStore store(bridgeNamespace());
const esp_err_t err = store.saveObject(
kBridgeConfigKey,
GatewayBridgeStoredConfigToValue(bridge_config, modbus_config, config,
GatewayBridgeStoredConfigToValue(bridge_config, modbus_config, merged_config,
bacnet_server_config));
if (err != ESP_OK) {
return err;
}
knx_config = config;
knx_config = merged_config;
bridge_config_loaded = true;
if (knx != nullptr) {
knx->setConfig(config);
knx->setConfig(merged_config);
}
if (knx_router != nullptr) {
knx_router->setConfig(config);
knx_router->setConfig(merged_config);
}
if (restart_router) {
return startKnx(used_ports, used_uarts);