feat(gateway): add support for full IP forwarding of KNX TP telegrams and enhance tunnel frame handling

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-19 03:14:03 +08:00
parent b447da5bfc
commit 3af2995b40
7 changed files with 68 additions and 10 deletions
@@ -509,9 +509,6 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
case M_FuncPropStateRead_req:
return true;
case L_data_req: {
if (tpUartOnline()) {
return true;
}
// In commissioning / programming mode ETS may address the device via its
// individual address, the cEMI-client tunnel address (device+1), or the
// unconfigured broadcast address 0xFFFF. Consume only those; let all
+52 -5
View File
@@ -3476,16 +3476,20 @@ 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");
if (!consumed_by_openknx && routed_to_dali) {
const bool sent_to_tp = !consumed_by_openknx && !routed_to_dali &&
transmitOpenKnxTpFrame(cemi, cemi_len);
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(),
tunnel_confirmation.size());
}
}
if (consumed_by_openknx || routed_to_dali) {
if (consumed_by_openknx || routed_to_dali || sent_to_tp) {
return;
}
ESP_LOGD(kTag, "KNX tunnel frame ignored: no OpenKNX/DALI/TP handler matched");
}
void GatewayKnxTpIpRouter::handleDeviceConfigurationRequest(const uint8_t* packet_data, size_t len,
@@ -3933,11 +3937,50 @@ void GatewayKnxTpIpRouter::sendTunnelIndication(const uint8_t* data, size_t len)
if (data == nullptr || len == 0) {
return;
}
for (auto& client : tunnel_clients_) {
if (client.connected) {
sendTunnelIndicationToClient(client, data, len);
std::vector<uint8_t> frame_data(data, data + len);
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
if (!frame.valid()) {
ESP_LOGW(kTag, "not sending invalid OpenKNX tunnel indication len=%u",
static_cast<unsigned>(len));
return;
}
auto is_tunnel_recipient = [](const TunnelClient& client) {
return client.connected && client.connection_type == kKnxConnectionTypeTunnel;
};
auto send_to_client = [this, data, len](TunnelClient& client) {
sendTunnelIndicationToClient(client, data, len);
};
const bool suppress_source_echo =
frame.addressType() == GroupAddress || frame.addressType() == IndividualAddress;
const uint16_t source_address = suppress_source_echo ? frame.sourceAddress() : 0;
if (frame.addressType() == IndividualAddress) {
for (auto& client : tunnel_clients_) {
if (!is_tunnel_recipient(client)) {
continue;
}
if (client.individual_address == source_address) {
continue;
}
if (client.individual_address == frame.destinationAddress()) {
send_to_client(client);
return;
}
}
}
for (auto& client : tunnel_clients_) {
if (!is_tunnel_recipient(client)) {
continue;
}
if (suppress_source_echo && client.individual_address == source_address) {
continue;
}
send_to_client(client);
}
}
void GatewayKnxTpIpRouter::sendTunnelIndicationToClient(TunnelClient& client, const uint8_t* data,
@@ -4152,6 +4195,10 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
sendRoutingIndication(response, response_len);
return;
}
if (response_client != nullptr && response_client->connected) {
sendCemiFrameToClient(*response_client, service, response, response_len);
return;
}
sendTunnelIndication(response, response_len);
});
if (needs_tunnel_confirmation && consumed && !sent_tunnel_confirmation) {