|
|
|
@@ -11,6 +11,10 @@
|
|
|
|
|
#include "ets_device_runtime.h"
|
|
|
|
|
#include "soc/uart_periph.h"
|
|
|
|
|
|
|
|
|
|
#include "knx/cemi_frame.h"
|
|
|
|
|
#include "knx/knx_ip_search_response.h"
|
|
|
|
|
#include "knx/knx_ip_description_response.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <cerrno>
|
|
|
|
@@ -741,17 +745,34 @@ bool IsKnxNetIpSecureService(uint16_t service) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsExtendedTpFrame(const uint8_t* data, size_t len) {
|
|
|
|
|
return len > 0 && (data[0] & 0xD3) == 0x10;
|
|
|
|
|
if (data == nullptr || len == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// TP-UART non-monitor mode: L_DATA_EXTENDED_IND indicator byte (0x10 with mask 0xD3)
|
|
|
|
|
if ((data[0] & 0xD3U) == 0x10U) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// Raw bus frame (transparent / bus-monitor mode): bits 7-6 = 01, bit 4 = 1
|
|
|
|
|
return (data[0] & 0xD0U) == 0x40U && (data[0] & 0x10U) != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t ExpectedTpFrameSize(const uint8_t* data, size_t len) {
|
|
|
|
|
if (data == nullptr || len < 6) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
// TP-UART non-monitor mode prepends L_DATA_STANDARD_IND (0x90) or
|
|
|
|
|
// L_DATA_EXTENDED_IND (0x10) indicator bytes before each frame.
|
|
|
|
|
const bool has_indicator = (data[0] & 0xD3U) == 0x90U || (data[0] & 0xD3U) == 0x10U;
|
|
|
|
|
const size_t off = has_indicator ? 1U : 0U;
|
|
|
|
|
// Standard frame: ctrl(1)+src(2)+dst(2)+npci(1)=6, data(L), crc(1)
|
|
|
|
|
// Extended frame: ctrl(1)+src(2)+dst(2)+npci(1)+ext_len(1)=7, data(L), crc(1)
|
|
|
|
|
// With indicator byte prepended, add 1 to each base.
|
|
|
|
|
if (IsExtendedTpFrame(data, len)) {
|
|
|
|
|
return 9U + data[6];
|
|
|
|
|
const size_t base = has_indicator ? 9U : 8U;
|
|
|
|
|
return base + data[6U + off];
|
|
|
|
|
}
|
|
|
|
|
return 8U + (data[5] & 0x0F);
|
|
|
|
|
const size_t base = has_indicator ? 8U : 7U;
|
|
|
|
|
return base + (data[5U + off] & 0x0FU);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ValidateTpChecksum(const uint8_t* data, size_t len) {
|
|
|
|
@@ -766,18 +787,44 @@ bool ValidateTpChecksum(const uint8_t* data, size_t len) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsTpUartControlByte(uint8_t byte) {
|
|
|
|
|
// L_Data.con: lower 7 bits must equal 0x0B (matches both positive 0x8B and negative 0x0B).
|
|
|
|
|
// Using the reference mask-based check to be robust across different TP-UART implementations.
|
|
|
|
|
return byte == kTpUartResetIndication ||
|
|
|
|
|
byte == kTpUartLDataConfirmPositive ||
|
|
|
|
|
byte == kTpUartLDataConfirmNegative || byte == kTpUartBusy ||
|
|
|
|
|
(byte & kTpUartStateIndicationMask) == kTpUartStateIndicationMask;
|
|
|
|
|
(byte & 0x7fU) == kTpUartLDataConfirmNegative ||
|
|
|
|
|
byte == kTpUartBusy ||
|
|
|
|
|
(byte & kTpUartStateIndicationMask) == kTpUartStateIndicationMask ||
|
|
|
|
|
(byte & 0x33U) == 0x00U; // L_ACKN_IND
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsTpUartFrameStart(uint8_t byte, bool* extended) {
|
|
|
|
|
if (extended == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
*extended = (byte & 0x80) == 0;
|
|
|
|
|
return (byte & 0x50) == 0x10;
|
|
|
|
|
// TP-UART non-monitor mode: controller sends L_DATA_STANDARD_IND (0x90) or
|
|
|
|
|
// L_DATA_EXTENDED_IND (0x10) indicator bytes before each frame.
|
|
|
|
|
// TP-UART bus-monitor (transparent) mode: raw bus control bytes are passed through.
|
|
|
|
|
// Standard raw: bit7=1, bit6=0, bit4=1. Extended raw: bit7=0, bit6=1, bit4=1.
|
|
|
|
|
// Detect both indicator and raw formats.
|
|
|
|
|
const bool is_standard_indicator = (byte & 0xD3U) == 0x90U; // L_DATA_STANDARD_IND
|
|
|
|
|
const bool is_extended_indicator = (byte & 0xD3U) == 0x10U; // L_DATA_EXTENDED_IND
|
|
|
|
|
if (is_standard_indicator || is_extended_indicator) {
|
|
|
|
|
*extended = is_extended_indicator;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// Raw bus frame format (transparent / bus-monitor mode)
|
|
|
|
|
if ((byte & 0x10U) == 0) {
|
|
|
|
|
return false; // bit 4 must be set for valid KNX frame control bytes
|
|
|
|
|
}
|
|
|
|
|
const uint8_t top = byte & 0xC0U;
|
|
|
|
|
if (top == 0x80U) {
|
|
|
|
|
*extended = false; // standard frame
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (top == 0x40U) {
|
|
|
|
|
*extended = true; // extended frame
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> WrapTpUartTelegram(const std::vector<uint8_t>& telegram) {
|
|
|
|
@@ -2047,6 +2094,8 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
|
|
|
|
|
ets_device_ = std::make_unique<openknx::EtsDeviceRuntime>(openknx_namespace_,
|
|
|
|
|
config_.individual_address,
|
|
|
|
|
effectiveTunnelAddress());
|
|
|
|
|
knx_ip_parameters_ = std::make_unique<IpParameterObject>(
|
|
|
|
|
ets_device_->deviceObject(), ets_device_->platform());
|
|
|
|
|
openknx_configured_.store(ets_device_->configured());
|
|
|
|
|
ESP_LOGI(kTag,
|
|
|
|
|
"OpenKNX runtime namespace=%s configured=%d ipInterface=0x%04x "
|
|
|
|
@@ -2191,6 +2240,7 @@ void GatewayKnxTpIpRouter::finishTask() {
|
|
|
|
|
{
|
|
|
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
|
|
|
setProgrammingLed(false);
|
|
|
|
|
knx_ip_parameters_.reset();
|
|
|
|
|
ets_device_.reset();
|
|
|
|
|
openknx_configured_.store(false);
|
|
|
|
|
}
|
|
|
|
@@ -2322,7 +2372,11 @@ bool GatewayKnxTpIpRouter::configureSocket() {
|
|
|
|
|
ESP_LOGW(kTag, "failed to enable TCP reuse for KNX port %u: errno=%d (%s)",
|
|
|
|
|
static_cast<unsigned>(config_.udp_port), errno, std::strerror(errno));
|
|
|
|
|
}
|
|
|
|
|
if (bind(tcp_sock_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
|
|
|
|
|
sockaddr_in tcp_bind_addr{};
|
|
|
|
|
tcp_bind_addr.sin_family = AF_INET;
|
|
|
|
|
tcp_bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
|
|
|
tcp_bind_addr.sin_port = htons(config_.udp_port);
|
|
|
|
|
if (bind(tcp_sock_, reinterpret_cast<sockaddr*>(&tcp_bind_addr), sizeof(tcp_bind_addr)) < 0) {
|
|
|
|
|
const int saved_errno = errno;
|
|
|
|
|
last_error_ = ErrnoDetail("failed to bind KNXnet/IP TCP socket on port " +
|
|
|
|
|
std::to_string(config_.udp_port),
|
|
|
|
@@ -2686,7 +2740,7 @@ void GatewayKnxTpIpRouter::handleUdpDatagram(const uint8_t* data, size_t len,
|
|
|
|
|
switch (service) {
|
|
|
|
|
case kServiceSearchRequest:
|
|
|
|
|
case kServiceSearchRequestExt:
|
|
|
|
|
handleSearchRequest(service, body, body_len, remote);
|
|
|
|
|
handleSearchRequest(body, body_len, remote);
|
|
|
|
|
break;
|
|
|
|
|
case kServiceDescriptionRequest:
|
|
|
|
|
handleDescriptionRequest(body, body_len, remote);
|
|
|
|
@@ -2732,7 +2786,7 @@ void GatewayKnxTpIpRouter::handleUdpDatagram(const uint8_t* data, size_t len,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::handleSearchRequest(uint16_t service, const uint8_t* body,
|
|
|
|
|
void GatewayKnxTpIpRouter::handleSearchRequest(const uint8_t* body,
|
|
|
|
|
size_t len, const sockaddr_in& remote) {
|
|
|
|
|
if (HasUnsupportedHpaiProtocolAt(body, len, 0, currentTransportAllowsTcpHpai())) {
|
|
|
|
|
ESP_LOGW(kTag, "ignore KNXnet/IP search request from %s: unsupported HPAI protocol",
|
|
|
|
@@ -2741,48 +2795,27 @@ void GatewayKnxTpIpRouter::handleSearchRequest(uint16_t service, const uint8_t*
|
|
|
|
|
}
|
|
|
|
|
sockaddr_in response_remote = ResponseEndpointFromHpai(body, len, remote);
|
|
|
|
|
selectOpenKnxNetworkInterface(response_remote);
|
|
|
|
|
std::set<uint8_t> requested_dibs;
|
|
|
|
|
if (service == kServiceSearchRequestExt && body != nullptr && len > 8) {
|
|
|
|
|
size_t offset = 8;
|
|
|
|
|
while (offset + 2 <= len) {
|
|
|
|
|
const uint8_t srp_len = body[offset];
|
|
|
|
|
if (srp_len < 2 || offset + srp_len > len) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
const uint8_t srp_type = body[offset + 1];
|
|
|
|
|
if (srp_type == 0x01) {
|
|
|
|
|
// The programming button belongs to the logical KNX-DALI device behind the tunnel.
|
|
|
|
|
return;
|
|
|
|
|
} else if (srp_type == 0x02 && srp_len >= 8) {
|
|
|
|
|
uint8_t mac[6]{};
|
|
|
|
|
if (!ReadBaseMac(mac) || std::memcmp(mac, body + offset + 2, 6) != 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else if (srp_type == 0x03) {
|
|
|
|
|
for (size_t service_offset = offset + 2; service_offset + 1 < offset + srp_len;
|
|
|
|
|
service_offset += 2) {
|
|
|
|
|
const uint8_t family = body[service_offset];
|
|
|
|
|
const uint8_t version = body[service_offset + 1];
|
|
|
|
|
if ((family == kKnxServiceFamilyCore && version > 2) ||
|
|
|
|
|
(family == kKnxServiceFamilyDeviceManagement && version > 1) ||
|
|
|
|
|
(family == kKnxServiceFamilyTunnelling &&
|
|
|
|
|
(!config_.tunnel_enabled || version > 1)) ||
|
|
|
|
|
(family == kKnxServiceFamilyRouting &&
|
|
|
|
|
(!config_.multicast_enabled || version > 1))) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (srp_type == 0x04) {
|
|
|
|
|
for (size_t dib_offset = offset + 2; dib_offset < offset + srp_len; ++dib_offset) {
|
|
|
|
|
requested_dibs.insert(body[dib_offset]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
offset += srp_len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto hpai = localHpaiForRemote(response_remote, currentTransportAllowsTcpHpai());
|
|
|
|
|
if (!hpai.has_value()) {
|
|
|
|
|
ESP_LOGW(kTag, "cannot send KNXnet/IP search response to %s: no active IPv4 interface",
|
|
|
|
|
EndpointString(response_remote).c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
sendSearchResponse(service == kServiceSearchRequestExt ? kServiceSearchResponseExt
|
|
|
|
|
: kServiceSearchResponse,
|
|
|
|
|
response_remote, requested_dibs);
|
|
|
|
|
std::vector<uint8_t> body_resp;
|
|
|
|
|
body_resp.insert(body_resp.end(), hpai->begin(), hpai->end());
|
|
|
|
|
auto dev_dib = buildDeviceInfoDib(response_remote);
|
|
|
|
|
body_resp.insert(body_resp.end(), dev_dib.begin(), dev_dib.end());
|
|
|
|
|
auto svc_dib = buildSupportedServiceDib();
|
|
|
|
|
body_resp.insert(body_resp.end(), svc_dib.begin(), svc_dib.end());
|
|
|
|
|
const auto packet = KnxNetIpPacket(kServiceSearchResponse, body_resp);
|
|
|
|
|
sendPacket(packet, response_remote);
|
|
|
|
|
ESP_LOGI(kTag, "sent KNXnet/IP search response namespace=%s mainGroup=%u to %s:%u endpoint=%u.%u.%u.%u:%u",
|
|
|
|
|
openknx_namespace_.c_str(), static_cast<unsigned>(config_.main_group),
|
|
|
|
|
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]),
|
|
|
|
|
static_cast<unsigned>(config_.udp_port));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::handleDescriptionRequest(const uint8_t* body, size_t len,
|
|
|
|
@@ -2794,7 +2827,18 @@ void GatewayKnxTpIpRouter::handleDescriptionRequest(const uint8_t* body, size_t
|
|
|
|
|
}
|
|
|
|
|
const sockaddr_in response_remote = ResponseEndpointFromHpai(body, len, remote);
|
|
|
|
|
selectOpenKnxNetworkInterface(response_remote);
|
|
|
|
|
sendDescriptionResponse(response_remote);
|
|
|
|
|
auto device = buildDeviceInfoDib(response_remote);
|
|
|
|
|
auto services = buildSupportedServiceDib();
|
|
|
|
|
std::vector<uint8_t> body_resp;
|
|
|
|
|
body_resp.reserve(device.size() + services.size());
|
|
|
|
|
body_resp.insert(body_resp.end(), device.begin(), device.end());
|
|
|
|
|
body_resp.insert(body_resp.end(), services.begin(), services.end());
|
|
|
|
|
const auto packet = KnxNetIpPacket(kServiceDescriptionResponse, body_resp);
|
|
|
|
|
sendPacket(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()),
|
|
|
|
|
tp_uart_online_, Ipv4String(response_remote.sin_addr.s_addr).c_str(),
|
|
|
|
|
static_cast<unsigned>(ntohs(response_remote.sin_port)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::handleRoutingIndication(const uint8_t* body, size_t len) {
|
|
|
|
@@ -2978,12 +3022,19 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* body, size_t l
|
|
|
|
|
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());
|
|
|
|
|
const bool group_frame = IsCemiGroupFrame(cemi, cemi_len);
|
|
|
|
|
|
|
|
|
|
// Forward ALL tunnel cEMI frames to the TP bus first, so that bus-check,
|
|
|
|
|
// property-read and other KNX bus frames reach the physical TP-UART
|
|
|
|
|
// (NCN5120) and real KNX devices on the bus can reply. The TP-UART
|
|
|
|
|
// response/confirmation will come back through pollTpUart() and be
|
|
|
|
|
// forwarded to ETS via sendTunnelIndication().
|
|
|
|
|
forwardCemiToTp(cemi, cemi_len);
|
|
|
|
|
|
|
|
|
|
const bool consumed_by_openknx = handleOpenKnxTunnelFrame(cemi, cemi_len, client);
|
|
|
|
|
if (consumed_by_openknx) {
|
|
|
|
|
if (group_frame) {
|
|
|
|
|
forwardCemiToTp(cemi, cemi_len);
|
|
|
|
|
}
|
|
|
|
|
// OpenKNX (CemiServer / Bau07B0) handled the frame (ETS memory-model
|
|
|
|
|
// queries, function-property commands, etc.). It may have emitted
|
|
|
|
|
// a response already via EmitTunnelFrame.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (shouldRouteDaliApplicationFrames()) {
|
|
|
|
@@ -2992,7 +3043,7 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* body, size_t l
|
|
|
|
|
ESP_LOGD(kTag, "KNX tunnel frame not routed to DALI: %s", result.error.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
forwardCemiToTp(cemi, cemi_len);
|
|
|
|
|
// Frame was already forwarded to TP-UART above; no need to forward again.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::handleDeviceConfigurationRequest(const uint8_t* body, size_t len,
|
|
|
|
@@ -3255,6 +3306,28 @@ std::optional<std::array<uint8_t, 8>> GatewayKnxTpIpRouter::localHpaiForRemote(
|
|
|
|
|
return hpai;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> GatewayKnxTpIpRouter::buildOpenKnxSearchResponse(
|
|
|
|
|
const sockaddr_in& remote) const {
|
|
|
|
|
// Use OpenKNX's proven DIB construction via KnxIpSearchResponse.
|
|
|
|
|
// Requires ets_device_ to be initialized (DeviceObject + Platform).
|
|
|
|
|
if (ets_device_ == nullptr || knx_ip_parameters_ == nullptr) {
|
|
|
|
|
ESP_LOGW(kTag, "OpenKNX search response unavailable; falling back to hand-rolled DIBs");
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
KnxIpSearchResponse response(*knx_ip_parameters_, ets_device_->deviceObject());
|
|
|
|
|
return std::vector<uint8_t>(response.data(), response.data() + response.totalLength());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> GatewayKnxTpIpRouter::buildOpenKnxDescriptionResponse(
|
|
|
|
|
const sockaddr_in& remote) const {
|
|
|
|
|
if (ets_device_ == nullptr || knx_ip_parameters_ == nullptr) {
|
|
|
|
|
ESP_LOGW(kTag, "OpenKNX description response unavailable; falling back to hand-rolled DIBs");
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
KnxIpDescriptionResponse response(*knx_ip_parameters_, ets_device_->deviceObject());
|
|
|
|
|
return std::vector<uint8_t>(response.data(), response.data() + response.totalLength());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> GatewayKnxTpIpRouter::buildDeviceInfoDib(
|
|
|
|
|
const sockaddr_in& remote) const {
|
|
|
|
|
std::vector<uint8_t> dib(54, 0);
|
|
|
|
@@ -3380,88 +3453,6 @@ std::vector<uint8_t> GatewayKnxTpIpRouter::buildSupportedServiceDib() const {
|
|
|
|
|
return dib;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::sendSearchResponse(uint16_t service, const sockaddr_in& remote,
|
|
|
|
|
const std::set<uint8_t>& requested_dibs) {
|
|
|
|
|
if (udp_sock_ < 0 && active_tcp_sock_ < 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const auto hpai = localHpaiForRemote(remote, currentTransportAllowsTcpHpai());
|
|
|
|
|
if (!hpai.has_value()) {
|
|
|
|
|
ESP_LOGW(kTag, "cannot send KNXnet/IP search response to %s: no active IPv4 interface",
|
|
|
|
|
EndpointString(remote).c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::vector<uint8_t> body;
|
|
|
|
|
body.insert(body.end(), hpai->begin(), hpai->end());
|
|
|
|
|
std::set<uint8_t> appended_dibs;
|
|
|
|
|
auto append_dib = [&body, &appended_dibs](const std::vector<uint8_t>& dib) {
|
|
|
|
|
if (dib.size() < 2 || !appended_dibs.insert(dib[1]).second) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
body.insert(body.end(), dib.begin(), dib.end());
|
|
|
|
|
};
|
|
|
|
|
append_dib(buildDeviceInfoDib(remote));
|
|
|
|
|
append_dib(buildSupportedServiceDib());
|
|
|
|
|
if (service == kServiceSearchResponseExt) {
|
|
|
|
|
append_dib(buildExtendedDeviceInfoDib());
|
|
|
|
|
for (const uint8_t dib_type : requested_dibs) {
|
|
|
|
|
switch (dib_type) {
|
|
|
|
|
case kKnxDibDeviceInfo:
|
|
|
|
|
append_dib(buildDeviceInfoDib(remote));
|
|
|
|
|
break;
|
|
|
|
|
case kKnxDibSupportedServices:
|
|
|
|
|
append_dib(buildSupportedServiceDib());
|
|
|
|
|
break;
|
|
|
|
|
case kKnxDibIpConfig:
|
|
|
|
|
append_dib(buildIpConfigDib(remote, false));
|
|
|
|
|
break;
|
|
|
|
|
case kKnxDibCurrentIpConfig:
|
|
|
|
|
append_dib(buildIpConfigDib(remote, true));
|
|
|
|
|
break;
|
|
|
|
|
case kKnxDibKnxAddresses:
|
|
|
|
|
append_dib(buildKnxAddressesDib());
|
|
|
|
|
break;
|
|
|
|
|
case kKnxDibTunnellingInfo:
|
|
|
|
|
if (config_.tunnel_enabled) {
|
|
|
|
|
append_dib(buildTunnelingInfoDib());
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case kKnxDibExtendedDeviceInfo:
|
|
|
|
|
append_dib(buildExtendedDeviceInfoDib());
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const auto packet = KnxNetIpPacket(service, body);
|
|
|
|
|
sendPacket(packet, remote);
|
|
|
|
|
ESP_LOGI(kTag, "sent KNXnet/IP search response namespace=%s mainGroup=%u to %s:%u endpoint=%u.%u.%u.%u:%u",
|
|
|
|
|
openknx_namespace_.c_str(), static_cast<unsigned>(config_.main_group),
|
|
|
|
|
Ipv4String(remote.sin_addr.s_addr).c_str(), static_cast<unsigned>(ntohs(remote.sin_port)),
|
|
|
|
|
static_cast<unsigned>((*hpai)[2]), static_cast<unsigned>((*hpai)[3]),
|
|
|
|
|
static_cast<unsigned>((*hpai)[4]), static_cast<unsigned>((*hpai)[5]),
|
|
|
|
|
static_cast<unsigned>(config_.udp_port));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::sendDescriptionResponse(const sockaddr_in& remote) {
|
|
|
|
|
if (udp_sock_ < 0 && active_tcp_sock_ < 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto device = buildDeviceInfoDib(remote);
|
|
|
|
|
auto services = buildSupportedServiceDib();
|
|
|
|
|
std::vector<uint8_t> body;
|
|
|
|
|
body.reserve(device.size() + services.size());
|
|
|
|
|
body.insert(body.end(), device.begin(), device.end());
|
|
|
|
|
body.insert(body.end(), services.begin(), services.end());
|
|
|
|
|
const auto packet = KnxNetIpPacket(kServiceDescriptionResponse, body);
|
|
|
|
|
sendPacket(packet, 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()),
|
|
|
|
|
tp_uart_online_, Ipv4String(remote.sin_addr.s_addr).c_str(),
|
|
|
|
|
static_cast<unsigned>(ntohs(remote.sin_port)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::sendTunnelIndication(const uint8_t* data, size_t len) {
|
|
|
|
|
if (data == nullptr || len == 0) {
|
|
|
|
|
return;
|
|
|
|
@@ -3769,34 +3760,46 @@ void GatewayKnxTpIpRouter::handleTpUartControlByte(uint8_t byte) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (byte == kTpUartBusy) {
|
|
|
|
|
last_error_ = "KNX TP-UART bus busy";
|
|
|
|
|
ESP_LOGW(kTag, "%s", last_error_.c_str());
|
|
|
|
|
ESP_LOGW(kTag, "KNX TP-UART bus busy");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (byte == kTpUartLDataConfirmNegative) {
|
|
|
|
|
last_error_ = "KNX TP-UART negative confirmation";
|
|
|
|
|
ESP_LOGW(kTag, "%s", last_error_.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (byte == kTpUartLDataConfirmPositive) {
|
|
|
|
|
// L_Data.con: use masked comparison consistent with IsTpUartControlByte
|
|
|
|
|
if ((byte & 0x7fU) == kTpUartLDataConfirmNegative) {
|
|
|
|
|
const bool positive = (byte & 0x80U) != 0;
|
|
|
|
|
if (!positive) {
|
|
|
|
|
ESP_LOGD(kTag, "KNX TP-UART negative confirmation 0x%02x", byte);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ((byte & kTpUartStateIndicationMask) == kTpUartStateIndicationMask) {
|
|
|
|
|
tp_uart_online_ = true;
|
|
|
|
|
}
|
|
|
|
|
// L_ACKN_IND: acknowledge indication (busy / nack status embedded in byte)
|
|
|
|
|
if ((byte & 0x33U) == 0x00U) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GatewayKnxTpIpRouter::handleTpTelegram(const uint8_t* data, size_t len) {
|
|
|
|
|
if (data == nullptr || len == 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const std::vector<uint8_t> telegram(data, data + len);
|
|
|
|
|
// In non-monitor mode the TP-UART prepends L_DATA_STANDARD_IND (0x90) or
|
|
|
|
|
// L_DATA_EXTENDED_IND (0x10) indicator bytes. Strip them so downstream
|
|
|
|
|
// helpers always receive a raw KNX TP telegram starting with the control byte.
|
|
|
|
|
const uint8_t* frame_data = data;
|
|
|
|
|
size_t frame_len = len;
|
|
|
|
|
if (len > 1U && ((data[0] & 0xD3U) == 0x90U || (data[0] & 0xD3U) == 0x10U)) {
|
|
|
|
|
frame_data = data + 1;
|
|
|
|
|
frame_len = len - 1U;
|
|
|
|
|
}
|
|
|
|
|
const std::vector<uint8_t> telegram(frame_data, frame_data + frame_len);
|
|
|
|
|
if (!tp_last_sent_telegram_.empty() &&
|
|
|
|
|
TpTelegramEqualsIgnoringRepeatBit(telegram, tp_last_sent_telegram_)) {
|
|
|
|
|
tp_last_sent_telegram_.clear();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const auto cemi = TpTelegramToCemi(data, len);
|
|
|
|
|
const auto cemi = TpTelegramToCemi(frame_data, frame_len);
|
|
|
|
|
if (!cemi.has_value()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|