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
+9
View File
@@ -1577,6 +1577,15 @@ config GATEWAY_DALI_BAUDRATE
help
Runtime baudrate used when initializing the local DALI bus.
config GATEWAY_CONTROLLER_TASK_STACK_SIZE
int "Gateway controller task stack bytes"
range 6144 24576
default 12288
help
Stack used by the gateway command controller. BLE bridge transport
requests are decoded in this task and may execute JSON-heavy bridge
management actions such as KNX programming-mode changes.
endmenu
menu "Connectivity Startup"
+6
View File
@@ -164,6 +164,10 @@
#define CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE 3072
#endif
#ifndef CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE
#define CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE 12288
#endif
#ifndef CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE
#define CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE 6144
#endif
@@ -978,6 +982,8 @@ extern "C" void app_main(void) {
ESP_ERROR_CHECK(s_cache->start());
gateway::GatewayControllerConfig controller_config;
controller_config.task_stack_size =
static_cast<uint32_t>(CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE);
const bool network_transport_supported = profile.enable_wifi || profile.enable_eth;
controller_config.setup_supported = true;
controller_config.ble_supported = profile.enable_ble;
+107 -12
View File
@@ -600,7 +600,7 @@ CONFIG_PARTITION_TABLE_MD5=y
#
# DALI Settings
#
CONFIG_GATEWAY_CHANNEL_COUNT=2
CONFIG_GATEWAY_CHANNEL_COUNT=1
#
# Gateway Channel 1
@@ -619,17 +619,78 @@ CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
#
# Gateway Channel 2
#
CONFIG_GATEWAY_CHANNEL2_GW_ID=4
# CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED is not set
CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE=y
# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set
# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set
CONFIG_GATEWAY_CHANNEL2_NATIVE_BUS_ID=1
CONFIG_GATEWAY_CHANNEL2_NATIVE_TX_PIN=4
CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN=3
CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE=1200
# end of Gateway Channel 2
#
# Gateway Channel 3
#
# end of Gateway Channel 3
#
# Gateway Channel 4
#
# end of Gateway Channel 4
#
# Gateway Channel 5
#
# end of Gateway Channel 5
#
# Gateway Channel 6
#
# end of Gateway Channel 6
#
# Gateway Channel 7
#
# end of Gateway Channel 7
#
# Gateway Channel 8
#
# end of Gateway Channel 8
#
# Gateway Channel 9
#
# end of Gateway Channel 9
#
# Gateway Channel 10
#
# end of Gateway Channel 10
#
# Gateway Channel 11
#
# end of Gateway Channel 11
#
# Gateway Channel 12
#
# end of Gateway Channel 12
#
# Gateway Channel 13
#
# end of Gateway Channel 13
#
# Gateway Channel 14
#
# end of Gateway Channel 14
#
# Gateway Channel 15
#
# end of Gateway Channel 15
#
# Gateway Channel 16
#
# end of Gateway Channel 16
#
# Gateway Cache
#
@@ -644,10 +705,11 @@ CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# end of Gateway Cache
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE=12288
# end of DALI Settings
#
# Gateway Startup Services
# Connectivity Startup
#
CONFIG_GATEWAY_BLE_SUPPORTED=y
CONFIG_GATEWAY_START_BLE_ENABLED=y
@@ -661,6 +723,7 @@ CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
# end of Connectivity Startup
#
# Gateway Wired Ethernet
@@ -678,7 +741,15 @@ CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=4096
# end of Gateway Wired Ethernet
#
# Bridge Runtime
#
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
# end of Bridge Runtime
#
# Modbus Settings
#
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y
@@ -686,8 +757,14 @@ CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII is not set
CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
# end of Modbus Settings
#
# BACnet Settings
#
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
# end of BACnet Settings
#
# KNX Settings
@@ -737,6 +814,9 @@ CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
# end of KNX Settings
#
# Cloud Settings
#
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX="devices"
@@ -747,14 +827,29 @@ CONFIG_GATEWAY_CLOUD_LTE_UART_PORT=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE=115200
# end of Cloud Settings
#
# Bridge Task Settings
#
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
# end of Bridge Task Settings
#
# USB Setup
#
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
# end of USB Setup
#
# UART0 Control
#
# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
# end of Gateway Startup Services
# end of UART0 Control
#
# Gateway Network Services
@@ -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:
+1 -1
Submodule knx updated: ae3645239f...d19859af47