|
|
|
@@ -718,6 +718,44 @@ std::string HexBytes(const uint8_t* data, size_t len) {
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<MessageCode> CemiMessageCode(const uint8_t* data, size_t len) {
|
|
|
|
|
if (data == nullptr || len == 0) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
return static_cast<MessageCode>(data[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()) {
|
|
|
|
|
return fallback_service;
|
|
|
|
|
}
|
|
|
|
|
switch (message_code.value()) {
|
|
|
|
|
case L_data_req:
|
|
|
|
|
case L_data_con:
|
|
|
|
|
case L_data_ind:
|
|
|
|
|
return kServiceTunnellingRequest;
|
|
|
|
|
default:
|
|
|
|
|
return kServiceDeviceConfigurationRequest;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuildTunnelConfirmationFrame(const uint8_t* data, size_t len,
|
|
|
|
|
std::vector<uint8_t>* confirmation) {
|
|
|
|
|
if (data == nullptr || confirmation == 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() || frame.messageCode() != L_data_req) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
frame.messageCode(L_data_con);
|
|
|
|
|
frame.confirm(ConfirmNoError);
|
|
|
|
|
*confirmation = std::move(frame_data);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DaliBridgeRequest RequestForTarget(uint16_t group_address,
|
|
|
|
|
const GatewayKnxDaliTarget& target,
|
|
|
|
|
BridgeOperation operation) {
|
|
|
|
@@ -2327,6 +2365,9 @@ bool GatewayKnxTpIpRouter::publishDaliStatus(const GatewayKnxDaliTarget& target,
|
|
|
|
|
kGwReg1GrpKoBlockSize * static_cast<uint16_t>(target.address);
|
|
|
|
|
switch_object = base + kGwReg1KoSwitchState;
|
|
|
|
|
dimm_object = base + kGwReg1KoDimmState;
|
|
|
|
|
} else if (target.kind == GatewayKnxDaliTargetKind::kBroadcast) {
|
|
|
|
|
switch_object = kGwReg1AppKoBroadcastSwitch;
|
|
|
|
|
dimm_object = kGwReg1AppKoBroadcastDimm;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
@@ -2982,6 +3023,15 @@ void GatewayKnxTpIpRouter::handleUdpDatagram(const uint8_t* data, size_t len,
|
|
|
|
|
static_cast<unsigned>(service), static_cast<unsigned>(body[1]),
|
|
|
|
|
static_cast<unsigned>(body[2]), static_cast<unsigned>(body[3]),
|
|
|
|
|
EndpointString(remote).c_str());
|
|
|
|
|
TunnelClient* client = findTunnelClient(body[1]);
|
|
|
|
|
if (client != nullptr) {
|
|
|
|
|
client->last_activity_tick = xTaskGetTickCount();
|
|
|
|
|
if (service == kServiceTunnellingAck && body[3] == kKnxNoError &&
|
|
|
|
|
!client->last_tunnel_confirmation_packet.empty() &&
|
|
|
|
|
client->last_tunnel_confirmation_sequence == body[2]) {
|
|
|
|
|
client->last_tunnel_confirmation_packet.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case kServiceRoutingIndication:
|
|
|
|
@@ -3123,6 +3173,22 @@ 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;
|
|
|
|
|
if (frame.messageCode() == L_data_req && frame.addressType() == IndividualAddress &&
|
|
|
|
|
ets_device_ != nullptr) {
|
|
|
|
|
const uint16_t dest = frame.destinationAddress();
|
|
|
|
|
const uint16_t own_address = ets_device_->individualAddress();
|
|
|
|
|
const uint16_t client_address = ets_device_->tunnelClientAddress();
|
|
|
|
|
const bool commissioning = !ets_device_->configured() || ets_device_->programmingMode();
|
|
|
|
|
if (dest == own_address || dest == client_address ||
|
|
|
|
|
(commissioning && dest == 0xffff)) {
|
|
|
|
|
consumed_by_local_application =
|
|
|
|
|
handleOpenKnxTunnelFrame(cemi, cemi_len, nullptr, kServiceRoutingIndication);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (consumed_by_local_application) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const bool consumed_by_openknx = handleOpenKnxBusFrame(cemi, cemi_len);
|
|
|
|
|
const bool routed_to_dali = routeOpenKnxGroupWrite(cemi, cemi_len, "KNX routing indication");
|
|
|
|
|
const bool sent_to_tp = transmitOpenKnxTpFrame(cemi, cemi_len);
|
|
|
|
@@ -3284,23 +3350,6 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* packet_data, s
|
|
|
|
|
sendTunnellingAck(channel_id, sequence, kKnxErrorConnectionId, remote);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (sequence == client->received_sequence) {
|
|
|
|
|
ESP_LOGD(kTag, "duplicate KNXnet/IP tunnelling request channel=%u seq=%u",
|
|
|
|
|
static_cast<unsigned>(channel_id), static_cast<unsigned>(sequence));
|
|
|
|
|
sendTunnellingAck(channel_id, sequence, kKnxNoError, client->data_remote);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (static_cast<uint8_t>(sequence - 1) != client->received_sequence) {
|
|
|
|
|
ESP_LOGW(kTag, "reject KNXnet/IP tunnelling request channel=%u seq=%u expected=%u from %s",
|
|
|
|
|
static_cast<unsigned>(channel_id), static_cast<unsigned>(sequence),
|
|
|
|
|
static_cast<unsigned>(static_cast<uint8_t>(client->received_sequence + 1)),
|
|
|
|
|
EndpointString(remote).c_str());
|
|
|
|
|
sendTunnellingAck(channel_id, sequence, kKnxErrorSequenceNumber, remote);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
client->received_sequence = sequence;
|
|
|
|
|
client->last_activity_tick = xTaskGetTickCount();
|
|
|
|
|
sendTunnellingAck(channel_id, sequence, kKnxNoError, client->data_remote);
|
|
|
|
|
CemiFrame& frame = tunneling.frame();
|
|
|
|
|
if (!frame.valid()) {
|
|
|
|
|
ESP_LOGW(kTag, "invalid OpenKNX tunnel cEMI channel=%u seq=%u from %s",
|
|
|
|
@@ -3313,6 +3362,46 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* packet_data, s
|
|
|
|
|
}
|
|
|
|
|
const uint8_t* cemi = frame.data();
|
|
|
|
|
const size_t cemi_len = frame.dataLength();
|
|
|
|
|
const std::vector<uint8_t> current_cemi(cemi, cemi + cemi_len);
|
|
|
|
|
const bool duplicate_sequence = sequence == client->received_sequence;
|
|
|
|
|
const bool duplicate_payload = duplicate_sequence && client->last_received_cemi == current_cemi;
|
|
|
|
|
if (duplicate_payload) {
|
|
|
|
|
ESP_LOGD(kTag, "duplicate KNXnet/IP tunnelling request channel=%u seq=%u",
|
|
|
|
|
static_cast<unsigned>(channel_id), static_cast<unsigned>(sequence));
|
|
|
|
|
sendTunnellingAck(channel_id, sequence, kKnxNoError, client->data_remote);
|
|
|
|
|
if (!client->last_tunnel_confirmation_packet.empty()) {
|
|
|
|
|
if (sendPacketToTunnelClient(*client, client->last_tunnel_confirmation_packet)) {
|
|
|
|
|
ESP_LOGI(kTag,
|
|
|
|
|
"resent cached KNXnet/IP tunnel confirmation channel=%u confirmSeq=%u after duplicate req seq=%u to %s",
|
|
|
|
|
static_cast<unsigned>(channel_id),
|
|
|
|
|
static_cast<unsigned>(client->last_tunnel_confirmation_sequence),
|
|
|
|
|
static_cast<unsigned>(sequence), EndpointString(client->data_remote).c_str());
|
|
|
|
|
} else {
|
|
|
|
|
ESP_LOGW(kTag,
|
|
|
|
|
"failed to resend cached KNXnet/IP tunnel confirmation channel=%u confirmSeq=%u to %s",
|
|
|
|
|
static_cast<unsigned>(channel_id),
|
|
|
|
|
static_cast<unsigned>(client->last_tunnel_confirmation_sequence),
|
|
|
|
|
EndpointString(client->data_remote).c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (duplicate_sequence) {
|
|
|
|
|
ESP_LOGW(kTag,
|
|
|
|
|
"accept KNXnet/IP tunnelling request channel=%u with repeated seq=%u because cEMI payload changed",
|
|
|
|
|
static_cast<unsigned>(channel_id), static_cast<unsigned>(sequence));
|
|
|
|
|
} else if (static_cast<uint8_t>(sequence - 1) != client->received_sequence) {
|
|
|
|
|
ESP_LOGW(kTag, "reject KNXnet/IP tunnelling request channel=%u seq=%u expected=%u from %s",
|
|
|
|
|
static_cast<unsigned>(channel_id), static_cast<unsigned>(sequence),
|
|
|
|
|
static_cast<unsigned>(static_cast<uint8_t>(client->received_sequence + 1)),
|
|
|
|
|
EndpointString(remote).c_str());
|
|
|
|
|
sendTunnellingAck(channel_id, sequence, kKnxErrorSequenceNumber, remote);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
client->received_sequence = sequence;
|
|
|
|
|
client->last_received_cemi = current_cemi;
|
|
|
|
|
client->last_activity_tick = xTaskGetTickCount();
|
|
|
|
|
sendTunnellingAck(channel_id, sequence, kKnxNoError, client->data_remote);
|
|
|
|
|
ESP_LOGI(kTag, "rx KNXnet/IP tunnelling request channel=%u seq=%u cemiLen=%u from %s",
|
|
|
|
|
static_cast<unsigned>(channel_id), static_cast<unsigned>(sequence),
|
|
|
|
|
static_cast<unsigned>(cemi_len), EndpointString(remote).c_str());
|
|
|
|
@@ -3320,6 +3409,13 @@ 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) {
|
|
|
|
|
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) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@@ -3604,13 +3700,16 @@ bool GatewayKnxTpIpRouter::currentTransportAllowsTcpHpai() const {
|
|
|
|
|
|
|
|
|
|
std::optional<std::array<uint8_t, 8>> GatewayKnxTpIpRouter::localHpaiForRemote(
|
|
|
|
|
const sockaddr_in& remote, bool tcp) const {
|
|
|
|
|
std::array<uint8_t, 8> hpai{};
|
|
|
|
|
hpai[0] = 0x08;
|
|
|
|
|
hpai[1] = tcp ? kKnxHpaiIpv4Tcp : kKnxHpaiIpv4Udp;
|
|
|
|
|
if (tcp) {
|
|
|
|
|
return hpai;
|
|
|
|
|
}
|
|
|
|
|
const auto netif = SelectKnxNetifForRemote(remote);
|
|
|
|
|
if (!netif.has_value()) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
std::array<uint8_t, 8> hpai{};
|
|
|
|
|
hpai[0] = 0x08;
|
|
|
|
|
hpai[1] = tcp ? kKnxHpaiIpv4Tcp : kKnxHpaiIpv4Udp;
|
|
|
|
|
WriteIp(hpai.data() + 2, netif->address);
|
|
|
|
|
WriteBe16(hpai.data() + 6, config_.udp_port);
|
|
|
|
|
return hpai;
|
|
|
|
@@ -3779,10 +3878,10 @@ void GatewayKnxTpIpRouter::sendTunnelIndicationToClient(TunnelClient& client, co
|
|
|
|
|
sendCemiFrameToClient(client, kServiceTunnellingRequest, data, len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::sendCemiFrameToClient(TunnelClient& client, uint16_t service,
|
|
|
|
|
bool GatewayKnxTpIpRouter::sendCemiFrameToClient(TunnelClient& client, uint16_t service,
|
|
|
|
|
const uint8_t* data, size_t len) {
|
|
|
|
|
if (!client.connected || data == nullptr || len == 0) {
|
|
|
|
|
return;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
std::vector<uint8_t> frame_data(data, data + len);
|
|
|
|
|
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
|
|
|
@@ -3790,20 +3889,34 @@ void GatewayKnxTpIpRouter::sendCemiFrameToClient(TunnelClient& client, uint16_t
|
|
|
|
|
ESP_LOGW(kTag, "not sending invalid OpenKNX cEMI service=0x%04x len=%u to %s",
|
|
|
|
|
static_cast<unsigned>(service), static_cast<unsigned>(len),
|
|
|
|
|
EndpointString(client.data_remote).c_str());
|
|
|
|
|
return;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
KnxIpTunnelingRequest request(frame);
|
|
|
|
|
request.serviceTypeIdentifier(service);
|
|
|
|
|
request.connectionHeader().length(LEN_CH);
|
|
|
|
|
request.connectionHeader().channelId(client.channel_id);
|
|
|
|
|
request.connectionHeader().sequenceCounter(client.send_sequence++);
|
|
|
|
|
const auto message_code = CemiMessageCode(data, len);
|
|
|
|
|
const uint8_t send_sequence = client.send_sequence++;
|
|
|
|
|
request.connectionHeader().sequenceCounter(send_sequence);
|
|
|
|
|
request.connectionHeader().status(kKnxNoError);
|
|
|
|
|
const std::vector<uint8_t> packet(request.data(), request.data() + request.totalLength());
|
|
|
|
|
sendPacketToTunnelClient(client, packet);
|
|
|
|
|
if (!sendPacketToTunnelClient(client, packet)) {
|
|
|
|
|
ESP_LOGW(kTag, "failed to send KNXnet/IP cEMI service=0x%04x channel=%u seq=%u to %s",
|
|
|
|
|
static_cast<unsigned>(service), static_cast<unsigned>(client.channel_id),
|
|
|
|
|
static_cast<unsigned>(request.connectionHeader().sequenceCounter()),
|
|
|
|
|
EndpointString(client.data_remote).c_str());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (service == kServiceTunnellingRequest && message_code.has_value() &&
|
|
|
|
|
message_code.value() == L_data_con) {
|
|
|
|
|
client.last_tunnel_confirmation_sequence = send_sequence;
|
|
|
|
|
client.last_tunnel_confirmation_packet = packet;
|
|
|
|
|
}
|
|
|
|
|
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]),
|
|
|
|
|
static_cast<unsigned>(len), EndpointString(client.data_remote).c_str());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::sendConnectionStateResponse(uint8_t channel_id, uint8_t status,
|
|
|
|
@@ -3844,18 +3957,26 @@ void GatewayKnxTpIpRouter::sendConnectResponse(uint8_t channel_id, uint8_t statu
|
|
|
|
|
}
|
|
|
|
|
KnxIpConnectResponse response(*knx_ip_parameters_, tunnel_address, config_.udp_port,
|
|
|
|
|
channel_id, connection_type);
|
|
|
|
|
const bool tcp = currentTransportAllowsTcpHpai();
|
|
|
|
|
const uint32_t endpoint_address = ntohl(netif->address);
|
|
|
|
|
response.controlEndpoint().code(currentTransportAllowsTcpHpai() ? IPV4_TCP : IPV4_UDP);
|
|
|
|
|
response.controlEndpoint().ipAddress(endpoint_address);
|
|
|
|
|
response.controlEndpoint().ipPortNumber(config_.udp_port);
|
|
|
|
|
response.controlEndpoint().code(tcp ? IPV4_TCP : IPV4_UDP);
|
|
|
|
|
response.controlEndpoint().ipAddress(tcp ? 0 : endpoint_address);
|
|
|
|
|
response.controlEndpoint().ipPortNumber(tcp ? 0 : config_.udp_port);
|
|
|
|
|
const std::vector<uint8_t> packet(response.data(), response.data() + response.totalLength());
|
|
|
|
|
sendPacket(packet, remote);
|
|
|
|
|
ESP_LOGI(kTag, "sent KNXnet/IP connect response channel=%u type=0x%02x to %s endpoint=%u.%u.%u.%u:%u",
|
|
|
|
|
std::string endpoint_string;
|
|
|
|
|
if (tcp) {
|
|
|
|
|
endpoint_string = "0.0.0.0:0 (TCP HPAI)";
|
|
|
|
|
} else {
|
|
|
|
|
sockaddr_in local_endpoint{};
|
|
|
|
|
local_endpoint.sin_family = AF_INET;
|
|
|
|
|
local_endpoint.sin_port = htons(config_.udp_port);
|
|
|
|
|
local_endpoint.sin_addr.s_addr = netif->address;
|
|
|
|
|
endpoint_string = EndpointString(local_endpoint);
|
|
|
|
|
}
|
|
|
|
|
ESP_LOGI(kTag, "sent KNXnet/IP connect response channel=%u type=0x%02x to %s endpoint=%s",
|
|
|
|
|
static_cast<unsigned>(channel_id), static_cast<unsigned>(connection_type),
|
|
|
|
|
EndpointString(remote).c_str(), static_cast<unsigned>((endpoint_address >> 24) & 0xff),
|
|
|
|
|
static_cast<unsigned>((endpoint_address >> 16) & 0xff),
|
|
|
|
|
static_cast<unsigned>((endpoint_address >> 8) & 0xff),
|
|
|
|
|
static_cast<unsigned>(endpoint_address & 0xff), static_cast<unsigned>(config_.udp_port));
|
|
|
|
|
EndpointString(remote).c_str(), endpoint_string.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::sendRoutingIndication(const uint8_t* data, size_t len) {
|
|
|
|
@@ -3908,15 +4029,57 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
|
|
|
|
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](const uint8_t* response,
|
|
|
|
|
size_t response_len) {
|
|
|
|
|
if (response_client != nullptr && response_client->connected) {
|
|
|
|
|
sendCemiFrameToClient(*response_client, response_service, response, response_len);
|
|
|
|
|
} else {
|
|
|
|
|
sendTunnelIndication(response, response_len);
|
|
|
|
|
data, len,
|
|
|
|
|
[this, response_client, response_service, needs_tunnel_confirmation,
|
|
|
|
|
&tunnel_confirmation, &sent_tunnel_confirmation](const uint8_t* response,
|
|
|
|
|
size_t response_len) {
|
|
|
|
|
if (response == nullptr || response_len == 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const bool routing_context =
|
|
|
|
|
response_client == nullptr && response_service == kServiceRoutingIndication;
|
|
|
|
|
const auto message_code = CemiMessageCode(response, response_len);
|
|
|
|
|
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 (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;
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|