feat(gateway): enhance DALI and KNX handling with broadcast management support and configurable task stack size

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-27 21:06:58 +08:00
parent 6d0b36b60a
commit 078c37a20f
12 changed files with 338 additions and 27 deletions
@@ -24,7 +24,7 @@ class GatewayBridgeService;
class GatewayRuntime;
struct GatewayControllerConfig {
uint32_t task_stack_size{6144};
uint32_t task_stack_size{12288};
UBaseType_t task_priority{5};
int color_temperature_min{2000};
int color_temperature_max{6500};
@@ -69,6 +69,8 @@ class EtsDeviceRuntime {
bool transmitTpFrame(const uint8_t* data, size_t len);
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
bool handleLocalBroadcastManagementFrame(const uint8_t* data, size_t len,
CemiFrameSender sender);
bool handleBusFrame(const uint8_t* data, size_t len);
bool emitGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len,
CemiFrameSender sender);
@@ -44,6 +44,8 @@ class OamRouterRuntime {
Platform* platform();
void setNetworkInterface(esp_netif_t* netif);
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
bool handleLocalBroadcastManagementFrame(const uint8_t* data, size_t len,
CemiFrameSender sender);
void loop();
private:
@@ -42,6 +42,21 @@ bool IsUsableIndividualAddress(uint16_t address) {
return address != 0 && address != kInvalidIndividualAddress;
}
bool IsBroadcastManagementRequest(CemiFrame& frame) {
if (frame.addressType() != GroupAddress || frame.destinationAddress() != 0x0000) {
return false;
}
switch (frame.apdu().type()) {
case IndividualAddressWrite:
case IndividualAddressRead:
case IndividualAddressSerialNumberRead:
case IndividualAddressSerialNumberWrite:
return true;
default:
return false;
}
}
std::string HexBytesString(const uint8_t* data, size_t length) {
if (data == nullptr || length == 0) {
return {};
@@ -336,6 +351,28 @@ bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
return consumed;
}
bool EtsDeviceRuntime::handleLocalBroadcastManagementFrame(const uint8_t* data, size_t len,
CemiFrameSender sender) {
if (data == nullptr || len < 2) {
return false;
}
std::vector<uint8_t> frame_data(data, data + len);
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
if (!frame.valid() || !IsBroadcastManagementRequest(frame)) {
return false;
}
const HopCountType hop_type =
frame.npdu().hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
sender_ = std::move(sender);
ActiveFunctionPropertyRuntimeScope callback_scope(this);
device_.injectDataBroadcastIndication(hop_type, frame.priority(), frame.sourceAddress(),
frame.apdu());
loop();
sender_ = nullptr;
installGroupObjectCallbacks();
return true;
}
bool EtsDeviceRuntime::handleBusFrame(const uint8_t* data, size_t len) {
auto* data_link_layer = device_.getDataLinkLayer();
if (data_link_layer == nullptr || data == nullptr || len < 2) {
@@ -592,6 +629,10 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
return true;
}
if (IsBroadcastManagementRequest(frame)) {
return true;
}
if (frame.addressType() == IndividualAddress) {
if (dest == own_address || dest == client_address ||
(commissioning && dest == kKnxUnconfiguredBroadcastAddress)) {
@@ -823,6 +823,22 @@ std::optional<MessageCode> CemiMessageCode(const uint8_t* data, size_t len) {
return static_cast<MessageCode>(data[0]);
}
bool IsKnxBroadcastManagementRequest(CemiFrame& frame) {
if (frame.messageCode() != L_data_req || frame.addressType() != GroupAddress ||
frame.destinationAddress() != 0x0000) {
return false;
}
switch (frame.apdu().type()) {
case IndividualAddressWrite:
case IndividualAddressRead:
case IndividualAddressSerialNumberRead:
case IndividualAddressSerialNumberWrite:
return true;
default:
return false;
}
}
uint16_t KnxIpServiceForCemi(const uint8_t* data, size_t len, uint16_t fallback_service) {
const auto message_code = CemiMessageCode(data, len);
if (!message_code.has_value()) {
@@ -19,14 +19,98 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
const uint8_t* suppress_routing_echo,
size_t suppress_routing_echo_len) {
bool route_to_oam = response_client != nullptr && response_client->oam_router_persona;
if (!route_to_oam && data != nullptr && len >= 2) {
bool route_to_all_internal_instances = false;
if (data != nullptr && len >= 2) {
std::vector<uint8_t> frame_data(data, data + len);
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
if (frame.valid() && oam_router_ != nullptr &&
MatchesOamRouterLocalIndividualAddress(frame, *oam_router_)) {
route_to_oam = true;
if (frame.valid()) {
route_to_all_internal_instances = IsKnxBroadcastManagementRequest(frame);
if (!route_to_oam && oam_router_ != nullptr &&
MatchesOamRouterLocalIndividualAddress(frame, *oam_router_)) {
route_to_oam = true;
}
}
}
if (route_to_all_internal_instances) {
SemaphoreGuard guard(openknx_lock_);
if (ets_device_ == nullptr && oam_router_ == nullptr) {
return false;
}
std::vector<uint8_t> tunnel_confirmation;
const bool needs_tunnel_confirmation =
response_client != nullptr && response_client->connected &&
response_service == kServiceTunnellingRequest &&
BuildTunnelConfirmationFrame(data, len, &tunnel_confirmation);
bool sent_tunnel_confirmation = false;
const auto send_response =
[this, response_client, response_service, needs_tunnel_confirmation,
&tunnel_confirmation, &sent_tunnel_confirmation, suppress_routing_echo,
suppress_routing_echo_len](const uint8_t* response, size_t response_len) {
if (response == nullptr || response_len == 0) {
return;
}
publishCloudCemiFrame(response, response_len);
const bool routing_context =
response_client == nullptr && response_service == kServiceRoutingIndication;
const auto message_code = CemiMessageCode(response, response_len);
if (routing_context && suppress_routing_echo != nullptr &&
IsLocalRoutingEchoIndication(response, response_len, suppress_routing_echo,
suppress_routing_echo_len)) {
return;
}
if (needs_tunnel_confirmation && !sent_tunnel_confirmation &&
message_code.has_value() && message_code.value() != L_data_con) {
sent_tunnel_confirmation = sendCemiFrameToClient(
*response_client, kServiceTunnellingRequest,
tunnel_confirmation.data(), tunnel_confirmation.size());
}
const uint16_t service = KnxIpServiceForCemi(response, response_len, response_service);
if (service == kServiceDeviceConfigurationRequest) {
if (response_client != nullptr && response_client->connected) {
sendCemiFrameToClient(*response_client, service, response, response_len);
} else if (routing_context) {
sendRoutingIndication(response, response_len);
}
return;
}
if (message_code.has_value() && message_code.value() == L_data_con) {
if (routing_context) {
return;
}
if (response_client != nullptr && response_client->connected) {
sent_tunnel_confirmation =
sendCemiFrameToClient(*response_client, service, response, response_len) ||
sent_tunnel_confirmation;
}
return;
}
if (routing_context) {
sendRoutingIndication(response, response_len);
return;
}
if (response_client != nullptr && response_client->connected) {
sendCemiFrameToClient(*response_client, service, response, response_len);
return;
}
sendTunnelIndication(response, response_len);
};
bool consumed = false;
if (ets_device_ != nullptr) {
consumed = ets_device_->handleLocalBroadcastManagementFrame(data, len, send_response) ||
consumed;
}
if (oam_router_ != nullptr) {
consumed = oam_router_->handleLocalBroadcastManagementFrame(data, len, send_response) ||
consumed;
}
if (needs_tunnel_confirmation && consumed && !sent_tunnel_confirmation) {
sendCemiFrameToClient(*response_client, kServiceTunnellingRequest,
tunnel_confirmation.data(), tunnel_confirmation.size());
}
syncOpenKnxConfigFromDevice();
return consumed;
}
if (route_to_oam) {
return handleOamRouterTunnelFrame(data, len, response_client, response_service,
suppress_routing_echo, suppress_routing_echo_len);
@@ -184,21 +184,31 @@ void GatewayKnxTpIpRouter::handleRoutingIndication(const uint8_t* packet_data, s
const uint8_t* cemi = frame.data();
const size_t cemi_len = frame.dataLength();
bool consumed_by_local_application = false;
const bool addressed_to_oam =
const bool addressed_to_oam =
oam_router_ != nullptr && MatchesOamRouterLocalIndividualAddress(frame, *oam_router_);
const bool addressed_to_reg1 =
const bool addressed_to_reg1 =
ets_device_ != nullptr && MatchesOpenKnxLocalIndividualAddress(frame, *ets_device_);
if (addressed_to_oam || addressed_to_reg1) {
std::vector<uint8_t> local_tunnel_frame;
if (BuildLocalRoutingTunnelFrame(cemi, cemi_len, &local_tunnel_frame)) {
consumed_by_local_application = handleOpenKnxTunnelFrame(
std::vector<uint8_t> local_tunnel_frame;
const bool has_local_tunnel_frame =
BuildLocalRoutingTunnelFrame(cemi, cemi_len, &local_tunnel_frame);
bool broadcast_management = false;
if (has_local_tunnel_frame) {
CemiFrame local_frame(local_tunnel_frame.data(),
static_cast<uint16_t>(local_tunnel_frame.size()));
broadcast_management = local_frame.valid() && IsKnxBroadcastManagementRequest(local_frame);
}
if ((addressed_to_oam || addressed_to_reg1 || broadcast_management) &&
has_local_tunnel_frame) {
consumed_by_local_application = handleOpenKnxTunnelFrame(
local_tunnel_frame.data(), local_tunnel_frame.size(), nullptr,
kServiceRoutingIndication,
frame.messageCode() == L_data_ind ? cemi : nullptr,
frame.messageCode() == L_data_ind ? cemi_len : 0);
}
}
if (consumed_by_local_application) {
if (broadcast_management) {
transmitOpenKnxTpFrame(cemi, cemi_len);
}
return;
}
const bool consumed_by_openknx = handleOpenKnxBusFrame(cemi, cemi_len);
@@ -387,6 +397,7 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* packet_data, s
if (frame.messageCode() == L_data_req && frame.sourceAddress() == 0) {
frame.sourceAddress(client->individual_address);
}
const bool broadcast_management = IsKnxBroadcastManagementRequest(frame);
const uint8_t* cemi = frame.data();
const size_t cemi_len = frame.dataLength();
const std::vector<uint8_t> current_cemi(cemi, cemi + cemi_len);
@@ -441,9 +452,9 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* packet_data, s
const bool consumed_by_openknx = handleOpenKnxTunnelFrame(
cemi, cemi_len, client, kServiceTunnellingRequest);
const bool routed_to_dali = routeOpenKnxGroupWrite(cemi, cemi_len, "KNX tunnel frame");
const bool sent_to_tp = !consumed_by_openknx && !routed_to_dali &&
const bool sent_to_tp = (!consumed_by_openknx || broadcast_management) && !routed_to_dali &&
transmitOpenKnxTpFrame(cemi, cemi_len);
if ((!consumed_by_openknx && routed_to_dali) || sent_to_tp) {
if (!consumed_by_openknx && (routed_to_dali || sent_to_tp)) {
std::vector<uint8_t> tunnel_confirmation;
if (BuildTunnelConfirmationFrame(cemi, cemi_len, &tunnel_confirmation)) {
sendCemiFrameToClient(*client, kServiceTunnellingRequest, tunnel_confirmation.data(),
@@ -22,6 +22,21 @@ bool IsUsableIndividualAddress(uint16_t address) {
return address != 0 && address != kInvalidIndividualAddress;
}
bool IsBroadcastManagementRequest(CemiFrame& frame) {
if (frame.addressType() != GroupAddress || frame.destinationAddress() != 0x0000) {
return false;
}
switch (frame.apdu().type()) {
case IndividualAddressWrite:
case IndividualAddressRead:
case IndividualAddressSerialNumberRead:
case IndividualAddressSerialNumberWrite:
return true;
default:
return false;
}
}
uint32_t OamBauNumberFromBaseMac() {
uint8_t mac[6]{};
if (esp_efuse_mac_get_default(mac) != ESP_OK && esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) {
@@ -218,6 +233,33 @@ bool OamRouterRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
#endif
}
bool OamRouterRuntime::handleLocalBroadcastManagementFrame(const uint8_t* data, size_t len,
CemiFrameSender sender) {
#if defined(ENABLE_BAU091A_PERSONA)
if (data == nullptr || len < 2) {
return false;
}
std::vector<uint8_t> frame_data(data, data + len);
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
if (!frame.valid() || !IsBroadcastManagementRequest(frame)) {
return false;
}
const HopCountType hop_type =
frame.npdu().hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
sender_ = std::move(sender);
device_.injectDataBroadcastIndication(hop_type, frame.priority(), frame.sourceAddress(),
frame.apdu());
loop();
sender_ = nullptr;
return true;
#else
(void)data;
(void)len;
(void)sender;
return false;
#endif
}
void OamRouterRuntime::loop() {
#if defined(ENABLE_BAU091A_PERSONA)
device_.loop();
@@ -268,6 +310,9 @@ bool OamRouterRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
return dest == individualAddress() || dest == tunnelClientAddress() ||
(commissioning && dest == kKnxUnconfiguredBroadcastAddress);
}
if (IsBroadcastManagementRequest(frame)) {
return true;
}
return false;
}
default: