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
+18
View File
@@ -713,6 +713,24 @@ config GATEWAY_KNX_MAIN_GROUP
Main group used by the built-in KNX to DALI router. Middle groups select
the data type and subgroups select broadcast, short-address, or group targets.
config GATEWAY_KNX_DALI_BUS_ID
int "KNX database target DALI bus id"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
range 0 15
default 0
help
Selects the native DALI HAL bus targeted by the ETS KNX product database.
The current KNX-DALI application supports one ETS-controlled DALI bus.
config GATEWAY_KNX_DEBUG_DUMP_MEMORY
bool "Dump full OpenKNX memory for debugging"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
default n
help
Prints the complete OpenKNX non-volatile memory image when it is restored
or committed. Enable only while debugging ETS download and association
table issues, because the log is large and may include KNX configuration data.
config GATEWAY_KNX_TUNNEL_ENABLED
bool "Enable KNXnet/IP tunneling mode"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
+21
View File
@@ -219,6 +219,10 @@
#define CONFIG_GATEWAY_KNX_MAIN_GROUP 0
#endif
#ifndef CONFIG_GATEWAY_KNX_DALI_BUS_ID
#define CONFIG_GATEWAY_KNX_DALI_BUS_ID 0
#endif
#ifndef CONFIG_GATEWAY_KNX_UDP_PORT
#define CONFIG_GATEWAY_KNX_UDP_PORT 3671
#endif
@@ -606,6 +610,22 @@ bool ValidateChannelBindings() {
if (kKnxBridgeSupported) {
const int knx_uart = CONFIG_GATEWAY_KNX_TP_UART_PORT;
if (kKnxBridgeStartupEnabled) {
const uint8_t knx_dali_bus_id = static_cast<uint8_t>(CONFIG_GATEWAY_KNX_DALI_BUS_ID);
int matches = 0;
for (int i = 0; i < CONFIG_GATEWAY_CHANNEL_COUNT; ++i) {
if (channels[i].enabled && channels[i].native_phy &&
channels[i].native_bus_id == knx_dali_bus_id) {
++matches;
}
}
if (matches != 1) {
ESP_LOGE(kTag,
"KNX DALI bus id %u must match exactly one enabled native DALI channel",
knx_dali_bus_id);
return false;
}
}
if (knx_uart >= 0 && k485ControlEnabled && knx_uart == 0) {
ESP_LOGE(kTag, "KNX TP UART0 conflicts with the UART0 control bridge");
return false;
@@ -864,6 +884,7 @@ extern "C" void app_main(void) {
default_knx.tunnel_enabled = kKnxTunnelEnabled;
default_knx.multicast_enabled = kKnxMulticastEnabled;
default_knx.main_group = static_cast<uint8_t>(CONFIG_GATEWAY_KNX_MAIN_GROUP);
default_knx.dali_bus_id = static_cast<uint8_t>(CONFIG_GATEWAY_KNX_DALI_BUS_ID);
default_knx.udp_port = static_cast<uint16_t>(CONFIG_GATEWAY_KNX_UDP_PORT);
default_knx.multicast_address = CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS;
default_knx.ip_interface_individual_address =
+2
View File
@@ -681,6 +681,8 @@ CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID=0xa401
CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER=0x0001
CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION=0x08
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
CONFIG_GATEWAY_KNX_DALI_BUS_ID=0
# CONFIG_GATEWAY_KNX_DEBUG_DUMP_MEMORY is not set
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
CONFIG_GATEWAY_KNX_UDP_PORT=3671
+5 -2
View File
@@ -676,10 +676,13 @@ CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
# CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED is not set
# CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS is not set
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID=0x00fa
CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER=0xa401
CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID=0x01e5
CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID=0xa401
CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER=0x0001
CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION=0x08
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
CONFIG_GATEWAY_KNX_DALI_BUS_ID=0
# CONFIG_GATEWAY_KNX_DEBUG_DUMP_MEMORY is not set
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
CONFIG_GATEWAY_KNX_UDP_PORT=3671
@@ -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);
});
+1 -1
Submodule knx updated: 23b0cddf24...82f22cf571