feat: Enhance OAM router functionality and improve KNX device handling
- Added support for OAM router configuration in app_main, allowing the IP interface individual address to be set based on the OAM router's individual address. - Updated GatewayBridgeService to validate IP interface addresses only when OAM router is disabled, ensuring proper address management. - Introduced KnxResponseDeduplicator to prevent duplicate responses in KNX communication. - Enhanced ETS device runtime to handle bus frames and set up frame receivers for OAM router. - Improved GatewayKnxTpIpRouter to manage OAM router interactions, including handling tunnel frames and bus frames. - Updated CMakeLists to include new knx_device_broker source file. - Refined logging messages to provide clearer context regarding the IP interface being used. - Added methods to retrieve IP interface names and friendly names based on the OAM router configuration. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -3427,8 +3427,9 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.ip_interface_individual_address == 0 ||
|
||||
config.ip_interface_individual_address == 0xffff) {
|
||||
if (!config.oam_router.enabled &&
|
||||
(config.ip_interface_individual_address == 0 ||
|
||||
config.ip_interface_individual_address == 0xffff)) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "KNX IP interface individual address must be a configured address";
|
||||
}
|
||||
@@ -3440,7 +3441,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.ip_interface_individual_address == config.individual_address) {
|
||||
if (!config.oam_router.enabled &&
|
||||
config.ip_interface_individual_address == config.individual_address) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "KNX IP interface and KNX-DALI gateway addresses must differ";
|
||||
}
|
||||
@@ -3463,11 +3465,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.oam_router.individual_address == config.individual_address ||
|
||||
config.oam_router.individual_address == config.ip_interface_individual_address ||
|
||||
config.oam_router.tunnel_address_base == config.individual_address ||
|
||||
config.oam_router.tunnel_address_base == config.ip_interface_individual_address) {
|
||||
config.oam_router.tunnel_address_base == config.oam_router.individual_address) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "OAM KNX/IP router addresses must differ from the shared IP interface and KNX-DALI gateway addresses";
|
||||
*error_message = "OAM KNX/IP router addresses must differ from KNX-DALI gateway and OAM tunnel addresses";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
@@ -4364,6 +4365,19 @@ esp_err_t GatewayBridgeService::startKnxEndpoint(ChannelRuntime* requested_runti
|
||||
requested_runtime->channel.gateway_id, owner->channel.gateway_id);
|
||||
}
|
||||
|
||||
for (const auto& runtime : runtimes_) {
|
||||
if (runtime.get() == owner) {
|
||||
continue;
|
||||
}
|
||||
if (runtime->knx_started ||
|
||||
(runtime->knx_router != nullptr && runtime->knx_router->started())) {
|
||||
ESP_LOGI(kTag,
|
||||
"gateway=%u stopping non-owner KNXnet/IP router; shared endpoint owner is gateway=%u",
|
||||
runtime->channel.gateway_id, owner->channel.gateway_id);
|
||||
runtime->stopKnx();
|
||||
}
|
||||
}
|
||||
|
||||
if (used_uarts != nullptr) {
|
||||
LockGuard guard(owner->lock);
|
||||
const auto owner_config = owner->activeKnxConfigLocked();
|
||||
@@ -4404,23 +4418,33 @@ esp_err_t GatewayBridgeService::stopKnxEndpoint(ChannelRuntime* requested_runtim
|
||||
|
||||
DaliBridgeResult GatewayBridgeService::routeKnxGroupWrite(uint16_t group_address,
|
||||
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 " +
|
||||
GatewayKnxGroupAddressString(group_address);
|
||||
return result;
|
||||
DaliBridgeResult aggregate;
|
||||
aggregate.error = "No DALI channel maps KNX group " +
|
||||
GatewayKnxGroupAddressString(group_address);
|
||||
size_t matched_count = 0;
|
||||
for (const auto& candidate : runtimes_) {
|
||||
LockGuard guard(candidate->lock);
|
||||
const auto config = candidate->activeKnxConfigLocked();
|
||||
if (candidate->knx == nullptr || !config.has_value() || !config->dali_router_enabled ||
|
||||
!candidate->channel.native_bus_id.has_value() ||
|
||||
candidate->channel.native_bus_id.value() != config->dali_bus_id ||
|
||||
!candidate->knx->matchesGroupAddress(group_address)) {
|
||||
continue;
|
||||
}
|
||||
++matched_count;
|
||||
DaliBridgeResult child = candidate->knx->handleGroupWrite(group_address, data, len);
|
||||
child.metadata["gatewayId"] = static_cast<int>(candidate->channel.gateway_id);
|
||||
child.metadata["channelIndex"] = static_cast<int>(candidate->channel.channel_index);
|
||||
aggregate.results.emplace_back(child.toJson());
|
||||
if (child.ok) {
|
||||
aggregate.ok = true;
|
||||
aggregate.error.clear();
|
||||
} else if (!child.error.empty() && aggregate.error.empty()) {
|
||||
aggregate.error = child.error;
|
||||
}
|
||||
}
|
||||
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx == nullptr || !runtime->knx->matchesGroupAddress(group_address)) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "Selected DALI bus does not map KNX group " +
|
||||
GatewayKnxGroupAddressString(group_address);
|
||||
return result;
|
||||
}
|
||||
return runtime->knx->handleGroupWrite(group_address, data, len);
|
||||
aggregate.metadata["matchedChannels"] = static_cast<int>(matched_count);
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayBridgeService::routeKnxGroupObjectWrite(uint16_t group_object_number,
|
||||
|
||||
@@ -7,6 +7,7 @@ idf_component_register(
|
||||
"src/gateway_knx_router_packets.cpp"
|
||||
"src/gateway_knx_router_services.cpp"
|
||||
"src/gateway_knx_secure_transport.cpp"
|
||||
"src/knx_device_broker.cpp"
|
||||
"src/oam_router_runtime.cpp"
|
||||
"src/ets_device_runtime.cpp"
|
||||
"src/ets_memory_loader.cpp"
|
||||
|
||||
@@ -22,6 +22,7 @@ class TpuartUartInterface;
|
||||
class EtsDeviceRuntime {
|
||||
public:
|
||||
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
|
||||
using CemiFrameReceiver = std::function<bool(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,
|
||||
@@ -62,6 +63,7 @@ class EtsDeviceRuntime {
|
||||
void setGroupWriteHandler(GroupWriteHandler handler);
|
||||
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
|
||||
void setBusFrameSender(CemiFrameSender sender);
|
||||
void setTpFrameReceiver(CemiFrameReceiver receiver);
|
||||
void setNetworkInterface(esp_netif_t* netif);
|
||||
bool hasTpUart() const;
|
||||
bool enableTpUart(bool enabled = true);
|
||||
@@ -121,6 +123,7 @@ class EtsDeviceRuntime {
|
||||
Bau07B0 device_;
|
||||
CemiFrameSender sender_;
|
||||
CemiFrameSender bus_frame_sender_;
|
||||
CemiFrameReceiver tp_frame_receiver_;
|
||||
GroupWriteHandler group_write_handler_;
|
||||
GroupObjectWriteHandler group_object_write_handler_;
|
||||
FunctionPropertyHandler command_handler_;
|
||||
|
||||
@@ -340,8 +340,10 @@ class GatewayKnxTpIpRouter {
|
||||
::sockaddr_in data_remote{};
|
||||
std::vector<uint8_t> last_received_cemi;
|
||||
std::vector<uint8_t> last_tunnel_confirmation_packet;
|
||||
std::vector<uint8_t> last_sent_cemi;
|
||||
uint16_t secure_session_id{0};
|
||||
bool oam_router_persona{false};
|
||||
TickType_t last_sent_cemi_tick{0};
|
||||
};
|
||||
|
||||
struct SecureSession {
|
||||
@@ -468,12 +470,16 @@ class GatewayKnxTpIpRouter {
|
||||
size_t suppress_routing_echo_len = 0);
|
||||
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
|
||||
bool transmitOpenKnxTpFrame(const uint8_t* data, size_t len);
|
||||
bool transmitOpenKnxTpFrameLocked(const uint8_t* data, size_t len);
|
||||
bool handleOpenKnxTpIngressFrame(const uint8_t* data, size_t len);
|
||||
void publishCloudCemiFrame(const uint8_t* data, size_t len);
|
||||
void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
|
||||
bool routeOpenKnxGroupWrite(const uint8_t* data, size_t len, const char* context);
|
||||
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
|
||||
bool shouldRouteDaliApplicationFrames() const;
|
||||
uint8_t advertisedMedium() const;
|
||||
const char* ipInterfaceName() const;
|
||||
const char* ipInterfaceFriendlyName() const;
|
||||
void syncOpenKnxConfigFromDevice();
|
||||
uint16_t effectiveIpInterfaceIndividualAddress() const;
|
||||
uint16_t effectiveKnxDeviceIndividualAddress() const;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
enum class KnxPortKind : uint8_t {
|
||||
kIpTunnel,
|
||||
kIpRouting,
|
||||
kTpUart,
|
||||
kCloud,
|
||||
kRfReserved,
|
||||
};
|
||||
|
||||
struct KnxIngressContext {
|
||||
KnxPortKind port{KnxPortKind::kIpTunnel};
|
||||
bool oam_persona_hint{false};
|
||||
bool broadcast_management{false};
|
||||
};
|
||||
|
||||
class KnxResponseDeduplicator {
|
||||
public:
|
||||
KnxResponseDeduplicator() = default;
|
||||
KnxResponseDeduplicator(const uint8_t* original, size_t len);
|
||||
|
||||
bool remember(const uint8_t* data, size_t len);
|
||||
bool remember(const std::vector<uint8_t>& data);
|
||||
size_t suppressedCount() const;
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> original_;
|
||||
std::vector<std::vector<uint8_t>> sent_;
|
||||
size_t suppressed_count_{0};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -43,9 +43,11 @@ class OamRouterRuntime {
|
||||
DeviceObject* deviceObject();
|
||||
Platform* platform();
|
||||
void setNetworkInterface(esp_netif_t* netif);
|
||||
void setBusFrameSender(CemiFrameSender sender);
|
||||
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, CemiFrameSender sender);
|
||||
void loop();
|
||||
|
||||
private:
|
||||
@@ -53,9 +55,11 @@ class OamRouterRuntime {
|
||||
static void EmitTunnelFrame(CemiFrame& frame, void* context);
|
||||
static uint16_t DefaultTunnelClientAddress(uint16_t individual_address);
|
||||
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
|
||||
bool shouldConsumeBusFrame(CemiFrame& frame) const;
|
||||
|
||||
std::string nvs_namespace_;
|
||||
CemiFrameSender sender_;
|
||||
CemiFrameSender bus_frame_sender_;
|
||||
#if defined(ENABLE_BAU091A_PERSONA)
|
||||
EspIdfPlatform platform_;
|
||||
Bau091A device_;
|
||||
|
||||
@@ -163,6 +163,9 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
||||
}
|
||||
|
||||
EtsDeviceRuntime::~EtsDeviceRuntime() {
|
||||
if (auto* data_link_layer = device_.getDataLinkLayer()) {
|
||||
data_link_layer->receiveFrameHandler({});
|
||||
}
|
||||
if (tp_uart_interface_ != nullptr) {
|
||||
device_.enabled(false);
|
||||
}
|
||||
@@ -289,6 +292,15 @@ void EtsDeviceRuntime::setBusFrameSender(CemiFrameSender sender) {
|
||||
bus_frame_sender_ = std::move(sender);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setTpFrameReceiver(CemiFrameReceiver receiver) {
|
||||
tp_frame_receiver_ = std::move(receiver);
|
||||
if (auto* data_link_layer = device_.getDataLinkLayer()) {
|
||||
data_link_layer->receiveFrameHandler([this](CemiFrame& frame) {
|
||||
return tp_frame_receiver_ ? tp_frame_receiver_(frame.data(), frame.dataLength()) : false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setNetworkInterface(esp_netif_t* netif) {
|
||||
platform_.networkInterface(netif);
|
||||
}
|
||||
@@ -647,12 +659,27 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::shouldConsumeBusFrame(CemiFrame& frame) const {
|
||||
if (frame.messageCode() != L_data_ind && frame.messageCode() != L_data_req) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_DATASECURE
|
||||
return frame.messageCode() == L_data_ind && frame.addressType() == GroupAddress &&
|
||||
frame.apdu().type() == SecureService;
|
||||
#else
|
||||
return false;
|
||||
if (frame.addressType() == GroupAddress && frame.apdu().type() == SecureService) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (IsBroadcastManagementRequest(frame)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (frame.addressType() != IndividualAddress) {
|
||||
return false;
|
||||
}
|
||||
const uint16_t dest = frame.destinationAddress();
|
||||
const bool commissioning = !const_cast<Bau07B0&>(device_).configured() || programmingMode();
|
||||
return dest == individualAddress() || dest == tunnelClientAddress() ||
|
||||
(commissioning && dest == kKnxUnconfiguredBroadcastAddress);
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
|
||||
@@ -824,7 +824,8 @@ std::optional<MessageCode> CemiMessageCode(const uint8_t* data, size_t len) {
|
||||
}
|
||||
|
||||
bool IsKnxBroadcastManagementRequest(CemiFrame& frame) {
|
||||
if (frame.messageCode() != L_data_req || frame.addressType() != GroupAddress ||
|
||||
if ((frame.messageCode() != L_data_req && frame.messageCode() != L_data_ind) ||
|
||||
frame.addressType() != GroupAddress ||
|
||||
frame.destinationAddress() != 0x0000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -154,9 +154,9 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task
|
||||
&log_tp_uart_rx_pin);
|
||||
}
|
||||
ESP_LOGI(kTag,
|
||||
"starting KNXnet/IP router namespace=%s udp=%u tunnel=%d multicast=%d group=%s "
|
||||
"starting KNXnet/IP router ipInterface=%s runtimeNamespace=%s udp=%u tunnel=%d multicast=%d group=%s "
|
||||
"tpUart=%d tx=%s rx=%s nineBit=%d commissioningOnly=%d",
|
||||
openknx_namespace_.c_str(), static_cast<unsigned>(config_.udp_port),
|
||||
ipInterfaceName(), openknx_namespace_.c_str(), static_cast<unsigned>(config_.udp_port),
|
||||
config_.tunnel_enabled, config_.multicast_enabled,
|
||||
config_.multicast_address.c_str(), config_.tp_uart.uart_port,
|
||||
UartPinDescription(config_.tp_uart.tx_pin, log_tp_uart_tx_pin).c_str(),
|
||||
@@ -351,6 +351,16 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
|
||||
sendTunnelIndication(data, len);
|
||||
sendRoutingIndication(data, len);
|
||||
});
|
||||
ets_device_->setTpFrameReceiver([this](const uint8_t* data, size_t len) {
|
||||
return handleOpenKnxTpIngressFrame(data, len);
|
||||
});
|
||||
if (oam_router_ != nullptr) {
|
||||
oam_router_->setBusFrameSender([this](const uint8_t* data, size_t len) {
|
||||
publishCloudCemiFrame(data, len);
|
||||
sendTunnelIndication(data, len);
|
||||
sendRoutingIndication(data, len);
|
||||
});
|
||||
}
|
||||
syncOpenKnxConfigFromDevice();
|
||||
}
|
||||
if (!configureTpUart()) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "gateway_knx_private.hpp"
|
||||
|
||||
#include "knx_device_broker.h"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
void GatewayKnxTpIpRouter::selectOpenKnxNetworkInterface(const sockaddr_in& remote) {
|
||||
@@ -18,7 +20,13 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
||||
uint16_t response_service,
|
||||
const uint8_t* suppress_routing_echo,
|
||||
size_t suppress_routing_echo_len) {
|
||||
bool route_to_oam = response_client != nullptr && response_client->oam_router_persona;
|
||||
const KnxIngressContext ingress_context{
|
||||
response_service == kServiceRoutingIndication ? KnxPortKind::kIpRouting
|
||||
: KnxPortKind::kIpTunnel,
|
||||
response_client != nullptr && response_client->oam_router_persona,
|
||||
false,
|
||||
};
|
||||
bool route_to_oam = ingress_context.oam_persona_hint;
|
||||
bool route_to_all_internal_instances = false;
|
||||
if (data != nullptr && len >= 2) {
|
||||
std::vector<uint8_t> frame_data(data, data + len);
|
||||
@@ -36,25 +44,35 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
||||
if (ets_device_ == nullptr && oam_router_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (response_client != nullptr && oam_router_ != nullptr &&
|
||||
(oam_router_->programmingMode() || !oam_router_->configured())) {
|
||||
response_client->oam_router_persona = true;
|
||||
}
|
||||
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;
|
||||
KnxResponseDeduplicator response_broker;
|
||||
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) {
|
||||
&tunnel_confirmation, &sent_tunnel_confirmation, &response_broker,
|
||||
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);
|
||||
std::vector<uint8_t> response_data(response, response + response_len);
|
||||
if (!response_broker.remember(response_data)) {
|
||||
return;
|
||||
}
|
||||
publishCloudCemiFrame(response_data.data(), response_data.size());
|
||||
const bool routing_context =
|
||||
response_client == nullptr && response_service == kServiceRoutingIndication;
|
||||
const auto message_code = CemiMessageCode(response, response_len);
|
||||
const auto message_code = CemiMessageCode(response_data.data(), response_data.size());
|
||||
if (routing_context && suppress_routing_echo != nullptr &&
|
||||
IsLocalRoutingEchoIndication(response, response_len, suppress_routing_echo,
|
||||
IsLocalRoutingEchoIndication(response_data.data(), response_data.size(), suppress_routing_echo,
|
||||
suppress_routing_echo_len)) {
|
||||
return;
|
||||
}
|
||||
@@ -65,12 +83,14 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
||||
tunnel_confirmation.data(), tunnel_confirmation.size());
|
||||
}
|
||||
|
||||
const uint16_t service = KnxIpServiceForCemi(response, response_len, response_service);
|
||||
const uint16_t service = KnxIpServiceForCemi(response_data.data(), response_data.size(),
|
||||
response_service);
|
||||
if (service == kServiceDeviceConfigurationRequest) {
|
||||
if (response_client != nullptr && response_client->connected) {
|
||||
sendCemiFrameToClient(*response_client, service, response, response_len);
|
||||
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
||||
response_data.size());
|
||||
} else if (routing_context) {
|
||||
sendRoutingIndication(response, response_len);
|
||||
sendRoutingIndication(response_data.data(), response_data.size());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -80,20 +100,22 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
||||
}
|
||||
if (response_client != nullptr && response_client->connected) {
|
||||
sent_tunnel_confirmation =
|
||||
sendCemiFrameToClient(*response_client, service, response, response_len) ||
|
||||
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
||||
response_data.size()) ||
|
||||
sent_tunnel_confirmation;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (routing_context) {
|
||||
sendRoutingIndication(response, response_len);
|
||||
sendRoutingIndication(response_data.data(), response_data.size());
|
||||
return;
|
||||
}
|
||||
if (response_client != nullptr && response_client->connected) {
|
||||
sendCemiFrameToClient(*response_client, service, response, response_len);
|
||||
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
||||
response_data.size());
|
||||
return;
|
||||
}
|
||||
sendTunnelIndication(response, response_len);
|
||||
sendTunnelIndication(response_data.data(), response_data.size());
|
||||
};
|
||||
bool consumed = false;
|
||||
if (ets_device_ != nullptr) {
|
||||
@@ -112,6 +134,9 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
||||
return consumed;
|
||||
}
|
||||
if (route_to_oam) {
|
||||
if (response_client != nullptr) {
|
||||
response_client->oam_router_persona = true;
|
||||
}
|
||||
return handleOamRouterTunnelFrame(data, len, response_client, response_service,
|
||||
suppress_routing_echo, suppress_routing_echo_len);
|
||||
}
|
||||
@@ -204,21 +229,26 @@ bool GatewayKnxTpIpRouter::handleOamRouterTunnelFrame(const uint8_t* data, size_
|
||||
response_service == kServiceTunnellingRequest &&
|
||||
BuildTunnelConfirmationFrame(data, len, &tunnel_confirmation);
|
||||
bool sent_tunnel_confirmation = false;
|
||||
KnxResponseDeduplicator response_broker;
|
||||
const bool consumed = oam_router_->handleTunnelFrame(
|
||||
data, len,
|
||||
[this, response_client, response_service, needs_tunnel_confirmation,
|
||||
&tunnel_confirmation, &sent_tunnel_confirmation,
|
||||
&tunnel_confirmation, &sent_tunnel_confirmation, &response_broker,
|
||||
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);
|
||||
std::vector<uint8_t> response_data(response, response + response_len);
|
||||
if (!response_broker.remember(response_data)) {
|
||||
return;
|
||||
}
|
||||
publishCloudCemiFrame(response_data.data(), response_data.size());
|
||||
const bool routing_context =
|
||||
response_client == nullptr && response_service == kServiceRoutingIndication;
|
||||
const auto message_code = CemiMessageCode(response, response_len);
|
||||
const auto message_code = CemiMessageCode(response_data.data(), response_data.size());
|
||||
if (routing_context && suppress_routing_echo != nullptr &&
|
||||
IsLocalRoutingEchoIndication(response, response_len, suppress_routing_echo,
|
||||
IsLocalRoutingEchoIndication(response_data.data(), response_data.size(), suppress_routing_echo,
|
||||
suppress_routing_echo_len)) {
|
||||
return;
|
||||
}
|
||||
@@ -229,12 +259,14 @@ bool GatewayKnxTpIpRouter::handleOamRouterTunnelFrame(const uint8_t* data, size_
|
||||
tunnel_confirmation.data(), tunnel_confirmation.size());
|
||||
}
|
||||
|
||||
const uint16_t service = KnxIpServiceForCemi(response, response_len, response_service);
|
||||
const uint16_t service = KnxIpServiceForCemi(response_data.data(), response_data.size(),
|
||||
response_service);
|
||||
if (service == kServiceDeviceConfigurationRequest) {
|
||||
if (response_client != nullptr && response_client->connected) {
|
||||
sendCemiFrameToClient(*response_client, service, response, response_len);
|
||||
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
||||
response_data.size());
|
||||
} else if (routing_context) {
|
||||
sendRoutingIndication(response, response_len);
|
||||
sendRoutingIndication(response_data.data(), response_data.size());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -244,20 +276,22 @@ bool GatewayKnxTpIpRouter::handleOamRouterTunnelFrame(const uint8_t* data, size_
|
||||
}
|
||||
if (response_client != nullptr && response_client->connected) {
|
||||
sent_tunnel_confirmation =
|
||||
sendCemiFrameToClient(*response_client, service, response, response_len) ||
|
||||
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
||||
response_data.size()) ||
|
||||
sent_tunnel_confirmation;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (routing_context) {
|
||||
sendRoutingIndication(response, response_len);
|
||||
sendRoutingIndication(response_data.data(), response_data.size());
|
||||
return;
|
||||
}
|
||||
if (response_client != nullptr && response_client->connected) {
|
||||
sendCemiFrameToClient(*response_client, service, response, response_len);
|
||||
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
||||
response_data.size());
|
||||
return;
|
||||
}
|
||||
sendTunnelIndication(response, response_len);
|
||||
sendTunnelIndication(response_data.data(), response_data.size());
|
||||
});
|
||||
if (needs_tunnel_confirmation && consumed && !sent_tunnel_confirmation) {
|
||||
sendCemiFrameToClient(*response_client, kServiceTunnellingRequest,
|
||||
@@ -268,6 +302,10 @@ bool GatewayKnxTpIpRouter::handleOamRouterTunnelFrame(const uint8_t* data, size_
|
||||
|
||||
bool GatewayKnxTpIpRouter::transmitOpenKnxTpFrame(const uint8_t* data, size_t len) {
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
return transmitOpenKnxTpFrameLocked(data, len);
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::transmitOpenKnxTpFrameLocked(const uint8_t* data, size_t len) {
|
||||
if (ets_device_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -276,6 +314,56 @@ bool GatewayKnxTpIpRouter::transmitOpenKnxTpFrame(const uint8_t* data, size_t le
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::handleOpenKnxTpIngressFrame(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len < 2 || (ets_device_ == nullptr && oam_router_ == nullptr)) {
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool broadcast_management = IsKnxBroadcastManagementRequest(frame);
|
||||
const bool addressed_to_oam =
|
||||
oam_router_ != nullptr && MatchesOamRouterLocalIndividualAddress(frame, *oam_router_);
|
||||
if (!broadcast_management && !addressed_to_oam) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const KnxIngressContext ingress_context{KnxPortKind::kTpUart, addressed_to_oam,
|
||||
broadcast_management};
|
||||
KnxResponseDeduplicator response_broker(frame_data.data(), frame_data.size());
|
||||
const auto send_response = [this, &response_broker](const uint8_t* response,
|
||||
size_t response_len) {
|
||||
if (response == nullptr || response_len == 0) {
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> response_data(response, response + response_len);
|
||||
if (!response_broker.remember(response_data)) {
|
||||
return;
|
||||
}
|
||||
publishCloudCemiFrame(response_data.data(), response_data.size());
|
||||
transmitOpenKnxTpFrameLocked(response_data.data(), response_data.size());
|
||||
sendTunnelIndication(response_data.data(), response_data.size());
|
||||
sendRoutingIndication(response_data.data(), response_data.size());
|
||||
};
|
||||
|
||||
bool consumed = false;
|
||||
if (ingress_context.broadcast_management && ets_device_ != nullptr) {
|
||||
consumed = ets_device_->handleBusFrame(data, len) || consumed;
|
||||
}
|
||||
if ((ingress_context.broadcast_management || ingress_context.oam_persona_hint) &&
|
||||
oam_router_ != nullptr) {
|
||||
consumed = oam_router_->handleBusFrame(data, len, send_response) || consumed;
|
||||
}
|
||||
if (consumed) {
|
||||
publishCloudCemiFrame(data, len);
|
||||
syncOpenKnxConfigFromDevice();
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::handleOpenKnxBusFrame(const uint8_t* data, size_t len) {
|
||||
bool consumed = false;
|
||||
{
|
||||
@@ -441,6 +529,14 @@ uint8_t GatewayKnxTpIpRouter::advertisedMedium() const {
|
||||
return (config_.tunnel_enabled || tp_uart_online_) ? kKnxMediumTp1 : kKnxMediumIp;
|
||||
}
|
||||
|
||||
const char* GatewayKnxTpIpRouter::ipInterfaceName() const {
|
||||
return config_.oam_router.enabled ? "oam_ip_router" : "knx_ip_interface";
|
||||
}
|
||||
|
||||
const char* GatewayKnxTpIpRouter::ipInterfaceFriendlyName() const {
|
||||
return config_.oam_router.enabled ? "OAM IP Router" : "DaliMaster KNX IP";
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
|
||||
if (ets_device_ == nullptr) {
|
||||
return;
|
||||
@@ -454,6 +550,15 @@ void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
|
||||
updated.individual_address = snapshot.individual_address;
|
||||
changed = true;
|
||||
}
|
||||
if (oam_router_ != nullptr) {
|
||||
const auto oam_snapshot = oam_router_->snapshot();
|
||||
if (oam_snapshot.individual_address != 0 && oam_snapshot.individual_address != 0xffff &&
|
||||
oam_snapshot.individual_address != updated.oam_router.individual_address) {
|
||||
updated.oam_router.individual_address = oam_snapshot.individual_address;
|
||||
updated.ip_interface_individual_address = oam_snapshot.individual_address;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (snapshot.configured || !snapshot.associations.empty()) {
|
||||
std::vector<GatewayKnxEtsAssociation> associations;
|
||||
associations.reserve(snapshot.associations.size());
|
||||
@@ -480,6 +585,18 @@ void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
|
||||
}
|
||||
|
||||
uint16_t GatewayKnxTpIpRouter::effectiveIpInterfaceIndividualAddress() const {
|
||||
if (config_.oam_router.enabled) {
|
||||
if (oam_router_ != nullptr) {
|
||||
const uint16_t address = oam_router_->individualAddress();
|
||||
if (address != 0 && address != 0xffff) {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
if (config_.oam_router.individual_address != 0 &&
|
||||
config_.oam_router.individual_address != 0xffff) {
|
||||
return config_.oam_router.individual_address;
|
||||
}
|
||||
}
|
||||
if (config_.ip_interface_individual_address != 0 &&
|
||||
config_.ip_interface_individual_address != 0xffff) {
|
||||
return config_.ip_interface_individual_address;
|
||||
@@ -498,6 +615,10 @@ uint16_t GatewayKnxTpIpRouter::effectiveKnxDeviceIndividualAddress() const {
|
||||
}
|
||||
|
||||
uint16_t GatewayKnxTpIpRouter::effectiveTunnelAddress() const {
|
||||
if (config_.oam_router.enabled && config_.oam_router.tunnel_address_base != 0 &&
|
||||
config_.oam_router.tunnel_address_base != 0xffff) {
|
||||
return config_.oam_router.tunnel_address_base;
|
||||
}
|
||||
const uint16_t interface_address = effectiveIpInterfaceIndividualAddress();
|
||||
uint16_t device = static_cast<uint16_t>((interface_address & 0x00ff) + 1);
|
||||
if (device == 0 || device > 0xff) {
|
||||
|
||||
@@ -141,16 +141,28 @@ std::vector<uint8_t> GatewayKnxTpIpRouter::buildDeviceInfoDib(
|
||||
|
||||
uint8_t mac[6]{};
|
||||
if (ReadBaseMac(mac)) {
|
||||
dib[8] = static_cast<uint8_t>((knx_internal::kReg1DaliManufacturerId >> 8) & 0xff);
|
||||
dib[9] = static_cast<uint8_t>(knx_internal::kReg1DaliManufacturerId & 0xff);
|
||||
std::memcpy(dib.data() + 10, mac + 2, 4);
|
||||
const bool oam_ip_owner = config_.oam_router.enabled;
|
||||
const uint16_t manufacturer = oam_ip_owner ? knx_internal::kOamRouterManufacturerId
|
||||
: knx_internal::kReg1DaliManufacturerId;
|
||||
uint32_t serial_suffix = (static_cast<uint32_t>(mac[2]) << 24) |
|
||||
(static_cast<uint32_t>(mac[3]) << 16) |
|
||||
(static_cast<uint32_t>(mac[4]) << 8) |
|
||||
static_cast<uint32_t>(mac[5]);
|
||||
if (oam_ip_owner) {
|
||||
serial_suffix += knx_internal::kOamRouterSerialMacIncrement;
|
||||
}
|
||||
dib[8] = static_cast<uint8_t>((manufacturer >> 8) & 0xff);
|
||||
dib[9] = static_cast<uint8_t>(manufacturer & 0xff);
|
||||
dib[10] = static_cast<uint8_t>((serial_suffix >> 24) & 0xff);
|
||||
dib[11] = static_cast<uint8_t>((serial_suffix >> 16) & 0xff);
|
||||
dib[12] = static_cast<uint8_t>((serial_suffix >> 8) & 0xff);
|
||||
dib[13] = static_cast<uint8_t>(serial_suffix & 0xff);
|
||||
std::memcpy(dib.data() + 18, mac, 6);
|
||||
}
|
||||
|
||||
WriteIp(dib.data() + 14, inet_addr(config_.multicast_address.c_str()));
|
||||
char friendly[31]{};
|
||||
std::snprintf(friendly, sizeof(friendly), "DALI GW MG%u %s",
|
||||
static_cast<unsigned>(config_.main_group), openknx_namespace_.c_str());
|
||||
std::snprintf(friendly, sizeof(friendly), "%s", ipInterfaceFriendlyName());
|
||||
std::memcpy(dib.data() + 24, friendly, std::min<size_t>(30, std::strlen(friendly)));
|
||||
(void)remote;
|
||||
return dib;
|
||||
@@ -336,6 +348,14 @@ bool GatewayKnxTpIpRouter::sendCemiFrameToClient(TunnelClient& client, uint16_t
|
||||
request.connectionHeader().length(LEN_CH);
|
||||
request.connectionHeader().channelId(client.channel_id);
|
||||
const auto message_code = CemiMessageCode(data, len);
|
||||
if (service == kServiceTunnellingRequest && message_code.has_value() &&
|
||||
message_code.value() != L_data_con && client.last_sent_cemi == frame_data &&
|
||||
xTaskGetTickCount() - client.last_sent_cemi_tick < pdMS_TO_TICKS(500)) {
|
||||
ESP_LOGD(kTag, "suppress duplicate KNXnet/IP cEMI channel=%u cemi=0x%02x len=%u to %s",
|
||||
static_cast<unsigned>(client.channel_id), static_cast<unsigned>(data[0]),
|
||||
static_cast<unsigned>(len), EndpointString(client.data_remote).c_str());
|
||||
return true;
|
||||
}
|
||||
const uint8_t send_sequence = client.send_sequence++;
|
||||
request.connectionHeader().sequenceCounter(send_sequence);
|
||||
request.connectionHeader().status(kKnxNoError);
|
||||
@@ -357,6 +377,11 @@ bool GatewayKnxTpIpRouter::sendCemiFrameToClient(TunnelClient& client, uint16_t
|
||||
client.last_tunnel_confirmation_packet = packet;
|
||||
}
|
||||
}
|
||||
if (service == kServiceTunnellingRequest && message_code.has_value() &&
|
||||
message_code.value() != L_data_con) {
|
||||
client.last_sent_cemi = std::move(frame_data);
|
||||
client.last_sent_cemi_tick = xTaskGetTickCount();
|
||||
}
|
||||
ESP_LOGI(kTag, "sent KNXnet/IP cEMI service=0x%04x channel=%u seq=%u cemi=0x%02x len=%u to %s",
|
||||
static_cast<unsigned>(service), static_cast<unsigned>(client.channel_id),
|
||||
static_cast<unsigned>(request.connectionHeader().sequenceCounter()), static_cast<unsigned>(data[0]),
|
||||
|
||||
@@ -119,9 +119,9 @@ void GatewayKnxTpIpRouter::handleSearchRequest(uint16_t service, const uint8_t*
|
||||
service == kServiceSearchRequestExt ? kServiceSearchResponseExt : kServiceSearchResponse,
|
||||
body_resp);
|
||||
sendPacket(response_packet, response_remote);
|
||||
ESP_LOGI(kTag, "sent KNXnet/IP search response service=0x%04x namespace=%s mainGroup=%u to %s:%u endpoint=%u.%u.%u.%u:%u",
|
||||
static_cast<unsigned>(service), openknx_namespace_.c_str(),
|
||||
static_cast<unsigned>(config_.main_group),
|
||||
ESP_LOGI(kTag, "sent KNXnet/IP search response service=0x%04x ipInterface=%s ia=0x%04x to %s:%u endpoint=%u.%u.%u.%u:%u",
|
||||
static_cast<unsigned>(service), ipInterfaceName(),
|
||||
static_cast<unsigned>(effectiveIpInterfaceIndividualAddress()),
|
||||
Ipv4String(response_remote.sin_addr.s_addr).c_str(), static_cast<unsigned>(ntohs(response_remote.sin_port)),
|
||||
static_cast<unsigned>((*hpai)[2]), static_cast<unsigned>((*hpai)[3]),
|
||||
static_cast<unsigned>((*hpai)[4]), static_cast<unsigned>((*hpai)[5]),
|
||||
@@ -164,8 +164,9 @@ void GatewayKnxTpIpRouter::handleDescriptionRequest(const uint8_t* packet_data,
|
||||
body_resp.insert(body_resp.end(), tunneling.begin(), tunneling.end());
|
||||
const auto response_packet = OpenKnxIpPacket(kServiceDescriptionResponse, body_resp);
|
||||
sendPacket(response_packet, response_remote);
|
||||
ESP_LOGI(kTag, "sent KNXnet/IP description response namespace=%s medium=0x%02x tpOnline=%d to %s:%u",
|
||||
openknx_namespace_.c_str(), static_cast<unsigned>(advertisedMedium()),
|
||||
ESP_LOGI(kTag, "sent KNXnet/IP description response ipInterface=%s ia=0x%04x medium=0x%02x tpOnline=%d to %s:%u",
|
||||
ipInterfaceName(), static_cast<unsigned>(effectiveIpInterfaceIndividualAddress()),
|
||||
static_cast<unsigned>(advertisedMedium()),
|
||||
tp_uart_online_, Ipv4String(response_remote.sin_addr.s_addr).c_str(),
|
||||
static_cast<unsigned>(ntohs(response_remote.sin_port)));
|
||||
}
|
||||
@@ -314,9 +315,13 @@ GatewayKnxTpIpRouter::TunnelClient* GatewayKnxTpIpRouter::allocateTunnelClient(
|
||||
free_client->connection_type = connection_type;
|
||||
free_client->received_sequence = 255;
|
||||
free_client->send_sequence = 0;
|
||||
const bool oam_router_persona =
|
||||
config_.oam_router.enabled && config_.oam_router.secure_tunnel_enabled &&
|
||||
active_secure_session_id_ != 0;
|
||||
const bool oam_runtime_available = config_.oam_router.enabled && oam_router_ != nullptr;
|
||||
const bool oam_secure_persona = oam_runtime_available &&
|
||||
config_.oam_router.secure_tunnel_enabled && active_secure_session_id_ != 0;
|
||||
const bool oam_programming_persona = oam_runtime_available && oam_programming_mode_ &&
|
||||
(connection_type == kKnxConnectionTypeDeviceManagement ||
|
||||
connection_type == kKnxConnectionTypeTunnel);
|
||||
const bool oam_router_persona = oam_secure_persona || oam_programming_persona;
|
||||
if (oam_router_persona) {
|
||||
const uint16_t first = config_.oam_router.tunnel_address_base;
|
||||
const uint16_t line = first & 0xff00;
|
||||
@@ -582,9 +587,11 @@ void GatewayKnxTpIpRouter::handleConnectRequest(const uint8_t* packet_data, size
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(kTag,
|
||||
"accepted KNXnet/IP connect namespace=%s channel=%u type=0x%02x tunnelPa=0x%04x ctrl=%s data=%s remote=%s active=%u/%u",
|
||||
openknx_namespace_.c_str(), static_cast<unsigned>(client->channel_id),
|
||||
static_cast<unsigned>(connection_type), static_cast<unsigned>(client->individual_address),
|
||||
"accepted KNXnet/IP connect ipInterface=%s channel=%u type=0x%02x persona=%s tunnelPa=0x%04x ctrl=%s data=%s remote=%s active=%u/%u",
|
||||
ipInterfaceName(), static_cast<unsigned>(client->channel_id),
|
||||
static_cast<unsigned>(connection_type),
|
||||
client->oam_router_persona ? "oam" : "reg1",
|
||||
static_cast<unsigned>(client->individual_address),
|
||||
EndpointString(control_remote).c_str(), EndpointString(data_remote).c_str(),
|
||||
EndpointString(remote).c_str(),
|
||||
static_cast<unsigned>(std::count_if(tunnel_clients_.begin(), tunnel_clients_.end(),
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "knx_device_broker.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
KnxResponseDeduplicator::KnxResponseDeduplicator(const uint8_t* original, size_t len) {
|
||||
if (original != nullptr && len > 0) {
|
||||
original_.assign(original, original + len);
|
||||
}
|
||||
}
|
||||
|
||||
bool KnxResponseDeduplicator::remember(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len == 0) {
|
||||
++suppressed_count_;
|
||||
return false;
|
||||
}
|
||||
return remember(std::vector<uint8_t>(data, data + len));
|
||||
}
|
||||
|
||||
bool KnxResponseDeduplicator::remember(const std::vector<uint8_t>& data) {
|
||||
if (data.empty() || (!original_.empty() && data == original_) ||
|
||||
std::find(sent_.begin(), sent_.end(), data) != sent_.end()) {
|
||||
++suppressed_count_;
|
||||
return false;
|
||||
}
|
||||
sent_.push_back(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t KnxResponseDeduplicator::suppressedCount() const { return suppressed_count_; }
|
||||
|
||||
} // namespace gateway
|
||||
@@ -208,6 +208,10 @@ void OamRouterRuntime::setNetworkInterface(esp_netif_t* netif) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void OamRouterRuntime::setBusFrameSender(CemiFrameSender sender) {
|
||||
bus_frame_sender_ = std::move(sender);
|
||||
}
|
||||
|
||||
bool OamRouterRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
||||
CemiFrameSender sender) {
|
||||
#if defined(ENABLE_BAU091A_PERSONA) && defined(USE_CEMI_SERVER)
|
||||
@@ -260,6 +264,34 @@ bool OamRouterRuntime::handleLocalBroadcastManagementFrame(const uint8_t* data,
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OamRouterRuntime::handleBusFrame(const uint8_t* data, size_t len,
|
||||
CemiFrameSender sender) {
|
||||
#if defined(ENABLE_BAU091A_PERSONA)
|
||||
auto* data_link_layer = device_.getSecondaryDataLinkLayer();
|
||||
if (data_link_layer == nullptr || 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 (!shouldConsumeBusFrame(frame)) {
|
||||
return false;
|
||||
}
|
||||
if (frame.messageCode() == L_data_req) {
|
||||
frame.messageCode(L_data_ind);
|
||||
}
|
||||
sender_ = std::move(sender);
|
||||
data_link_layer->externalFrameReceived(frame);
|
||||
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();
|
||||
@@ -277,10 +309,16 @@ bool OamRouterRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context)
|
||||
|
||||
void OamRouterRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
||||
auto* self = static_cast<OamRouterRuntime*>(context);
|
||||
if (self == nullptr || !self->sender_) {
|
||||
if (self == nullptr) {
|
||||
return;
|
||||
}
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
if (self->sender_) {
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
return;
|
||||
}
|
||||
if (self->bus_frame_sender_) {
|
||||
self->bus_frame_sender_(frame.data(), frame.dataLength());
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t OamRouterRuntime::DefaultTunnelClientAddress(uint16_t individual_address) {
|
||||
@@ -320,4 +358,20 @@ bool OamRouterRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
||||
}
|
||||
}
|
||||
|
||||
bool OamRouterRuntime::shouldConsumeBusFrame(CemiFrame& frame) const {
|
||||
if (frame.messageCode() != L_data_ind && frame.messageCode() != L_data_req) {
|
||||
return false;
|
||||
}
|
||||
if (IsBroadcastManagementRequest(frame)) {
|
||||
return true;
|
||||
}
|
||||
if (frame.addressType() != IndividualAddress) {
|
||||
return false;
|
||||
}
|
||||
const uint16_t dest = frame.destinationAddress();
|
||||
const bool commissioning = !configured() || programmingMode();
|
||||
return dest == individualAddress() || dest == tunnelClientAddress() ||
|
||||
(commissioning && dest == kKnxUnconfiguredBroadcastAddress);
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
|
||||
Reference in New Issue
Block a user