Implement KNX Gateway functionality with support for DALI integration
- Added gateway_knx.cpp to handle KNX communication and DALI bridge requests. - Implemented functions for encoding/decoding KNX telegrams and managing group writes. - Introduced GatewayKnxBridge and GatewayKnxTpIpRouter classes for managing KNX to DALI routing and IP tunneling. - Added configuration handling for KNX settings, including UART and multicast options. - Implemented error handling and logging for various KNX operations. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include "dali_domain.hpp"
|
||||
#include "gateway_cache.hpp"
|
||||
#include "gateway_cloud.hpp"
|
||||
#include "gateway_knx.hpp"
|
||||
#include "gateway_modbus.hpp"
|
||||
#include "gateway_provisioning.hpp"
|
||||
|
||||
@@ -58,6 +59,7 @@ constexpr const char* kModbusManagementPrefix = "@DALIGW";
|
||||
struct GatewayBridgeStoredConfig {
|
||||
BridgeRuntimeConfig bridge;
|
||||
std::optional<GatewayModbusConfig> modbus;
|
||||
std::optional<GatewayKnxConfig> knx;
|
||||
std::optional<GatewayBacnetBridgeConfig> bacnet_server;
|
||||
};
|
||||
|
||||
@@ -799,11 +801,15 @@ cJSON* ToCjson(const DaliValue& value) {
|
||||
DaliValue::Object GatewayBridgeStoredConfigToValue(
|
||||
const BridgeRuntimeConfig& bridge_config,
|
||||
const std::optional<GatewayModbusConfig>& modbus_config,
|
||||
const std::optional<GatewayKnxConfig>& knx_config,
|
||||
const std::optional<GatewayBacnetBridgeConfig>& bacnet_server_config) {
|
||||
DaliValue::Object out = bridge_config.toJson();
|
||||
if (modbus_config.has_value()) {
|
||||
out["modbus"] = GatewayModbusConfigToValue(modbus_config.value());
|
||||
}
|
||||
if (knx_config.has_value()) {
|
||||
out["knx"] = GatewayKnxConfigToValue(knx_config.value());
|
||||
}
|
||||
if (bacnet_server_config.has_value()) {
|
||||
DaliValue::Object bacnet;
|
||||
bacnet["deviceInstance"] = static_cast<int64_t>(bacnet_server_config->deviceInstance);
|
||||
@@ -817,9 +823,10 @@ DaliValue::Object GatewayBridgeStoredConfigToValue(
|
||||
std::string GatewayBridgeStoredConfigToJson(
|
||||
const BridgeRuntimeConfig& bridge_config,
|
||||
const std::optional<GatewayModbusConfig>& modbus_config,
|
||||
const std::optional<GatewayKnxConfig>& knx_config,
|
||||
const std::optional<GatewayBacnetBridgeConfig>& bacnet_server_config) {
|
||||
cJSON* root = ToCjson(DaliValue(GatewayBridgeStoredConfigToValue(
|
||||
bridge_config, modbus_config, bacnet_server_config)));
|
||||
bridge_config, modbus_config, knx_config, bacnet_server_config)));
|
||||
const std::string body = PrintJson(root);
|
||||
cJSON_Delete(root);
|
||||
return body;
|
||||
@@ -843,6 +850,7 @@ GatewayBridgeStoredConfig GatewayBridgeStoredConfigFromValue(const DaliValue::Ob
|
||||
GatewayBridgeStoredConfig config;
|
||||
config.bridge = BridgeRuntimeConfig::fromJson(object);
|
||||
config.modbus = GatewayModbusConfigFromValue(getObjectValue(object, "modbus"));
|
||||
config.knx = GatewayKnxConfigFromValue(getObjectValue(object, "knx"));
|
||||
config.bacnet_server = GatewayBacnetBridgeConfigFromValue(
|
||||
getObjectValue(object, "bacnetServer"));
|
||||
return config;
|
||||
@@ -1152,12 +1160,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
std::unique_ptr<DaliComm> comm;
|
||||
std::unique_ptr<DaliBridgeEngine> engine;
|
||||
std::unique_ptr<GatewayModbusBridge> modbus;
|
||||
std::unique_ptr<GatewayKnxBridge> knx;
|
||||
std::unique_ptr<GatewayKnxTpIpRouter> knx_router;
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
std::unique_ptr<GatewayBacnetBridgeAdapter> bacnet;
|
||||
#endif
|
||||
std::unique_ptr<DaliCloudBridge> cloud;
|
||||
BridgeRuntimeConfig bridge_config;
|
||||
std::optional<GatewayModbusConfig> modbus_config;
|
||||
std::optional<GatewayKnxConfig> knx_config;
|
||||
std::optional<GatewayBacnetBridgeConfig> bacnet_server_config;
|
||||
BridgeDiscoveryInventory discovery_inventory;
|
||||
std::optional<GatewayCloudConfig> cloud_config;
|
||||
@@ -1166,6 +1177,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
bool cloud_config_loaded{false};
|
||||
bool cloud_started{false};
|
||||
bool modbus_started{false};
|
||||
bool knx_started{false};
|
||||
bool bacnet_started{false};
|
||||
TaskHandle_t modbus_task_handle{nullptr};
|
||||
std::atomic_bool modbus_stop_requested{false};
|
||||
@@ -1210,6 +1222,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
const auto stored_config = GatewayBridgeStoredConfigFromValue(bridge_object);
|
||||
bridge_config = stored_config.bridge;
|
||||
modbus_config = stored_config.modbus;
|
||||
knx_config = stored_config.knx;
|
||||
bacnet_server_config = stored_config.bacnet_server;
|
||||
bridge_config_loaded = true;
|
||||
}
|
||||
@@ -1245,6 +1258,22 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
modbus->setConfig(modbus_config.value());
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
if (knx_config.has_value()) {
|
||||
knx->setConfig(knx_config.value());
|
||||
knx_router->setConfig(knx_config.value());
|
||||
}
|
||||
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
if (service_config.bacnet_enabled) {
|
||||
bacnet = std::make_unique<GatewayBacnetBridgeAdapter>(*engine);
|
||||
@@ -1257,6 +1286,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
#endif
|
||||
|
||||
applyCloudModelsLocked();
|
||||
knx_started = false;
|
||||
bacnet_started = false;
|
||||
diagnostic_snapshot_cache.clear();
|
||||
}
|
||||
@@ -1433,14 +1463,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
BridgeProvisioningStore store(bridgeNamespace());
|
||||
const esp_err_t err = store.saveObject(
|
||||
kBridgeConfigKey,
|
||||
GatewayBridgeStoredConfigToValue(parsed->bridge, parsed->modbus,
|
||||
parsed->bacnet_server));
|
||||
GatewayBridgeStoredConfigToValue(parsed->bridge, parsed->modbus, parsed->knx,
|
||||
parsed->bacnet_server));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
LockGuard guard(lock);
|
||||
bridge_config = parsed->bridge;
|
||||
modbus_config = parsed->modbus;
|
||||
knx_config = parsed->knx;
|
||||
bacnet_server_config = parsed->bacnet_server;
|
||||
bridge_config_loaded = true;
|
||||
applyBridgeConfigLocked();
|
||||
@@ -1456,6 +1487,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
LockGuard guard(lock);
|
||||
bridge_config = BridgeRuntimeConfig{};
|
||||
modbus_config.reset();
|
||||
knx_config.reset();
|
||||
bacnet_server_config.reset();
|
||||
bridge_config_loaded = false;
|
||||
applyBridgeConfigLocked();
|
||||
@@ -1800,6 +1832,41 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t startKnx() {
|
||||
LockGuard guard(lock);
|
||||
if (!service_config.knx_enabled) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
if (knx == nullptr || knx_router == nullptr) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
const auto config = activeKnxConfigLocked();
|
||||
if (!config.has_value()) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
knx->setConfig(config.value());
|
||||
knx_router->setConfig(config.value());
|
||||
if (!config->ip_router_enabled) {
|
||||
knx_started = false;
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
const esp_err_t err = knx_router->start(service_config.knx_task_stack_size,
|
||||
service_config.knx_task_priority);
|
||||
knx_started = err == ESP_OK;
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t stopKnx() {
|
||||
LockGuard guard(lock);
|
||||
if (knx_router != nullptr) {
|
||||
const esp_err_t err = knx_router->stop();
|
||||
knx_started = false;
|
||||
return err;
|
||||
}
|
||||
knx_started = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
GatewayBridgeHttpResponse execute(std::string_view json) {
|
||||
cJSON* root = cJSON_ParseWithLength(json.data(), json.size());
|
||||
if (root == nullptr) {
|
||||
@@ -1887,6 +1954,42 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
cJSON_AddItemToObject(root, "bacnet", bacnet_json);
|
||||
}
|
||||
|
||||
cJSON* knx_json = cJSON_CreateObject();
|
||||
if (knx_json != nullptr) {
|
||||
const auto effective_knx = knx_config.has_value() ? knx_config : service_config.default_knx_config;
|
||||
cJSON_AddBoolToObject(knx_json, "enabled", service_config.knx_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "started", knx_started);
|
||||
cJSON_AddBoolToObject(knx_json, "routerReady", knx_router != nullptr && knx_router->started());
|
||||
if (knx_router != nullptr) {
|
||||
cJSON_AddStringToObject(knx_json, "lastError", knx_router->lastError().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, "tunnelEnabled", effective_knx->tunnel_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "multicastEnabled",
|
||||
effective_knx->multicast_enabled);
|
||||
cJSON_AddNumberToObject(knx_json, "mainGroup", effective_knx->main_group);
|
||||
cJSON_AddNumberToObject(knx_json, "udpPort", effective_knx->udp_port);
|
||||
cJSON_AddStringToObject(knx_json, "multicastAddress",
|
||||
effective_knx->multicast_address.c_str());
|
||||
cJSON_AddNumberToObject(knx_json, "individualAddress",
|
||||
effective_knx->individual_address);
|
||||
cJSON* serial_json = cJSON_CreateObject();
|
||||
if (serial_json != nullptr) {
|
||||
cJSON_AddNumberToObject(serial_json, "uartPort", effective_knx->tp_uart.uart_port);
|
||||
cJSON_AddNumberToObject(serial_json, "txPin", effective_knx->tp_uart.tx_pin);
|
||||
cJSON_AddNumberToObject(serial_json, "rxPin", effective_knx->tp_uart.rx_pin);
|
||||
cJSON_AddNumberToObject(serial_json, "baudrate", effective_knx->tp_uart.baudrate);
|
||||
cJSON_AddItemToObject(knx_json, "tpUart", serial_json);
|
||||
}
|
||||
}
|
||||
cJSON_AddItemToObject(root, "knx", knx_json);
|
||||
}
|
||||
|
||||
cJSON* cloud_json = cJSON_CreateObject();
|
||||
if (cloud_json != nullptr) {
|
||||
cJSON_AddBoolToObject(cloud_json, "enabled", service_config.cloud_enabled);
|
||||
@@ -1906,6 +2009,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
GatewayBridgeHttpResponse configJson() const {
|
||||
return GatewayBridgeHttpResponse{ESP_OK,
|
||||
GatewayBridgeStoredConfigToJson(bridge_config, modbus_config,
|
||||
knx_config,
|
||||
bacnet_server_config)};
|
||||
}
|
||||
|
||||
@@ -2104,6 +2208,37 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return JsonOk(root);
|
||||
}
|
||||
|
||||
GatewayBridgeHttpResponse knxBindingsJson() const {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(root, "gatewayId", channel.gateway_id);
|
||||
cJSON* bindings = cJSON_CreateArray();
|
||||
if (bindings != nullptr && knx != nullptr) {
|
||||
for (const auto& binding : knx->describeDaliBindings()) {
|
||||
cJSON* item = cJSON_CreateObject();
|
||||
if (item == nullptr) {
|
||||
continue;
|
||||
}
|
||||
cJSON_AddStringToObject(item, "address", binding.address.c_str());
|
||||
cJSON_AddNumberToObject(item, "rawAddress", binding.group_address);
|
||||
cJSON_AddNumberToObject(item, "mainGroup", binding.main_group);
|
||||
cJSON_AddNumberToObject(item, "middleGroup", binding.middle_group);
|
||||
cJSON_AddNumberToObject(item, "subGroup", binding.sub_group);
|
||||
cJSON_AddStringToObject(item, "name", binding.name.c_str());
|
||||
cJSON_AddStringToObject(item, "datapointType", binding.datapoint_type.c_str());
|
||||
cJSON_AddStringToObject(item, "dataType",
|
||||
GatewayKnxDataTypeToString(binding.data_type));
|
||||
cJSON_AddStringToObject(item, "targetKind",
|
||||
GatewayKnxTargetKindToString(binding.target.kind));
|
||||
if (binding.target.address >= 0) {
|
||||
cJSON_AddNumberToObject(item, "targetAddress", binding.target.address);
|
||||
}
|
||||
cJSON_AddItemToArray(bindings, item);
|
||||
}
|
||||
}
|
||||
cJSON_AddItemToObject(root, "bindings", bindings);
|
||||
return JsonOk(root);
|
||||
}
|
||||
|
||||
GatewayBridgeHttpResponse bacnetBindingsJson() {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(root, "gatewayId", channel.gateway_id);
|
||||
@@ -2597,7 +2732,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
BridgeProvisioningStore store(bridgeNamespace());
|
||||
const esp_err_t err = store.saveObject(
|
||||
kBridgeConfigKey,
|
||||
GatewayBridgeStoredConfigToValue(bridge_config, config, bacnet_server_config));
|
||||
GatewayBridgeStoredConfigToValue(bridge_config, config, knx_config,
|
||||
bacnet_server_config));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
@@ -2609,6 +2745,34 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<GatewayKnxConfig> activeKnxConfigLocked() const {
|
||||
if (knx_config.has_value()) {
|
||||
return knx_config;
|
||||
}
|
||||
return service_config.default_knx_config;
|
||||
}
|
||||
|
||||
esp_err_t saveKnxConfig(const GatewayKnxConfig& config) {
|
||||
LockGuard guard(lock);
|
||||
BridgeProvisioningStore store(bridgeNamespace());
|
||||
const esp_err_t err = store.saveObject(
|
||||
kBridgeConfigKey,
|
||||
GatewayBridgeStoredConfigToValue(bridge_config, modbus_config, config,
|
||||
bacnet_server_config));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
knx_config = config;
|
||||
bridge_config_loaded = true;
|
||||
if (knx != nullptr) {
|
||||
knx->setConfig(config);
|
||||
}
|
||||
if (knx_router != nullptr) {
|
||||
knx_router->setConfig(config);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> processModbusPdu(const GatewayModbusConfig& config,
|
||||
uint8_t unit_id,
|
||||
const std::vector<uint8_t>& pdu) {
|
||||
@@ -3215,6 +3379,16 @@ esp_err_t GatewayBridgeService::start() {
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.knx_enabled && config_.knx_startup_enabled) {
|
||||
for (const auto& runtime : runtimes_) {
|
||||
const esp_err_t err = runtime->startKnx();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.bacnet_enabled && config_.bacnet_startup_enabled) {
|
||||
for (const auto& runtime : runtimes_) {
|
||||
const esp_err_t err = runtime->startBacnet();
|
||||
@@ -3294,6 +3468,9 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
|
||||
if (action == "modbus") {
|
||||
return runtime->modbusBindingsJson();
|
||||
}
|
||||
if (action == "knx") {
|
||||
return runtime->knxBindingsJson();
|
||||
}
|
||||
if (action == "bacnet") {
|
||||
return runtime->bacnetBindingsJson();
|
||||
}
|
||||
@@ -3502,6 +3679,41 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
}
|
||||
return handleGet("modbus", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_config" || action == "save_knx") {
|
||||
cJSON* knx_root = cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (knx_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "invalid KNX config JSON");
|
||||
}
|
||||
const cJSON* knx_node = cJSON_GetObjectItemCaseSensitive(knx_root, "knx");
|
||||
if (knx_node == nullptr) {
|
||||
knx_node = knx_root;
|
||||
}
|
||||
const DaliValue knx_value = FromCjson(knx_node);
|
||||
cJSON_Delete(knx_root);
|
||||
const auto parsed = GatewayKnxConfigFromValue(&knx_value);
|
||||
if (!parsed.has_value()) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "invalid KNX config");
|
||||
}
|
||||
const esp_err_t err = runtime->saveKnxConfig(parsed.value());
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, "failed to save KNX bridge config");
|
||||
}
|
||||
return handleGet("knx", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_start") {
|
||||
const esp_err_t err = runtime->startKnx();
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, "failed to start KNX/IP bridge");
|
||||
}
|
||||
return handleGet("knx", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_stop") {
|
||||
const esp_err_t err = runtime->stopKnx();
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, "failed to stop KNX/IP bridge");
|
||||
}
|
||||
return handleGet("knx", gateway_id.value());
|
||||
}
|
||||
if (action == "bacnet_start") {
|
||||
const esp_err_t err = runtime->startBacnet();
|
||||
if (err != ESP_OK) {
|
||||
|
||||
Reference in New Issue
Block a user