f39ae6f0c6
Signed-off-by: Tony <tonylu@tony-cloud.com>
640 lines
27 KiB
C++
640 lines
27 KiB
C++
#include "gateway_knx_private.hpp"
|
|
|
|
#include "knx_device_broker.h"
|
|
|
|
namespace gateway {
|
|
|
|
void GatewayKnxTpIpRouter::selectOpenKnxNetworkInterface(const sockaddr_in& remote) {
|
|
const auto netif = SelectKnxNetifForRemote(remote);
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (ets_device_ != nullptr) {
|
|
ets_device_->setNetworkInterface(netif.has_value() ? netif->netif : nullptr);
|
|
}
|
|
if (oam_router_ != nullptr) {
|
|
oam_router_->setNetworkInterface(netif.has_value() ? netif->netif : nullptr);
|
|
}
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t len,
|
|
TunnelClient* response_client,
|
|
uint16_t response_service,
|
|
const uint8_t* suppress_routing_echo,
|
|
size_t suppress_routing_echo_len) {
|
|
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);
|
|
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
|
if (frame.valid()) {
|
|
route_to_all_internal_instances = IsKnxBroadcastManagementRequest(frame);
|
|
if (!route_to_oam && oam_router_ != nullptr &&
|
|
(MatchesOamRouterLocalIndividualAddress(frame, *oam_router_) ||
|
|
oam_router_->matchesSecureSyncSerial(frame) ||
|
|
oam_router_->matchesRecentSecureToolAccess(frame))) {
|
|
route_to_oam = true;
|
|
}
|
|
}
|
|
}
|
|
if (route_to_all_internal_instances) {
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
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, &response_broker,
|
|
suppress_routing_echo, suppress_routing_echo_len](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());
|
|
const bool routing_context =
|
|
response_client == nullptr && response_service == kServiceRoutingIndication;
|
|
const auto message_code = CemiMessageCode(response_data.data(), response_data.size());
|
|
if (routing_context && suppress_routing_echo != nullptr &&
|
|
IsLocalRoutingEchoIndication(response_data.data(), response_data.size(), 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_data.data(), response_data.size(),
|
|
response_service);
|
|
if (service == kServiceDeviceConfigurationRequest) {
|
|
if (response_client != nullptr && response_client->connected) {
|
|
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
|
response_data.size());
|
|
} else if (routing_context) {
|
|
sendRoutingIndication(response_data.data(), response_data.size());
|
|
}
|
|
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_data.data(),
|
|
response_data.size()) ||
|
|
sent_tunnel_confirmation;
|
|
}
|
|
return;
|
|
}
|
|
if (routing_context) {
|
|
sendRoutingIndication(response_data.data(), response_data.size());
|
|
return;
|
|
}
|
|
if (response_client != nullptr && response_client->connected) {
|
|
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
|
response_data.size());
|
|
return;
|
|
}
|
|
sendTunnelIndication(response_data.data(), response_data.size());
|
|
};
|
|
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) {
|
|
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);
|
|
}
|
|
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (ets_device_ == 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 bool consumed = ets_device_->handleTunnelFrame(
|
|
data, len,
|
|
[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);
|
|
});
|
|
if (needs_tunnel_confirmation && consumed && !sent_tunnel_confirmation) {
|
|
sendCemiFrameToClient(*response_client, kServiceTunnellingRequest,
|
|
tunnel_confirmation.data(), tunnel_confirmation.size());
|
|
}
|
|
syncOpenKnxConfigFromDevice();
|
|
return consumed;
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::handleOamRouterTunnelFrame(const uint8_t* data, size_t len,
|
|
TunnelClient* response_client,
|
|
uint16_t response_service,
|
|
const uint8_t* suppress_routing_echo,
|
|
size_t suppress_routing_echo_len) {
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (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;
|
|
KnxResponseDeduplicator response_broker;
|
|
const bool consumed = oam_router_->handleTunnelFrame(
|
|
data, len,
|
|
[this, response_client, response_service, needs_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;
|
|
}
|
|
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_data.data(), response_data.size());
|
|
if (routing_context && suppress_routing_echo != nullptr &&
|
|
IsLocalRoutingEchoIndication(response_data.data(), response_data.size(), 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_data.data(), response_data.size(),
|
|
response_service);
|
|
if (service == kServiceDeviceConfigurationRequest) {
|
|
if (response_client != nullptr && response_client->connected) {
|
|
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
|
response_data.size());
|
|
} else if (routing_context) {
|
|
sendRoutingIndication(response_data.data(), response_data.size());
|
|
}
|
|
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_data.data(),
|
|
response_data.size()) ||
|
|
sent_tunnel_confirmation;
|
|
}
|
|
return;
|
|
}
|
|
if (routing_context) {
|
|
sendRoutingIndication(response_data.data(), response_data.size());
|
|
return;
|
|
}
|
|
if (response_client != nullptr && response_client->connected) {
|
|
sendCemiFrameToClient(*response_client, service, response_data.data(),
|
|
response_data.size());
|
|
return;
|
|
}
|
|
sendTunnelIndication(response_data.data(), response_data.size());
|
|
});
|
|
if (needs_tunnel_confirmation && consumed && !sent_tunnel_confirmation) {
|
|
sendCemiFrameToClient(*response_client, kServiceTunnellingRequest,
|
|
tunnel_confirmation.data(), tunnel_confirmation.size());
|
|
}
|
|
return consumed;
|
|
}
|
|
|
|
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;
|
|
}
|
|
const bool sent = ets_device_->transmitTpFrame(data, len);
|
|
tp_uart_online_ = ets_device_->tpUartOnline();
|
|
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_) ||
|
|
oam_router_->matchesSecureSyncSerial(frame) ||
|
|
oam_router_->matchesRecentSecureToolAccess(frame));
|
|
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;
|
|
{
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (ets_device_ == nullptr) {
|
|
return false;
|
|
}
|
|
consumed = ets_device_->handleBusFrame(data, len);
|
|
syncOpenKnxConfigFromDevice();
|
|
}
|
|
if (consumed) {
|
|
publishCloudCemiFrame(data, len);
|
|
}
|
|
return consumed;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::publishCloudCemiFrame(const uint8_t* data, size_t len) {
|
|
if (data == nullptr || len == 0 || !config_.oam_router.cloud_remote.enabled ||
|
|
!cloud_cemi_publisher_) {
|
|
return;
|
|
}
|
|
cloud_cemi_uplink_frames_.fetch_add(1, std::memory_order_relaxed);
|
|
cloud_cemi_publisher_(data, len);
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::routeOpenKnxGroupWrite(const uint8_t* data, size_t len,
|
|
const char* context) {
|
|
const auto decoded = DecodeOpenKnxGroupWrite(data, len);
|
|
if (!decoded.has_value()) {
|
|
return false;
|
|
}
|
|
if (!shouldRouteDaliApplicationFrames()) {
|
|
return true;
|
|
}
|
|
const DaliBridgeResult result = group_write_handler_
|
|
? group_write_handler_(decoded->group_address,
|
|
decoded->data.data(),
|
|
decoded->data.size())
|
|
: bridge_.handleGroupWrite(decoded->group_address,
|
|
decoded->data.data(),
|
|
decoded->data.size());
|
|
if (!result.ok && !result.error.empty()) {
|
|
ESP_LOGD(kTag, "%s not routed to DALI: %s", context == nullptr ? "KNX group write" : context,
|
|
result.error.c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::handleFunctionPropertyExtCommand(
|
|
uint16_t object_type, uint8_t object_instance, uint8_t property_id,
|
|
const uint8_t* data, size_t len, std::vector<uint8_t>* response) {
|
|
if (response == nullptr || object_type != kGroupObjectTableObjectType ||
|
|
property_id != kPidGoDiagnostics) {
|
|
return false;
|
|
}
|
|
|
|
const auto decoded = DecodeGoDiagnosticsGroupWrite(data, len);
|
|
if (!decoded.has_value()) {
|
|
const std::string payload = HexBytes(data, len);
|
|
ESP_LOGW(kTag,
|
|
"OpenKNX GO diagnostics write malformed objType=0x%04X objInst=%u property=0x%02X len=%u payload=%s",
|
|
static_cast<unsigned>(object_type), static_cast<unsigned>(object_instance),
|
|
static_cast<unsigned>(property_id), static_cast<unsigned>(len), payload.c_str());
|
|
*response = {ReturnCodes::DataVoid};
|
|
return true;
|
|
}
|
|
|
|
const std::string group_address_text =
|
|
GatewayKnxGroupAddressString(decoded->group_address);
|
|
const std::string payload = HexBytes(decoded->payload, decoded->payload_len);
|
|
ESP_LOGI(kTag,
|
|
"OpenKNX GO diagnostics group write ga=0x%04X (%s) len=%u payload=%s",
|
|
static_cast<unsigned>(decoded->group_address), group_address_text.c_str(),
|
|
static_cast<unsigned>(decoded->payload_len), payload.c_str());
|
|
|
|
if (!shouldRouteDaliApplicationFrames()) {
|
|
ESP_LOGW(kTag,
|
|
"OpenKNX GO diagnostics group write ga=0x%04X (%s) blocked by commissioning-only routing state",
|
|
static_cast<unsigned>(decoded->group_address), group_address_text.c_str());
|
|
*response = {ReturnCodes::TemporarilyNotAvailable};
|
|
return true;
|
|
}
|
|
|
|
const DaliBridgeResult result =
|
|
group_write_handler_ ? group_write_handler_(decoded->group_address, decoded->payload,
|
|
decoded->payload_len)
|
|
: bridge_.handleGroupWrite(decoded->group_address,
|
|
decoded->payload,
|
|
decoded->payload_len);
|
|
const uint8_t return_code = GoDiagnosticsReturnCode(result);
|
|
if (return_code == ReturnCodes::AddressVoid) {
|
|
ESP_LOGW(kTag,
|
|
"OpenKNX GO diagnostics group write ga=0x%04X (%s) returning AddressVoid: %s",
|
|
static_cast<unsigned>(decoded->group_address), group_address_text.c_str(),
|
|
result.error.empty() ? "unmapped KNX group address"
|
|
: result.error.c_str());
|
|
} else if (!result.ok) {
|
|
ESP_LOGW(kTag,
|
|
"OpenKNX GO diagnostics group write ga=0x%04X (%s) failed rc=0x%02X: %s",
|
|
static_cast<unsigned>(decoded->group_address), group_address_text.c_str(),
|
|
static_cast<unsigned>(return_code),
|
|
result.error.empty() ? "command routing failed" : result.error.c_str());
|
|
}
|
|
response->assign(1, return_code);
|
|
return true;
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::handleFunctionPropertyExtState(
|
|
uint16_t object_type, uint8_t object_instance, uint8_t property_id,
|
|
const uint8_t* data, size_t len, std::vector<uint8_t>* response) {
|
|
if (response == nullptr || object_type != kGroupObjectTableObjectType ||
|
|
property_id != kPidGoDiagnostics) {
|
|
return false;
|
|
}
|
|
|
|
const auto decoded = DecodeGoDiagnosticsGroupWrite(data, len);
|
|
if (!decoded.has_value()) {
|
|
const std::string payload = HexBytes(data, len);
|
|
ESP_LOGW(kTag,
|
|
"OpenKNX GO diagnostics state request malformed objType=0x%04X objInst=%u property=0x%02X len=%u payload=%s",
|
|
static_cast<unsigned>(object_type), static_cast<unsigned>(object_instance),
|
|
static_cast<unsigned>(property_id), static_cast<unsigned>(len), payload.c_str());
|
|
*response = {ReturnCodes::DataVoid};
|
|
return true;
|
|
}
|
|
|
|
const std::string group_address_text =
|
|
GatewayKnxGroupAddressString(decoded->group_address);
|
|
ESP_LOGW(kTag,
|
|
"OpenKNX GO diagnostics state request unsupported ga=0x%04X (%s)",
|
|
static_cast<unsigned>(decoded->group_address), group_address_text.c_str());
|
|
*response = {ReturnCodes::InvalidCommand};
|
|
return true;
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::emitOpenKnxGroupValue(uint16_t group_object_number,
|
|
const uint8_t* data, size_t len) {
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (ets_device_ == nullptr) {
|
|
return false;
|
|
}
|
|
const bool emitted = ets_device_->emitGroupValue(
|
|
group_object_number, data, len, [this](const uint8_t* frame_data, size_t frame_len) {
|
|
sendRoutingIndication(frame_data, frame_len);
|
|
sendTunnelIndication(frame_data, frame_len);
|
|
if (ets_device_ != nullptr) {
|
|
const bool sent_to_tp = ets_device_->transmitTpFrame(frame_data, frame_len);
|
|
tp_uart_online_ = sent_to_tp || ets_device_->tpUartOnline();
|
|
}
|
|
});
|
|
syncOpenKnxConfigFromDevice();
|
|
return emitted;
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::shouldRouteDaliApplicationFrames() const {
|
|
if (!commissioning_only_) {
|
|
return true;
|
|
}
|
|
return openknx_configured_.load();
|
|
}
|
|
|
|
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;
|
|
}
|
|
const auto snapshot = ets_device_->snapshot();
|
|
openknx_configured_.store(snapshot.configured);
|
|
bool changed = false;
|
|
GatewayKnxConfig updated = config_;
|
|
if (snapshot.individual_address != 0 && snapshot.individual_address != 0xffff &&
|
|
snapshot.individual_address != updated.individual_address) {
|
|
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());
|
|
for (const auto& association : snapshot.associations) {
|
|
associations.push_back(GatewayKnxEtsAssociation{association.group_address,
|
|
association.group_object_number});
|
|
}
|
|
if (associations.size() != updated.ets_associations.size() ||
|
|
!std::equal(associations.begin(), associations.end(), updated.ets_associations.begin(),
|
|
[](const GatewayKnxEtsAssociation& lhs,
|
|
const GatewayKnxEtsAssociation& rhs) {
|
|
return lhs.group_address == rhs.group_address &&
|
|
lhs.group_object_number == rhs.group_object_number;
|
|
})) {
|
|
updated.ets_associations = std::move(associations);
|
|
changed = true;
|
|
}
|
|
}
|
|
if (!changed) {
|
|
return;
|
|
}
|
|
config_ = updated;
|
|
bridge_.setConfig(config_);
|
|
}
|
|
|
|
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;
|
|
}
|
|
return 0xff01;
|
|
}
|
|
|
|
uint16_t GatewayKnxTpIpRouter::effectiveKnxDeviceIndividualAddress() const {
|
|
if (ets_device_ != nullptr) {
|
|
const uint16_t address = ets_device_->individualAddress();
|
|
if (address != 0 && address != 0xffff) {
|
|
return address;
|
|
}
|
|
}
|
|
return config_.individual_address;
|
|
}
|
|
|
|
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) {
|
|
device = 1;
|
|
}
|
|
uint16_t address = static_cast<uint16_t>((interface_address & 0xff00) | device);
|
|
if (address == 0xffff) {
|
|
address = static_cast<uint16_t>((interface_address & 0xff00) | 0x0001);
|
|
}
|
|
return address;
|
|
}
|
|
|
|
} // namespace gateway
|