feat(gateway): add DALI bus ID configuration and enhance group object write handling

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-16 03:31:12 +08:00
parent e79223c87e
commit 277379abd7
13 changed files with 288 additions and 37 deletions
@@ -68,6 +68,7 @@ struct DaliChannelInfo {
uint8_t gateway_id{0};
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
std::string name;
std::optional<uint8_t> native_bus_id;
};
struct DaliRawFrame {
+6 -2
View File
@@ -474,8 +474,12 @@ std::vector<DaliChannelInfo> DaliDomainService::channelInfo() const {
std::vector<DaliChannelInfo> info;
info.reserve(channels_.size());
for (const auto& channel : channels_) {
info.push_back(DaliChannelInfo{channel->config.channel_index, channel->config.gateway_id,
channel->phy_kind, channel->config.name});
DaliChannelInfo item{channel->config.channel_index, channel->config.gateway_id,
channel->phy_kind, channel->config.name};
if (channel->hardware_bus.has_value()) {
item.native_bus_id = channel->hardware_bus->bus_id;
}
info.push_back(std::move(item));
}
return info;
}
@@ -74,6 +74,8 @@ class GatewayBridgeService {
esp_err_t stopKnxEndpoint(ChannelRuntime* requested_runtime);
DaliBridgeResult routeKnxGroupWrite(uint16_t group_address, const uint8_t* data,
size_t len);
DaliBridgeResult routeKnxGroupObjectWrite(uint16_t group_object_number,
const uint8_t* data, size_t len);
void handleDaliRawFrame(const DaliRawFrame& frame);
void collectUsedRuntimeResources(uint8_t except_gateway_id,
std::set<uint16_t>* modbus_tcp_ports,
@@ -1438,6 +1438,10 @@ struct GatewayBridgeService::ChannelRuntime {
[this](uint16_t group_address, const uint8_t* data, size_t len) {
return service.routeKnxGroupWrite(group_address, data, len);
});
knx_router->setGroupObjectWriteHandler(
[this](uint16_t group_object_number, const uint8_t* data, size_t len) {
return service.routeKnxGroupObjectWrite(group_object_number, data, len);
});
if (const auto active_knx = activeKnxConfigLocked(); active_knx.has_value()) {
knx->setConfig(active_knx.value());
knx_router->setConfig(active_knx.value());
@@ -2058,12 +2062,13 @@ struct GatewayBridgeService::ChannelRuntime {
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 nineBit=%d "
"multicast=%d multicastGroup=%s mainGroup=%u daliBus=%u tpUart=%d tx=%d rx=%d nineBit=%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,
static_cast<unsigned>(runtime_config.main_group),
static_cast<unsigned>(runtime_config.dali_bus_id), runtime_config.tp_uart.uart_port,
runtime_config.tp_uart.tx_pin, runtime_config.tp_uart.rx_pin,
runtime_config.tp_uart.nine_bit_mode,
runtime_config.individual_address);
@@ -3205,13 +3210,6 @@ struct GatewayBridgeService::ChannelRuntime {
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.ip_interface_individual_address & 0x00ff;
if (device > 0 && device + channel_index <= 0x00ff) {
config.ip_interface_individual_address = static_cast<uint16_t>(
(config.ip_interface_individual_address & 0xff00) | (device + channel_index));
}
return config;
}
@@ -3945,7 +3943,9 @@ GatewayBridgeService::ChannelRuntime* GatewayBridgeService::selectKnxEndpointRun
}
LockGuard guard(runtime->lock);
const auto config = runtime->activeKnxConfigLocked();
return config.has_value() && config->ip_router_enabled;
return config.has_value() && config->ip_router_enabled &&
runtime->channel.native_bus_id.has_value() &&
runtime->channel.native_bus_id.value() == config->dali_bus_id;
};
if (eligible(knx_endpoint_runtime_)) {
@@ -3963,7 +3963,13 @@ GatewayBridgeService::ChannelRuntime* GatewayBridgeService::selectKnxEndpointRun
}
knx_endpoint_runtime_ = selected;
if (selected != nullptr) {
ESP_LOGI(kTag, "gateway=%u owns shared KNXnet/IP endpoint", selected->channel.gateway_id);
LockGuard guard(selected->lock);
const auto config = selected->activeKnxConfigLocked();
ESP_LOGI(kTag, "gateway=%u owns shared KNXnet/IP endpoint daliBus=%u",
selected->channel.gateway_id,
config.has_value() ? static_cast<unsigned>(config->dali_bus_id) : 0U);
} else {
ESP_LOGW(kTag, "no native DALI channel matches the configured KNX DALI bus id");
}
return selected;
}
@@ -4026,37 +4032,45 @@ esp_err_t GatewayBridgeService::stopKnxEndpoint(ChannelRuntime* requested_runtim
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()) {
ChannelRuntime* runtime = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_
: selectKnxEndpointRuntime();
if (runtime == nullptr) {
DaliBridgeResult result;
result.error = "No DALI bridge mapping matched KNX group " +
result.error = "No DALI channel is selected for 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";
result.error = "Selected DALI bus does not map KNX group " +
GatewayKnxGroupAddressString(group_address);
return result;
}
return runtime->knx->handleGroupWrite(group_address, data, len);
}
DaliBridgeResult GatewayBridgeService::routeKnxGroupObjectWrite(uint16_t group_object_number,
const uint8_t* data, size_t len) {
ChannelRuntime* runtime = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_
: selectKnxEndpointRuntime();
if (runtime == nullptr) {
DaliBridgeResult result;
result.error = "No DALI channel is selected for KNX group object " +
std::to_string(group_object_number);
return result;
}
LockGuard guard(runtime->lock);
if (runtime->knx == nullptr) {
DaliBridgeResult result;
result.error = "Selected DALI bus has no KNX bridge for group object " +
std::to_string(group_object_number);
return result;
}
return runtime->knx->handleGroupObjectWrite(group_object_number, data, len);
}
void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) {
const auto update = DecodeDaliKnxStatusUpdate(frame);
if (!update.has_value()) {
@@ -13,6 +13,8 @@
#include <string>
#include <vector>
class GroupObject;
namespace gateway::openknx {
class TpuartUartInterface;
@@ -22,6 +24,8 @@ class EtsDeviceRuntime {
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
using GroupWriteHandler = std::function<void(uint16_t group_address, const uint8_t* data,
size_t len)>;
using GroupObjectWriteHandler = std::function<void(uint16_t group_object_number,
const uint8_t* data, size_t len)>;
using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response)>;
@@ -47,6 +51,7 @@ class EtsDeviceRuntime {
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
FunctionPropertyHandler state_handler);
void setGroupWriteHandler(GroupWriteHandler handler);
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
void setBusFrameSender(CemiFrameSender sender);
void setNetworkInterface(esp_netif_t* netif);
bool hasTpUart() const;
@@ -65,6 +70,7 @@ class EtsDeviceRuntime {
static void EmitTunnelFrame(CemiFrame& frame, void* context);
static void HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
uint8_t data_length, void* context);
static void HandleGroupObjectWrite(GroupObject& ko);
static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length);
@@ -75,6 +81,7 @@ class EtsDeviceRuntime {
static bool DispatchFunctionProperty(FunctionPropertyHandler* handler, uint8_t object_index,
uint8_t property_id, uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length);
void installGroupObjectCallbacks();
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
bool shouldConsumeBusFrame(CemiFrame& frame) const;
@@ -85,8 +92,11 @@ class EtsDeviceRuntime {
CemiFrameSender sender_;
CemiFrameSender bus_frame_sender_;
GroupWriteHandler group_write_handler_;
GroupObjectWriteHandler group_object_write_handler_;
FunctionPropertyHandler command_handler_;
FunctionPropertyHandler state_handler_;
bool suppress_group_object_write_callback_{false};
uint16_t group_object_callback_count_{0};
};
} // namespace gateway::openknx
@@ -65,6 +65,7 @@ struct GatewayKnxConfig {
bool ets_database_enabled{true};
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
uint8_t main_group{0};
uint8_t dali_bus_id{0};
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
uint16_t ip_interface_individual_address{0xff01};
@@ -147,6 +148,8 @@ class GatewayKnxBridge {
bool matchesGroupAddress(uint16_t group_address) const;
DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data,
size_t len);
DaliBridgeResult handleGroupObjectWrite(uint16_t group_object_number,
const uint8_t* data, size_t len);
bool handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
@@ -200,6 +203,9 @@ class GatewayKnxTpIpRouter {
using GroupWriteHandler = std::function<DaliBridgeResult(uint16_t group_address,
const uint8_t* data,
size_t len)>;
using GroupObjectWriteHandler = std::function<DaliBridgeResult(uint16_t group_object_number,
const uint8_t* data,
size_t len)>;
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge,
std::string openknx_namespace = "openknx");
@@ -208,6 +214,7 @@ class GatewayKnxTpIpRouter {
void setConfig(const GatewayKnxConfig& config);
void setCommissioningOnly(bool enabled);
void setGroupWriteHandler(GroupWriteHandler handler);
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
const GatewayKnxConfig& config() const;
bool tpUartOnline() const;
bool programmingMode();
@@ -338,6 +345,7 @@ class GatewayKnxTpIpRouter {
GatewayKnxBridge& bridge_;
GroupWriteHandler group_write_handler_;
GroupObjectWriteHandler group_object_write_handler_;
std::string openknx_namespace_;
GatewayKnxConfig config_;
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
@@ -4,6 +4,7 @@
#include "esp_log.h"
#include "knx/cemi_server.h"
#include "knx/group_object.h"
#include "knx/secure_application_layer.h"
#include "knx/property.h"
#include "tpuart_uart_interface.h"
@@ -19,6 +20,7 @@ namespace gateway::openknx {
namespace {
thread_local EtsDeviceRuntime* active_function_property_runtime = nullptr;
EtsDeviceRuntime* active_group_object_runtime = nullptr;
class ActiveFunctionPropertyRuntimeScope {
public:
@@ -121,6 +123,7 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
}
ESP_LOGI("gateway_knx", "OpenKNX loading memory namespace=%s", nvs_namespace_.c_str());
device_.readMemory();
installGroupObjectCallbacks();
if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) &&
IsUsableIndividualAddress(fallback_individual_address)) {
device_.deviceObject().individualAddress(fallback_individual_address);
@@ -149,6 +152,19 @@ EtsDeviceRuntime::~EtsDeviceRuntime() {
#ifdef USE_DATASECURE
device_.secureGroupWriteCallback(nullptr, nullptr);
#endif
#ifdef SMALL_GROUPOBJECT
if (active_group_object_runtime == this) {
GroupObject::classCallback(GroupObjectUpdatedHandler{});
}
#else
auto& table = device_.groupObjectTable();
for (uint16_t asap = 1; asap <= table.entryCount(); ++asap) {
table.get(asap).callback(GroupObjectUpdatedHandler{});
}
#endif
if (active_group_object_runtime == this) {
active_group_object_runtime = nullptr;
}
device_.functionPropertyCallback(nullptr);
device_.functionPropertyStateCallback(nullptr);
if (auto* server = device_.getCemiServer()) {
@@ -223,6 +239,11 @@ void EtsDeviceRuntime::setGroupWriteHandler(GroupWriteHandler handler) {
group_write_handler_ = std::move(handler);
}
void EtsDeviceRuntime::setGroupObjectWriteHandler(GroupObjectWriteHandler handler) {
group_object_write_handler_ = std::move(handler);
installGroupObjectCallbacks();
}
void EtsDeviceRuntime::setBusFrameSender(CemiFrameSender sender) {
bus_frame_sender_ = std::move(sender);
}
@@ -272,11 +293,20 @@ bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
if (!consumed) {
return false;
}
const bool suppress_group_object_route =
frame.messageCode() == L_data_req && frame.addressType() == GroupAddress &&
frame.apdu().type() == GroupValueWrite;
const bool previous_suppression = suppress_group_object_write_callback_;
if (suppress_group_object_route) {
suppress_group_object_write_callback_ = true;
}
sender_ = std::move(sender);
ActiveFunctionPropertyRuntimeScope callback_scope(this);
server->frameReceived(frame);
loop();
sender_ = nullptr;
suppress_group_object_write_callback_ = previous_suppression;
installGroupObjectCallbacks();
return consumed;
}
@@ -293,6 +323,7 @@ bool EtsDeviceRuntime::handleBusFrame(const uint8_t* data, size_t len) {
}
data_link_layer->externalFrameReceived(frame);
loop();
installGroupObjectCallbacks();
return consumed;
}
@@ -314,10 +345,13 @@ bool EtsDeviceRuntime::emitGroupValue(uint16_t group_object_number, const uint8_
} else {
std::copy_n(data, len, group_object.valueRef());
}
const bool previous_suppression = suppress_group_object_write_callback_;
suppress_group_object_write_callback_ = true;
sender_ = std::move(sender);
group_object.objectWritten();
loop();
sender_ = nullptr;
suppress_group_object_write_callback_ = previous_suppression;
return true;
}
@@ -349,10 +383,36 @@ void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
void EtsDeviceRuntime::HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
uint8_t data_length, void* context) {
auto* self = static_cast<EtsDeviceRuntime*>(context);
if (self == nullptr || !self->group_write_handler_) {
if (self == nullptr) {
return;
}
self->group_write_handler_(group_address, data, data_length);
if (self->group_object_write_handler_) {
return;
}
if (self->group_write_handler_) {
self->group_write_handler_(group_address, data, data_length);
}
}
void EtsDeviceRuntime::HandleGroupObjectWrite(GroupObject& ko) {
auto* self = active_group_object_runtime;
if (self == nullptr || self->suppress_group_object_write_callback_ ||
!self->group_object_write_handler_) {
return;
}
const size_t value_size = ko.valueSize();
const uint8_t* value = ko.valueRef();
if (value == nullptr || value_size == 0) {
ESP_LOGW("gateway_knx", "OpenKNX group-object callback ignored namespace=%s ko=%u len=%u",
self->nvs_namespace_.c_str(), static_cast<unsigned>(ko.asap()),
static_cast<unsigned>(value_size));
return;
}
const std::string value_hex = HexBytesString(value, value_size);
ESP_LOGI("gateway_knx", "OpenKNX group-object callback namespace=%s ko=%u len=%u value=%s",
self->nvs_namespace_.c_str(), static_cast<unsigned>(ko.asap()),
static_cast<unsigned>(value_size), value_hex.c_str());
self->group_object_write_handler_(ko.asap(), value, value_size);
}
bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
@@ -395,6 +455,24 @@ bool EtsDeviceRuntime::DispatchFunctionProperty(FunctionPropertyHandler* handler
return true;
}
void EtsDeviceRuntime::installGroupObjectCallbacks() {
active_group_object_runtime = this;
auto& table = device_.groupObjectTable();
const uint16_t count = table.entryCount();
#ifdef SMALL_GROUPOBJECT
GroupObject::classCallback(&EtsDeviceRuntime::HandleGroupObjectWrite);
#else
for (uint16_t asap = 1; asap <= count; ++asap) {
table.get(asap).callback(&EtsDeviceRuntime::HandleGroupObjectWrite);
}
#endif
if (count != group_object_callback_count_) {
ESP_LOGI("gateway_knx", "OpenKNX group-object callbacks namespace=%s count=%u",
nvs_namespace_.c_str(), static_cast<unsigned>(count));
group_object_callback_count_ = count;
}
}
uint16_t EtsDeviceRuntime::DefaultTunnelClientAddress(uint16_t individual_address) {
if (!IsUsableIndividualAddress(individual_address)) {
return 0x1101;
+91 -1
View File
@@ -610,6 +610,23 @@ std::optional<int> MetadataInt(const DaliBridgeResult& result, const std::string
return getObjectInt(result.metadata, key);
}
std::string HexBytes(const uint8_t* data, size_t len) {
if (data == nullptr || len == 0) {
return {};
}
std::string out;
out.reserve(len * 3);
char buffer[4] = {0};
for (size_t index = 0; index < len; ++index) {
std::snprintf(buffer, sizeof(buffer), "%02X", data[index]);
out += buffer;
if (index + 1 < len) {
out.push_back(' ');
}
}
return out;
}
DaliBridgeRequest RequestForTarget(uint16_t group_address,
const GatewayKnxDaliTarget& target,
BridgeOperation operation) {
@@ -642,6 +659,17 @@ DaliBridgeResult ErrorResult(uint16_t group_address, const char* message) {
return result;
}
DaliBridgeResult IgnoredResult(uint16_t group_address, uint16_t group_object_number,
const char* reason) {
DaliBridgeResult result;
result.sequence = "knx-" + GatewayKnxGroupAddressString(group_address);
result.ok = true;
result.metadata["ignored"] = true;
result.metadata["groupObjectNumber"] = static_cast<int>(group_object_number);
result.metadata["reason"] = reason == nullptr ? "ignored" : reason;
return result;
}
bool SendAll(int sock, const uint8_t* data, size_t len, const sockaddr_in& remote) {
return sendto(sock, data, len, 0, reinterpret_cast<const sockaddr*>(&remote),
sizeof(remote)) == static_cast<int>(len);
@@ -716,6 +744,11 @@ std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value
config.main_group = static_cast<uint8_t>(
std::clamp(ObjectIntAny(object, {"mainGroup", "main_group"}).value_or(config.main_group),
0, 31));
config.dali_bus_id = static_cast<uint8_t>(std::clamp(
ObjectIntAny(object, {"daliBusId", "dali_bus_id", "targetDaliBusId",
"target_dali_bus_id"})
.value_or(config.dali_bus_id),
0, 15));
config.udp_port = static_cast<uint16_t>(std::clamp(
ObjectIntAny(object, {"udpPort", "port", "udp_port"}).value_or(config.udp_port), 1,
65535));
@@ -790,6 +823,7 @@ DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config) {
out["etsDatabaseEnabled"] = config.ets_database_enabled;
out["mappingMode"] = GatewayKnxMappingModeToString(config.mapping_mode);
out["mainGroup"] = static_cast<int>(config.main_group);
out["daliBusId"] = static_cast<int>(config.dali_bus_id);
out["udpPort"] = static_cast<int>(config.udp_port);
out["multicastAddress"] = config.multicast_address;
out["ipInterfaceIndividualAddress"] =
@@ -1186,6 +1220,42 @@ DaliBridgeResult GatewayKnxBridge::handleGroupWrite(uint16_t group_address, cons
return executeForDecodedWrite(group_address, data_type.value(), target.value(), data, len);
}
DaliBridgeResult GatewayKnxBridge::handleGroupObjectWrite(uint16_t group_object_number,
const uint8_t* data, size_t len) {
const uint16_t group_address = GwReg1GroupAddressForObject(config_.main_group,
group_object_number);
const std::string payload = HexBytes(data, len);
ESP_LOGI(kTag, "OpenKNX KO write ko=%u derivedGa=%s len=%u payload=%s",
static_cast<unsigned>(group_object_number),
GatewayKnxGroupAddressString(group_address).c_str(), static_cast<unsigned>(len),
payload.c_str());
if (!config_.dali_router_enabled) {
return ErrorResult(group_address, "KNX to DALI router disabled");
}
const auto binding = GwReg1BindingForObject(config_.main_group, group_object_number);
if (!binding.has_value()) {
ESP_LOGW(kTag, "OpenKNX KO write ignored ko=%u: unsupported GW-REG1 object",
static_cast<unsigned>(group_object_number));
return IgnoredResult(group_address, group_object_number,
"unsupported GW-REG1 group object");
}
DaliBridgeResult result = executeForDecodedWrite(binding->group_address, binding->data_type,
binding->target, data, len);
result.metadata["source"] = "openknx_group_object";
result.metadata["groupObjectNumber"] = static_cast<int>(group_object_number);
result.metadata["objectRole"] = binding->object_role;
if (result.ok) {
ESP_LOGI(kTag, "OpenKNX KO write routed ko=%u role=%s target=%s",
static_cast<unsigned>(group_object_number), binding->object_role.c_str(),
TargetName(binding->target).c_str());
} else {
ESP_LOGW(kTag, "OpenKNX KO write failed ko=%u role=%s error=%s",
static_cast<unsigned>(group_object_number), binding->object_role.c_str(),
result.error.c_str());
}
return result;
}
bool GatewayKnxBridge::handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
@@ -1719,6 +1789,10 @@ void GatewayKnxTpIpRouter::setGroupWriteHandler(GroupWriteHandler handler) {
group_write_handler_ = std::move(handler);
}
void GatewayKnxTpIpRouter::setGroupObjectWriteHandler(GroupObjectWriteHandler handler) {
group_object_write_handler_ = std::move(handler);
}
const GatewayKnxConfig& GatewayKnxTpIpRouter::config() const { return config_; }
bool GatewayKnxTpIpRouter::tpUartOnline() const { return tp_uart_online_; }
@@ -1908,8 +1982,24 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
ESP_LOGD(kTag, "secure KNX group write not routed to DALI: %s", result.error.c_str());
}
});
ets_device_->setGroupObjectWriteHandler(
[this](uint16_t group_object_number, const uint8_t* data, size_t len) {
const DaliBridgeResult result = group_object_write_handler_
? group_object_write_handler_(group_object_number,
data, len)
: bridge_.handleGroupObjectWrite(group_object_number,
data, len);
const bool ignored = getObjectBool(result.metadata, "ignored").value_or(false);
if (ignored) {
const auto reason = getObjectString(result.metadata, "reason").value_or("ignored");
ESP_LOGW(kTag, "OpenKNX group object %u accepted by ETS but ignored: %s",
static_cast<unsigned>(group_object_number), reason.c_str());
} else if (!result.ok && !result.error.empty()) {
ESP_LOGW(kTag, "OpenKNX group object %u not routed to DALI: %s",
static_cast<unsigned>(group_object_number), result.error.c_str());
}
});
ets_device_->setBusFrameSender([this](const uint8_t* data, size_t len) {
routeOpenKnxGroupWrite(data, len, "KNX TP frame");
sendTunnelIndication(data, len);
sendRoutingIndication(data, len);
});