feat: Enhance EtsDeviceRuntime constructor and multicast handling
- Updated EtsDeviceRuntime constructor to accept an optional tunnel_client_address parameter with a default value of 0. - Modified EspIdfPlatform::setupMultiCast to use IPPROTO_UDP for socket creation and improved multicast interface selection based on the current IP address. - Ensured that the multicast interface is set only if a valid local address is available. - Adjusted the client address assignment in EtsDeviceRuntime to use the provided tunnel_client_address if valid, falling back to the default otherwise. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -693,14 +693,23 @@ config GATEWAY_KNX_MULTICAST_ADDRESS
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED && GATEWAY_KNX_MULTICAST_ENABLED
|
||||
default "224.0.23.12"
|
||||
|
||||
config GATEWAY_KNX_INDIVIDUAL_ADDRESS
|
||||
int "KNX individual address raw value"
|
||||
config GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS
|
||||
int "KNXnet/IP interface individual address raw value"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 0 65535
|
||||
default 4353
|
||||
default 65281
|
||||
help
|
||||
Raw 16-bit individual address advertised to KNXnet/IP tunnel clients.
|
||||
The default 4353 is 1.1.1.
|
||||
Raw 16-bit individual address advertised by the KNXnet/IP interface.
|
||||
The default 65281 is 15.15.1.
|
||||
|
||||
config GATEWAY_KNX_INDIVIDUAL_ADDRESS
|
||||
int "Logical KNX-DALI gateway individual address raw value"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 0 65535
|
||||
default 65534
|
||||
help
|
||||
Raw 16-bit individual address used by the ETS-programmable KNX-DALI gateway device.
|
||||
The default 65534 is 15.15.254, used as the unprogrammed logical device address.
|
||||
|
||||
config GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO
|
||||
int "KNX programming button GPIO"
|
||||
|
||||
@@ -228,7 +228,11 @@
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS
|
||||
#define CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS 4353
|
||||
#define CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS 65534
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS
|
||||
#define CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS 65281
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO
|
||||
@@ -862,6 +866,8 @@ extern "C" void app_main(void) {
|
||||
default_knx.main_group = static_cast<uint8_t>(CONFIG_GATEWAY_KNX_MAIN_GROUP);
|
||||
default_knx.udp_port = static_cast<uint16_t>(CONFIG_GATEWAY_KNX_UDP_PORT);
|
||||
default_knx.multicast_address = CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS;
|
||||
default_knx.ip_interface_individual_address =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS);
|
||||
default_knx.individual_address =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS);
|
||||
default_knx.programming_button_gpio = CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO;
|
||||
|
||||
@@ -686,7 +686,8 @@ CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_UDP_PORT=3671
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12"
|
||||
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=4353
|
||||
CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS=65281
|
||||
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=65534
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO=0
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_ACTIVE_LOW=y
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO=10
|
||||
@@ -1806,7 +1807,7 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
|
||||
|
||||
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
|
||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
|
||||
# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
|
||||
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
|
||||
@@ -2940,7 +2941,7 @@ CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160
|
||||
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
|
||||
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
|
||||
CONFIG_MAIN_TASK_STACK_SIZE=3584
|
||||
CONFIG_MAIN_TASK_STACK_SIZE=8192
|
||||
# CONFIG_CONSOLE_UART_DEFAULT is not set
|
||||
# CONFIG_CONSOLE_UART_CUSTOM is not set
|
||||
# CONFIG_CONSOLE_UART_NONE is not set
|
||||
|
||||
@@ -7,6 +7,8 @@ CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||
CONFIG_PARTITION_TABLE_MD5=y
|
||||
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
|
||||
@@ -697,7 +697,8 @@ CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_UDP_PORT=3671
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12"
|
||||
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=4353
|
||||
CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS=65281
|
||||
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=65534
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO=0
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_ACTIVE_LOW=y
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO=10
|
||||
|
||||
@@ -2198,11 +2198,36 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
|
||||
cJSON* knx_json = cJSON_CreateObject();
|
||||
if (knx_json != nullptr) {
|
||||
auto* endpoint_runtime = service.knx_endpoint_runtime_;
|
||||
if (endpoint_runtime == nullptr) {
|
||||
endpoint_runtime = const_cast<GatewayBridgeService&>(service).selectKnxEndpointRuntime();
|
||||
}
|
||||
bool programming_mode = false;
|
||||
bool programming_control_available = false;
|
||||
int endpoint_owner_gateway_id = -1;
|
||||
if (endpoint_runtime != nullptr) {
|
||||
LockGuard owner_guard(endpoint_runtime->lock);
|
||||
endpoint_owner_gateway_id = endpoint_runtime->channel.gateway_id;
|
||||
programming_control_available = endpoint_runtime->knx_router != nullptr &&
|
||||
endpoint_runtime->knx_router->started();
|
||||
if (programming_control_available) {
|
||||
programming_mode = endpoint_runtime->knx_router->programmingMode();
|
||||
}
|
||||
}
|
||||
const auto effective_knx = knx_config.has_value() ? knx_config : service_config.default_knx_config;
|
||||
cJSON_AddBoolToObject(knx_json, "enabled", service_config.knx_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "started", knx_started);
|
||||
cJSON_AddBoolToObject(knx_json, "routerReady", knx_router != nullptr && knx_router->started());
|
||||
cJSON_AddBoolToObject(knx_json, "programmingMode", programming_mode);
|
||||
cJSON_AddBoolToObject(knx_json, "programmingControlAvailable",
|
||||
programming_control_available);
|
||||
cJSON_AddBoolToObject(knx_json, "endpointOwner",
|
||||
endpoint_owner_gateway_id == channel.gateway_id);
|
||||
if (endpoint_owner_gateway_id >= 0) {
|
||||
cJSON_AddNumberToObject(knx_json, "endpointOwnerGatewayId",
|
||||
endpoint_owner_gateway_id);
|
||||
}
|
||||
const std::string router_error = knx_router == nullptr ? "" : knx_router->lastError();
|
||||
cJSON_AddStringToObject(knx_json, "lastError",
|
||||
knx_last_error.empty() ? router_error.c_str()
|
||||
@@ -2269,6 +2294,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
cJSON_AddNumberToObject(knx_json, "udpPort", effective_knx->udp_port);
|
||||
cJSON_AddStringToObject(knx_json, "multicastAddress",
|
||||
effective_knx->multicast_address.c_str());
|
||||
cJSON_AddNumberToObject(knx_json, "ipInterfaceIndividualAddress",
|
||||
effective_knx->ip_interface_individual_address);
|
||||
cJSON_AddNumberToObject(knx_json, "individualAddress",
|
||||
effective_knx->individual_address);
|
||||
cJSON* serial_json = cJSON_CreateObject();
|
||||
@@ -3066,6 +3093,25 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (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";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.individual_address == 0 || config.individual_address == 0xffff) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "KNX-DALI gateway individual address must be a configured address";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.ip_interface_individual_address == config.individual_address) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "KNX IP interface and KNX-DALI gateway addresses must differ";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!config.ip_router_enabled || !GatewayKnxConfigUsesTpUart(config)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -3162,10 +3208,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
GatewayKnxConfig config = service_config.default_knx_config.value();
|
||||
const uint8_t channel_index = channel.channel_index;
|
||||
config.main_group = static_cast<uint8_t>(std::min<int>(31, config.main_group + channel_index));
|
||||
const uint16_t device = config.individual_address & 0x00ff;
|
||||
const uint16_t device = config.ip_interface_individual_address & 0x00ff;
|
||||
if (device > 0 && device + channel_index <= 0x00ff) {
|
||||
config.individual_address = static_cast<uint16_t>((config.individual_address & 0xff00) |
|
||||
(device + channel_index));
|
||||
config.ip_interface_individual_address = static_cast<uint16_t>(
|
||||
(config.ip_interface_individual_address & 0xff00) | (device + channel_index));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
@@ -4415,6 +4461,40 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
}
|
||||
return handleGet("knx", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_programming_mode") {
|
||||
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (body_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "KNX programming mode JSON is required");
|
||||
}
|
||||
const cJSON* enabled_item = cJSON_GetObjectItemCaseSensitive(body_root, "enabled");
|
||||
if (!cJSON_IsBool(enabled_item)) {
|
||||
cJSON_Delete(body_root);
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "boolean enabled field is required");
|
||||
}
|
||||
const bool enabled = cJSON_IsTrue(enabled_item);
|
||||
cJSON_Delete(body_root);
|
||||
|
||||
ChannelRuntime* owner = selectKnxEndpointRuntime();
|
||||
if (owner == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_NOT_FOUND, "no KNX/IP endpoint owner is configured");
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_ERR_INVALID_STATE;
|
||||
std::string detail = "KNX/IP router is unavailable";
|
||||
{
|
||||
LockGuard guard(owner->lock);
|
||||
if (owner->knx_router != nullptr) {
|
||||
err = owner->knx_router->setProgrammingMode(enabled);
|
||||
detail = owner->knx_router->lastError();
|
||||
}
|
||||
owner->knx_last_error = err == ESP_OK ? std::string() : detail;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, detail.empty() ? "failed to change KNX programming mode"
|
||||
: detail.c_str());
|
||||
}
|
||||
return handleGet("status", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_security_read_factory_key") {
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
|
||||
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||
|
||||
@@ -63,7 +63,8 @@ struct GatewayKnxConfig {
|
||||
uint8_t main_group{0};
|
||||
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
|
||||
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
|
||||
uint16_t individual_address{0x1101};
|
||||
uint16_t ip_interface_individual_address{0xff01};
|
||||
uint16_t individual_address{0xfffe};
|
||||
int programming_button_gpio{-1};
|
||||
bool programming_button_active_low{true};
|
||||
int programming_led_gpio{-1};
|
||||
@@ -208,6 +209,9 @@ class GatewayKnxTpIpRouter {
|
||||
void setGroupWriteHandler(GroupWriteHandler handler);
|
||||
const GatewayKnxConfig& config() const;
|
||||
bool tpUartOnline() const;
|
||||
bool programmingMode();
|
||||
esp_err_t setProgrammingMode(bool enabled);
|
||||
esp_err_t toggleProgrammingMode();
|
||||
|
||||
esp_err_t start(uint32_t task_stack_size, UBaseType_t task_priority);
|
||||
esp_err_t stop();
|
||||
@@ -217,6 +221,14 @@ class GatewayKnxTpIpRouter {
|
||||
|
||||
private:
|
||||
static constexpr size_t kMaxTunnelClients = 16;
|
||||
static constexpr size_t kMaxTcpClients = 4;
|
||||
|
||||
struct TcpClient {
|
||||
int sock{-1};
|
||||
::sockaddr_in remote{};
|
||||
std::vector<uint8_t> rx_buffer;
|
||||
TickType_t last_activity_tick{0};
|
||||
};
|
||||
|
||||
struct TunnelClient {
|
||||
bool connected{false};
|
||||
@@ -225,6 +237,7 @@ class GatewayKnxTpIpRouter {
|
||||
uint8_t received_sequence{255};
|
||||
uint8_t send_sequence{0};
|
||||
uint16_t individual_address{0};
|
||||
int tcp_sock{-1};
|
||||
TickType_t last_activity_tick{0};
|
||||
::sockaddr_in control_remote{};
|
||||
::sockaddr_in data_remote{};
|
||||
@@ -237,6 +250,9 @@ class GatewayKnxTpIpRouter {
|
||||
void finishTask();
|
||||
void closeSockets();
|
||||
bool configureSocket();
|
||||
void handleTcpAccept();
|
||||
void handleTcpClient(TcpClient& client);
|
||||
void closeTcpClient(TcpClient& client);
|
||||
bool configureTpUart();
|
||||
bool initializeTpUart();
|
||||
bool configureProgrammingGpio();
|
||||
@@ -276,7 +292,12 @@ class GatewayKnxTpIpRouter {
|
||||
void sendSearchResponse(uint16_t service, const ::sockaddr_in& remote,
|
||||
const std::set<uint8_t>& requested_dibs = {});
|
||||
void sendDescriptionResponse(const ::sockaddr_in& remote);
|
||||
std::array<uint8_t, 8> localHpaiForRemote(const ::sockaddr_in& remote) const;
|
||||
bool sendPacket(const std::vector<uint8_t>& packet, const ::sockaddr_in& remote) const;
|
||||
bool sendPacketToTunnelClient(const TunnelClient& client,
|
||||
const std::vector<uint8_t>& packet) const;
|
||||
bool currentTransportAllowsTcpHpai() const;
|
||||
std::optional<std::array<uint8_t, 8>> localHpaiForRemote(const ::sockaddr_in& remote,
|
||||
bool tcp = false) const;
|
||||
std::vector<uint8_t> buildDeviceInfoDib(const ::sockaddr_in& remote) const;
|
||||
std::vector<uint8_t> buildSupportedServiceDib() const;
|
||||
std::vector<uint8_t> buildExtendedDeviceInfoDib() const;
|
||||
@@ -298,8 +319,10 @@ class GatewayKnxTpIpRouter {
|
||||
void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
|
||||
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
|
||||
bool shouldRouteDaliApplicationFrames() const;
|
||||
uint8_t advertisedMedium() const;
|
||||
void syncOpenKnxConfigFromDevice();
|
||||
uint16_t effectiveIndividualAddress() const;
|
||||
uint16_t effectiveIpInterfaceIndividualAddress() const;
|
||||
uint16_t effectiveKnxDeviceIndividualAddress() const;
|
||||
uint16_t effectiveTunnelAddress() const;
|
||||
void pollTpUart();
|
||||
void pollProgrammingButton();
|
||||
@@ -322,9 +345,12 @@ class GatewayKnxTpIpRouter {
|
||||
std::atomic_bool stop_requested_{false};
|
||||
std::atomic_bool started_{false};
|
||||
int udp_sock_{-1};
|
||||
int tcp_sock_{-1};
|
||||
int active_tcp_sock_{-1};
|
||||
int tp_uart_port_{-1};
|
||||
std::vector<uint32_t> multicast_joined_interfaces_;
|
||||
TickType_t network_refresh_tick_{0};
|
||||
std::array<TcpClient, kMaxTcpClients> tcp_clients_{};
|
||||
std::array<TunnelClient, kMaxTunnelClients> tunnel_clients_{};
|
||||
uint8_t last_tunnel_channel_id_{0};
|
||||
std::vector<uint8_t> tp_rx_frame_;
|
||||
|
||||
@@ -67,6 +67,7 @@ constexpr uint8_t kKnxConnectionTypeDeviceManagement = 0x03;
|
||||
constexpr uint8_t kKnxConnectionTypeTunnel = 0x04;
|
||||
constexpr uint8_t kKnxTunnelLayerLink = 0x02;
|
||||
constexpr uint8_t kKnxHpaiIpv4Udp = 0x01;
|
||||
constexpr uint8_t kKnxHpaiIpv4Tcp = 0x02;
|
||||
constexpr uint8_t kKnxDibDeviceInfo = 0x01;
|
||||
constexpr uint8_t kKnxDibSupportedServices = 0x02;
|
||||
constexpr uint8_t kKnxDibIpConfig = 0x03;
|
||||
@@ -81,7 +82,8 @@ constexpr uint8_t kKnxServiceFamilyDeviceManagement = 0x03;
|
||||
constexpr uint8_t kKnxServiceFamilyTunnelling = 0x04;
|
||||
constexpr uint8_t kKnxServiceFamilyRouting = 0x05;
|
||||
constexpr uint16_t kKnxManufacturerId = 0x00a4;
|
||||
constexpr uint16_t kKnxDeviceDescriptor = 0x07b0;
|
||||
constexpr uint16_t kKnxIpOnlyDeviceDescriptor = 0x57b0;
|
||||
constexpr uint16_t kKnxTpIpInterfaceDeviceDescriptor = 0x091a;
|
||||
constexpr uint8_t kKnxIpAssignmentManual = 0x01;
|
||||
constexpr uint8_t kKnxIpCapabilityManual = 0x01;
|
||||
constexpr uint8_t kTpUartResetRequest = 0x01;
|
||||
@@ -245,7 +247,7 @@ sockaddr_in EndpointFromHpaiAt(const uint8_t* body, size_t len, size_t offset,
|
||||
const sockaddr_in& fallback) {
|
||||
sockaddr_in out = fallback;
|
||||
if (body == nullptr || offset + 8 > len || body[offset] != 0x08 ||
|
||||
body[offset + 1] != kKnxHpaiIpv4Udp) {
|
||||
(body[offset + 1] != kKnxHpaiIpv4Udp && body[offset + 1] != kKnxHpaiIpv4Tcp)) {
|
||||
return out;
|
||||
}
|
||||
uint32_t address = 0;
|
||||
@@ -265,6 +267,15 @@ sockaddr_in ResponseEndpointFromHpai(const uint8_t* body, size_t len,
|
||||
return EndpointFromHpaiAt(body, len, 0, fallback);
|
||||
}
|
||||
|
||||
bool HasUnsupportedHpaiProtocolAt(const uint8_t* body, size_t len, size_t offset,
|
||||
bool allow_tcp) {
|
||||
if (body == nullptr || offset + 2 > len || body[offset] != 0x08) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t protocol = body[offset + 1];
|
||||
return protocol != kKnxHpaiIpv4Udp && !(allow_tcp && protocol == kKnxHpaiIpv4Tcp);
|
||||
}
|
||||
|
||||
void WriteBe16(uint8_t* data, uint16_t value) {
|
||||
data[0] = static_cast<uint8_t>((value >> 8) & 0xff);
|
||||
data[1] = static_cast<uint8_t>(value & 0xff);
|
||||
@@ -647,6 +658,18 @@ bool SendAll(int sock, const uint8_t* data, size_t len, const sockaddr_in& remot
|
||||
sizeof(remote)) == static_cast<int>(len);
|
||||
}
|
||||
|
||||
bool SendStream(int sock, const uint8_t* data, size_t len) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
const int written = send(sock, data + sent, len - sent, 0);
|
||||
if (written <= 0) {
|
||||
return false;
|
||||
}
|
||||
sent += static_cast<size_t>(written);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> KnxNetIpPacket(uint16_t service, const std::vector<uint8_t>& body) {
|
||||
std::vector<uint8_t> packet(6 + body.size());
|
||||
packet[0] = kKnxNetIpHeaderSize;
|
||||
@@ -833,8 +856,20 @@ std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value
|
||||
65535));
|
||||
config.multicast_address = ObjectStringAny(object, {"multicastAddress", "multicast_address"})
|
||||
.value_or(config.multicast_address);
|
||||
config.ip_interface_individual_address = static_cast<uint16_t>(std::clamp(
|
||||
ObjectIntAny(object, {"ipInterfaceIndividualAddress",
|
||||
"ip_interface_individual_address",
|
||||
"ipInterfaceAddress",
|
||||
"ip_interface_address"})
|
||||
.value_or(config.ip_interface_individual_address),
|
||||
0, 0xffff));
|
||||
config.individual_address = static_cast<uint16_t>(std::clamp(
|
||||
ObjectIntAny(object, {"individualAddress", "individual_address"})
|
||||
ObjectIntAny(object, {"individualAddress",
|
||||
"individual_address",
|
||||
"knxDaliGatewayIndividualAddress",
|
||||
"knx_dali_gateway_individual_address",
|
||||
"deviceIndividualAddress",
|
||||
"device_individual_address"})
|
||||
.value_or(config.individual_address),
|
||||
0, 0xffff));
|
||||
config.programming_button_gpio = std::clamp(
|
||||
@@ -889,6 +924,8 @@ DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config) {
|
||||
out["mainGroup"] = static_cast<int>(config.main_group);
|
||||
out["udpPort"] = static_cast<int>(config.udp_port);
|
||||
out["multicastAddress"] = config.multicast_address;
|
||||
out["ipInterfaceIndividualAddress"] =
|
||||
static_cast<int>(config.ip_interface_individual_address);
|
||||
out["individualAddress"] = static_cast<int>(config.individual_address);
|
||||
out["programmingButtonGpio"] = config.programming_button_gpio;
|
||||
out["programmingButtonActiveLow"] = config.programming_button_active_low;
|
||||
@@ -1831,6 +1868,35 @@ const GatewayKnxConfig& GatewayKnxTpIpRouter::config() const { return config_; }
|
||||
|
||||
bool GatewayKnxTpIpRouter::tpUartOnline() const { return tp_uart_online_; }
|
||||
|
||||
bool GatewayKnxTpIpRouter::programmingMode() {
|
||||
if (openknx_lock_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
return ets_device_ != nullptr && ets_device_->programmingMode();
|
||||
}
|
||||
|
||||
esp_err_t GatewayKnxTpIpRouter::setProgrammingMode(bool enabled) {
|
||||
if (openknx_lock_ == nullptr) {
|
||||
last_error_ = "KNX runtime lock is unavailable";
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
if (ets_device_ == nullptr) {
|
||||
last_error_ = "KNX OpenKNX runtime is unavailable";
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
ets_device_->setProgrammingMode(enabled);
|
||||
setProgrammingLed(enabled);
|
||||
ESP_LOGI(kTag, "KNX programming mode %s namespace=%s",
|
||||
enabled ? "enabled" : "disabled", openknx_namespace_.c_str());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayKnxTpIpRouter::toggleProgrammingMode() {
|
||||
return setProgrammingMode(!programmingMode());
|
||||
}
|
||||
|
||||
esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task_priority) {
|
||||
if (started_ || task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
@@ -1931,14 +1997,15 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
|
||||
{
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
ets_device_ = std::make_unique<openknx::EtsDeviceRuntime>(openknx_namespace_,
|
||||
config_.individual_address);
|
||||
config_.individual_address,
|
||||
effectiveTunnelAddress());
|
||||
openknx_configured_.store(ets_device_->configured());
|
||||
ESP_LOGI(kTag,
|
||||
"OpenKNX runtime namespace=%s configured=%d individual=0x%04x tunnelClient=0x%04x "
|
||||
"commissioningOnly=%d",
|
||||
"OpenKNX runtime namespace=%s configured=%d ipInterface=0x%04x "
|
||||
"device=0x%04x tunnelClient=0x%04x commissioningOnly=%d",
|
||||
openknx_namespace_.c_str(), ets_device_->configured(),
|
||||
ets_device_->individualAddress(), ets_device_->tunnelClientAddress(),
|
||||
commissioning_only_);
|
||||
effectiveIpInterfaceIndividualAddress(), ets_device_->individualAddress(),
|
||||
ets_device_->tunnelClientAddress(), commissioning_only_);
|
||||
ets_device_->setFunctionPropertyHandlers(
|
||||
[this](uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
@@ -1994,34 +2061,7 @@ void GatewayKnxTpIpRouter::taskLoop() {
|
||||
return;
|
||||
}
|
||||
std::array<uint8_t, 768> buffer{};
|
||||
while (!stop_requested_) {
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
if (network_refresh_tick_ == 0 ||
|
||||
now - network_refresh_tick_ >= pdMS_TO_TICKS(1000)) {
|
||||
refreshNetworkInterfaces(false);
|
||||
pruneStaleTunnelClients();
|
||||
network_refresh_tick_ = now;
|
||||
}
|
||||
sockaddr_in remote{};
|
||||
socklen_t remote_len = sizeof(remote);
|
||||
const int received = recvfrom(udp_sock_, buffer.data(), buffer.size(), 0,
|
||||
reinterpret_cast<sockaddr*>(&remote), &remote_len);
|
||||
if (received <= 0) {
|
||||
pollTpUart();
|
||||
{
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
if (ets_device_ != nullptr) {
|
||||
pollProgrammingButton();
|
||||
ets_device_->loop();
|
||||
updateProgrammingLed();
|
||||
}
|
||||
}
|
||||
if (!stop_requested_) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote);
|
||||
auto run_maintenance = [this]() {
|
||||
pollTpUart();
|
||||
{
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
@@ -2031,6 +2071,69 @@ void GatewayKnxTpIpRouter::taskLoop() {
|
||||
updateProgrammingLed();
|
||||
}
|
||||
}
|
||||
};
|
||||
while (!stop_requested_) {
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
if (network_refresh_tick_ == 0 ||
|
||||
now - network_refresh_tick_ >= pdMS_TO_TICKS(1000)) {
|
||||
refreshNetworkInterfaces(false);
|
||||
pruneStaleTunnelClients();
|
||||
network_refresh_tick_ = now;
|
||||
}
|
||||
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
int max_fd = -1;
|
||||
if (udp_sock_ >= 0) {
|
||||
FD_SET(udp_sock_, &read_fds);
|
||||
max_fd = std::max(max_fd, udp_sock_);
|
||||
}
|
||||
if (tcp_sock_ >= 0) {
|
||||
FD_SET(tcp_sock_, &read_fds);
|
||||
max_fd = std::max(max_fd, tcp_sock_);
|
||||
}
|
||||
for (const auto& client : tcp_clients_) {
|
||||
if (client.sock >= 0) {
|
||||
FD_SET(client.sock, &read_fds);
|
||||
max_fd = std::max(max_fd, client.sock);
|
||||
}
|
||||
}
|
||||
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 20000;
|
||||
const int selected = max_fd >= 0 ? select(max_fd + 1, &read_fds, nullptr, nullptr, &timeout)
|
||||
: 0;
|
||||
if (selected < 0) {
|
||||
ESP_LOGW(kTag, "KNXnet/IP socket select failed: errno=%d (%s)", errno,
|
||||
std::strerror(errno));
|
||||
run_maintenance();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
continue;
|
||||
}
|
||||
if (selected == 0) {
|
||||
run_maintenance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tcp_sock_ >= 0 && FD_ISSET(tcp_sock_, &read_fds)) {
|
||||
handleTcpAccept();
|
||||
}
|
||||
for (auto& client : tcp_clients_) {
|
||||
if (client.sock >= 0 && FD_ISSET(client.sock, &read_fds)) {
|
||||
handleTcpClient(client);
|
||||
}
|
||||
}
|
||||
sockaddr_in remote{};
|
||||
socklen_t remote_len = sizeof(remote);
|
||||
if (udp_sock_ >= 0 && FD_ISSET(udp_sock_, &read_fds)) {
|
||||
const int received = recvfrom(udp_sock_, buffer.data(), buffer.size(), 0,
|
||||
reinterpret_cast<sockaddr*>(&remote), &remote_len);
|
||||
if (received > 0) {
|
||||
handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote);
|
||||
}
|
||||
}
|
||||
run_maintenance();
|
||||
}
|
||||
finishTask();
|
||||
}
|
||||
@@ -2093,6 +2196,15 @@ void GatewayKnxTpIpRouter::closeSockets() {
|
||||
close(udp_sock_);
|
||||
udp_sock_ = -1;
|
||||
}
|
||||
if (tcp_sock_ >= 0) {
|
||||
shutdown(tcp_sock_, SHUT_RDWR);
|
||||
close(tcp_sock_);
|
||||
tcp_sock_ = -1;
|
||||
}
|
||||
for (auto& client : tcp_clients_) {
|
||||
closeTcpClient(client);
|
||||
}
|
||||
active_tcp_sock_ = -1;
|
||||
multicast_joined_interfaces_.clear();
|
||||
network_refresh_tick_ = 0;
|
||||
for (auto& client : tunnel_clients_) {
|
||||
@@ -2106,7 +2218,7 @@ void GatewayKnxTpIpRouter::closeSockets() {
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::configureSocket() {
|
||||
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (udp_sock_ < 0) {
|
||||
last_error_ = ErrnoDetail("failed to create KNXnet/IP UDP socket", errno);
|
||||
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
||||
@@ -2149,15 +2261,139 @@ bool GatewayKnxTpIpRouter::configureSocket() {
|
||||
}
|
||||
refreshNetworkInterfaces(true);
|
||||
}
|
||||
|
||||
tcp_sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (tcp_sock_ < 0) {
|
||||
last_error_ = ErrnoDetail("failed to create KNXnet/IP TCP socket", errno);
|
||||
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
||||
closeSockets();
|
||||
return false;
|
||||
}
|
||||
int reuse = 1;
|
||||
if (setsockopt(tcp_sock_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
|
||||
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) {
|
||||
const int saved_errno = errno;
|
||||
last_error_ = ErrnoDetail("failed to bind KNXnet/IP TCP socket on port " +
|
||||
std::to_string(config_.udp_port),
|
||||
saved_errno);
|
||||
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
||||
closeSockets();
|
||||
return false;
|
||||
}
|
||||
if (listen(tcp_sock_, static_cast<int>(kMaxTcpClients)) < 0) {
|
||||
const int saved_errno = errno;
|
||||
last_error_ = ErrnoDetail("failed to listen on KNXnet/IP TCP port " +
|
||||
std::to_string(config_.udp_port),
|
||||
saved_errno);
|
||||
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
||||
closeSockets();
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(kTag, "KNXnet/IP listening on UDP/TCP port %u",
|
||||
static_cast<unsigned>(config_.udp_port));
|
||||
return true;
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::handleTcpAccept() {
|
||||
sockaddr_in remote{};
|
||||
socklen_t remote_len = sizeof(remote);
|
||||
const int client_sock = accept(tcp_sock_, reinterpret_cast<sockaddr*>(&remote), &remote_len);
|
||||
if (client_sock < 0) {
|
||||
ESP_LOGW(kTag, "failed to accept KNXnet/IP TCP client: errno=%d (%s)", errno,
|
||||
std::strerror(errno));
|
||||
return;
|
||||
}
|
||||
TcpClient* slot = nullptr;
|
||||
for (auto& client : tcp_clients_) {
|
||||
if (client.sock < 0) {
|
||||
slot = &client;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot == nullptr) {
|
||||
ESP_LOGW(kTag, "reject KNXnet/IP TCP client from %s: no free TCP slots",
|
||||
EndpointString(remote).c_str());
|
||||
close(client_sock);
|
||||
return;
|
||||
}
|
||||
slot->sock = client_sock;
|
||||
slot->remote = remote;
|
||||
slot->rx_buffer.clear();
|
||||
slot->last_activity_tick = xTaskGetTickCount();
|
||||
ESP_LOGI(kTag, "accepted KNXnet/IP TCP client from %s", EndpointString(remote).c_str());
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::handleTcpClient(TcpClient& client) {
|
||||
if (client.sock < 0) {
|
||||
return;
|
||||
}
|
||||
std::array<uint8_t, 512> buffer{};
|
||||
const int received = recv(client.sock, buffer.data(), buffer.size(), 0);
|
||||
if (received <= 0) {
|
||||
ESP_LOGI(kTag, "closed KNXnet/IP TCP client from %s", EndpointString(client.remote).c_str());
|
||||
closeTcpClient(client);
|
||||
return;
|
||||
}
|
||||
client.last_activity_tick = xTaskGetTickCount();
|
||||
client.rx_buffer.insert(client.rx_buffer.end(), buffer.begin(), buffer.begin() + received);
|
||||
while (client.rx_buffer.size() >= 6) {
|
||||
uint16_t service = 0;
|
||||
uint16_t total_len = 0;
|
||||
if (!ParseKnxNetIpHeader(client.rx_buffer.data(), client.rx_buffer.size(), &service,
|
||||
&total_len)) {
|
||||
ESP_LOGW(kTag, "invalid KNXnet/IP TCP packet from %s; closing stream",
|
||||
EndpointString(client.remote).c_str());
|
||||
closeTcpClient(client);
|
||||
return;
|
||||
}
|
||||
if (client.rx_buffer.size() < total_len) {
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> packet(client.rx_buffer.begin(), client.rx_buffer.begin() + total_len);
|
||||
client.rx_buffer.erase(client.rx_buffer.begin(), client.rx_buffer.begin() + total_len);
|
||||
active_tcp_sock_ = client.sock;
|
||||
handleUdpDatagram(packet.data(), packet.size(), client.remote);
|
||||
active_tcp_sock_ = -1;
|
||||
if (client.sock < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::closeTcpClient(TcpClient& client) {
|
||||
if (client.sock < 0) {
|
||||
client.rx_buffer.clear();
|
||||
return;
|
||||
}
|
||||
const int sock = client.sock;
|
||||
for (auto& tunnel : tunnel_clients_) {
|
||||
if (tunnel.connected && tunnel.tcp_sock == sock) {
|
||||
resetTunnelClient(tunnel);
|
||||
}
|
||||
}
|
||||
if (active_tcp_sock_ == sock) {
|
||||
active_tcp_sock_ = -1;
|
||||
}
|
||||
shutdown(sock, SHUT_RDWR);
|
||||
close(sock);
|
||||
client.sock = -1;
|
||||
client.rx_buffer.clear();
|
||||
client.last_activity_tick = 0;
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::refreshNetworkInterfaces(bool force_log) {
|
||||
if (!config_.multicast_enabled || udp_sock_ < 0) {
|
||||
return;
|
||||
}
|
||||
const auto netifs = ActiveKnxNetifs();
|
||||
if (netifs.empty()) {
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
if (ets_device_ != nullptr) {
|
||||
ets_device_->setNetworkInterface(nullptr);
|
||||
}
|
||||
if (force_log) {
|
||||
ESP_LOGW(kTag, "KNX multicast group %s not joined yet: no IPv4 interface is up",
|
||||
config_.multicast_address.c_str());
|
||||
@@ -2165,6 +2401,13 @@ void GatewayKnxTpIpRouter::refreshNetworkInterfaces(bool force_log) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
if (ets_device_ != nullptr) {
|
||||
ets_device_->setNetworkInterface(netifs.front().netif);
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t multicast_address = inet_addr(config_.multicast_address.c_str());
|
||||
for (const auto& netif : netifs) {
|
||||
if (std::find(multicast_joined_interfaces_.begin(), multicast_joined_interfaces_.end(),
|
||||
@@ -2338,8 +2581,8 @@ bool GatewayKnxTpIpRouter::initializeTpUart() {
|
||||
saw_reset = true;
|
||||
const std::array<uint8_t, 3> set_address{
|
||||
kTpUartSetAddressRequest,
|
||||
static_cast<uint8_t>((effectiveIndividualAddress() >> 8) & 0xff),
|
||||
static_cast<uint8_t>(effectiveIndividualAddress() & 0xff),
|
||||
static_cast<uint8_t>((effectiveIpInterfaceIndividualAddress() >> 8) & 0xff),
|
||||
static_cast<uint8_t>(effectiveIpInterfaceIndividualAddress() & 0xff),
|
||||
};
|
||||
uart_write_bytes(uart_port, set_address.data(), set_address.size());
|
||||
const uint8_t state_request = kTpUartStateRequest;
|
||||
@@ -2427,6 +2670,11 @@ void GatewayKnxTpIpRouter::handleUdpDatagram(const uint8_t* data, size_t len,
|
||||
|
||||
void GatewayKnxTpIpRouter::handleSearchRequest(uint16_t service, 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",
|
||||
EndpointString(remote).c_str());
|
||||
return;
|
||||
}
|
||||
sockaddr_in response_remote = ResponseEndpointFromHpai(body, len, remote);
|
||||
selectOpenKnxNetworkInterface(response_remote);
|
||||
std::set<uint8_t> requested_dibs;
|
||||
@@ -2439,10 +2687,8 @@ void GatewayKnxTpIpRouter::handleSearchRequest(uint16_t service, const uint8_t*
|
||||
}
|
||||
const uint8_t srp_type = body[offset + 1];
|
||||
if (srp_type == 0x01) {
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
if (ets_device_ == nullptr || !ets_device_->programmingMode()) {
|
||||
return;
|
||||
}
|
||||
// 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) {
|
||||
@@ -2477,6 +2723,11 @@ void GatewayKnxTpIpRouter::handleSearchRequest(uint16_t service, const uint8_t*
|
||||
|
||||
void GatewayKnxTpIpRouter::handleDescriptionRequest(const uint8_t* body, size_t len,
|
||||
const sockaddr_in& remote) {
|
||||
if (HasUnsupportedHpaiProtocolAt(body, len, 0, currentTransportAllowsTcpHpai())) {
|
||||
ESP_LOGW(kTag, "ignore KNXnet/IP description request from %s: unsupported HPAI protocol",
|
||||
EndpointString(remote).c_str());
|
||||
return;
|
||||
}
|
||||
const sockaddr_in response_remote = ResponseEndpointFromHpai(body, len, remote);
|
||||
selectOpenKnxNetworkInterface(response_remote);
|
||||
sendDescriptionResponse(response_remote);
|
||||
@@ -2562,9 +2813,11 @@ GatewayKnxTpIpRouter::TunnelClient* GatewayKnxTpIpRouter::allocateTunnelClient(
|
||||
size_t free_index = 0;
|
||||
for (size_t index = 0; index < tunnel_clients_.size(); ++index) {
|
||||
auto& client = tunnel_clients_[index];
|
||||
const bool same_tcp_stream = active_tcp_sock_ >= 0 && client.tcp_sock == active_tcp_sock_;
|
||||
const bool same_udp_endpoints = EndpointEquals(client.control_remote, control_remote) &&
|
||||
EndpointEquals(client.data_remote, data_remote);
|
||||
if (client.connected && client.connection_type == connection_type &&
|
||||
EndpointEquals(client.control_remote, control_remote) &&
|
||||
EndpointEquals(client.data_remote, data_remote)) {
|
||||
(same_tcp_stream || same_udp_endpoints)) {
|
||||
ESP_LOGW(kTag, "replacing existing KNXnet/IP tunnel channel=%u for endpoint %s",
|
||||
static_cast<unsigned>(client.channel_id), EndpointString(data_remote).c_str());
|
||||
resetTunnelClient(client);
|
||||
@@ -2593,6 +2846,7 @@ GatewayKnxTpIpRouter::TunnelClient* GatewayKnxTpIpRouter::allocateTunnelClient(
|
||||
free_client->last_activity_tick = xTaskGetTickCount();
|
||||
free_client->control_remote = control_remote;
|
||||
free_client->data_remote = data_remote;
|
||||
free_client->tcp_sock = active_tcp_sock_;
|
||||
last_tunnel_channel_id_ = channel_id;
|
||||
return free_client;
|
||||
}
|
||||
@@ -2629,7 +2883,8 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* body, size_t l
|
||||
sendTunnellingAck(channel_id, sequence, kKnxErrorConnectionId, remote);
|
||||
return;
|
||||
}
|
||||
if (!EndpointEquals(remote, client->data_remote)) {
|
||||
const bool same_tcp_stream = client->tcp_sock >= 0 && active_tcp_sock_ == client->tcp_sock;
|
||||
if (!same_tcp_stream && !EndpointEquals(remote, client->data_remote)) {
|
||||
ESP_LOGW(kTag, "reject KNXnet/IP tunnelling request channel=%u seq=%u from %s: expected data endpoint %s",
|
||||
static_cast<unsigned>(channel_id), static_cast<unsigned>(sequence),
|
||||
EndpointString(remote).c_str(), EndpointString(client->data_remote).c_str());
|
||||
@@ -2693,7 +2948,8 @@ void GatewayKnxTpIpRouter::handleDeviceConfigurationRequest(const uint8_t* body,
|
||||
sendDeviceConfigurationAck(channel_id, sequence, kKnxErrorConnectionId, remote);
|
||||
return;
|
||||
}
|
||||
if (!EndpointEquals(remote, client->data_remote)) {
|
||||
const bool same_tcp_stream = client->tcp_sock >= 0 && active_tcp_sock_ == client->tcp_sock;
|
||||
if (!same_tcp_stream && !EndpointEquals(remote, client->data_remote)) {
|
||||
ESP_LOGW(kTag, "reject KNXnet/IP device-configuration request channel=%u seq=%u from %s: expected data endpoint %s",
|
||||
static_cast<unsigned>(channel_id), static_cast<unsigned>(sequence),
|
||||
EndpointString(remote).c_str(), EndpointString(client->data_remote).c_str());
|
||||
@@ -2720,6 +2976,13 @@ void GatewayKnxTpIpRouter::handleConnectRequest(const uint8_t* body, size_t len,
|
||||
EndpointString(remote).c_str(), static_cast<unsigned>(len));
|
||||
return;
|
||||
}
|
||||
if (HasUnsupportedHpaiProtocolAt(body, len, 0, currentTransportAllowsTcpHpai()) ||
|
||||
HasUnsupportedHpaiProtocolAt(body, len, 8, currentTransportAllowsTcpHpai())) {
|
||||
ESP_LOGW(kTag, "reject KNXnet/IP connect from %s: unsupported HPAI protocol",
|
||||
EndpointString(remote).c_str());
|
||||
sendConnectResponse(0, kKnxErrorConnectionType, remote, kKnxConnectionTypeTunnel, 0);
|
||||
return;
|
||||
}
|
||||
sockaddr_in control_remote = EndpointFromHpaiAt(body, len, 0, remote);
|
||||
sockaddr_in data_remote = EndpointFromHpaiAt(body, len, 8, remote);
|
||||
selectOpenKnxNetworkInterface(control_remote);
|
||||
@@ -2748,6 +3011,12 @@ void GatewayKnxTpIpRouter::handleConnectRequest(const uint8_t* body, size_t len,
|
||||
sendConnectResponse(0, kKnxErrorTunnellingLayer, control_remote, connection_type, 0);
|
||||
return;
|
||||
}
|
||||
if (!SelectKnxNetifForRemote(control_remote).has_value()) {
|
||||
ESP_LOGW(kTag, "reject KNXnet/IP connect from %s: no active IPv4 interface for response",
|
||||
EndpointString(remote).c_str());
|
||||
sendConnectResponse(0, kKnxErrorConnectionType, control_remote, connection_type, 0);
|
||||
return;
|
||||
}
|
||||
TunnelClient* client = allocateTunnelClient(control_remote, data_remote, connection_type);
|
||||
if (client == nullptr) {
|
||||
ESP_LOGW(kTag, "reject KNXnet/IP connect from %s: no free tunnel client slots",
|
||||
@@ -2775,11 +3044,18 @@ void GatewayKnxTpIpRouter::handleConnectionStateRequest(const uint8_t* body, siz
|
||||
if (body == nullptr || len < 2) {
|
||||
return;
|
||||
}
|
||||
if (HasUnsupportedHpaiProtocolAt(body, len, 2, currentTransportAllowsTcpHpai())) {
|
||||
ESP_LOGW(kTag,
|
||||
"reject KNXnet/IP connection-state request from %s: unsupported HPAI protocol",
|
||||
EndpointString(remote).c_str());
|
||||
return;
|
||||
}
|
||||
const uint8_t channel_id = body[0];
|
||||
const sockaddr_in control_remote = EndpointFromHpaiAt(body, len, 2, remote);
|
||||
TunnelClient* client = findTunnelClient(channel_id);
|
||||
const bool endpoint_matches = client != nullptr &&
|
||||
EndpointEquals(control_remote, client->control_remote);
|
||||
((client->tcp_sock >= 0 && active_tcp_sock_ == client->tcp_sock) ||
|
||||
EndpointEquals(control_remote, client->control_remote));
|
||||
const uint8_t status = endpoint_matches ? kKnxNoError : kKnxErrorConnectionId;
|
||||
if (client != nullptr) {
|
||||
if (endpoint_matches) {
|
||||
@@ -2799,19 +3075,26 @@ void GatewayKnxTpIpRouter::handleDisconnectRequest(const uint8_t* body, size_t l
|
||||
if (body == nullptr || len < 2) {
|
||||
return;
|
||||
}
|
||||
if (HasUnsupportedHpaiProtocolAt(body, len, 2, currentTransportAllowsTcpHpai())) {
|
||||
ESP_LOGW(kTag, "reject KNXnet/IP disconnect request from %s: unsupported HPAI protocol",
|
||||
EndpointString(remote).c_str());
|
||||
return;
|
||||
}
|
||||
const uint8_t channel_id = body[0];
|
||||
const sockaddr_in control_remote = EndpointFromHpaiAt(body, len, 2, remote);
|
||||
TunnelClient* client = findTunnelClient(channel_id);
|
||||
const bool endpoint_matches = client != nullptr &&
|
||||
EndpointEquals(control_remote, client->control_remote);
|
||||
((client->tcp_sock >= 0 && active_tcp_sock_ == client->tcp_sock) ||
|
||||
EndpointEquals(control_remote, client->control_remote));
|
||||
const uint8_t status = endpoint_matches ? kKnxNoError : kKnxErrorConnectionId;
|
||||
const std::string expected = client == nullptr ? "none" : EndpointString(client->control_remote);
|
||||
if (status == kKnxNoError) {
|
||||
resetTunnelClient(*client);
|
||||
}
|
||||
ESP_LOGI(kTag, "rx KNXnet/IP disconnect request channel=%u status=0x%02x from %s ctrl=%s expected=%s",
|
||||
static_cast<unsigned>(channel_id), static_cast<unsigned>(status),
|
||||
EndpointString(remote).c_str(), EndpointString(control_remote).c_str(),
|
||||
client == nullptr ? "none" : EndpointString(client->control_remote).c_str());
|
||||
expected.c_str());
|
||||
sendDisconnectResponse(channel_id, status, control_remote);
|
||||
}
|
||||
|
||||
@@ -2859,22 +3142,51 @@ void GatewayKnxTpIpRouter::sendConnectionHeaderAck(uint16_t service, uint8_t cha
|
||||
const sockaddr_in& remote) {
|
||||
const std::vector<uint8_t> body{0x04, channel_id, sequence, status};
|
||||
const auto packet = KnxNetIpPacket(service, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
sendPacket(packet, remote);
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::sendSecureSessionStatus(uint8_t status, const sockaddr_in& remote) {
|
||||
const std::vector<uint8_t> body{status, 0x00};
|
||||
const auto packet = KnxNetIpPacket(kServiceSecureSessionStatus, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
sendPacket(packet, remote);
|
||||
}
|
||||
|
||||
std::array<uint8_t, 8> GatewayKnxTpIpRouter::localHpaiForRemote(
|
||||
const sockaddr_in& remote) const {
|
||||
bool GatewayKnxTpIpRouter::sendPacket(const std::vector<uint8_t>& packet,
|
||||
const sockaddr_in& remote) const {
|
||||
if (packet.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (active_tcp_sock_ >= 0) {
|
||||
return SendStream(active_tcp_sock_, packet.data(), packet.size());
|
||||
}
|
||||
return udp_sock_ >= 0 && SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::sendPacketToTunnelClient(
|
||||
const TunnelClient& client, const std::vector<uint8_t>& packet) const {
|
||||
if (packet.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (client.tcp_sock >= 0) {
|
||||
return SendStream(client.tcp_sock, packet.data(), packet.size());
|
||||
}
|
||||
return udp_sock_ >= 0 && SendAll(udp_sock_, packet.data(), packet.size(), client.data_remote);
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::currentTransportAllowsTcpHpai() const {
|
||||
return active_tcp_sock_ >= 0;
|
||||
}
|
||||
|
||||
std::optional<std::array<uint8_t, 8>> GatewayKnxTpIpRouter::localHpaiForRemote(
|
||||
const sockaddr_in& remote, bool tcp) const {
|
||||
const auto netif = SelectKnxNetifForRemote(remote);
|
||||
if (!netif.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::array<uint8_t, 8> hpai{};
|
||||
hpai[0] = 0x08;
|
||||
hpai[1] = kKnxHpaiIpv4Udp;
|
||||
const auto netif = SelectKnxNetifForRemote(remote);
|
||||
WriteIp(hpai.data() + 2, netif.has_value() ? netif->address : htonl(INADDR_ANY));
|
||||
hpai[1] = tcp ? kKnxHpaiIpv4Tcp : kKnxHpaiIpv4Udp;
|
||||
WriteIp(hpai.data() + 2, netif->address);
|
||||
WriteBe16(hpai.data() + 6, config_.udp_port);
|
||||
return hpai;
|
||||
}
|
||||
@@ -2884,12 +3196,9 @@ std::vector<uint8_t> GatewayKnxTpIpRouter::buildDeviceInfoDib(
|
||||
std::vector<uint8_t> dib(54, 0);
|
||||
dib[0] = static_cast<uint8_t>(dib.size());
|
||||
dib[1] = kKnxDibDeviceInfo;
|
||||
dib[2] = GatewayKnxConfigUsesTpUart(config_) ? kKnxMediumTp1 : kKnxMediumIp;
|
||||
{
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
dib[3] = ets_device_ != nullptr && ets_device_->programmingMode() ? 1 : 0;
|
||||
}
|
||||
WriteBe16(dib.data() + 4, effectiveIndividualAddress());
|
||||
dib[2] = advertisedMedium();
|
||||
dib[3] = 0;
|
||||
WriteBe16(dib.data() + 4, effectiveIpInterfaceIndividualAddress());
|
||||
WriteBe16(dib.data() + 6, 0);
|
||||
|
||||
uint8_t mac[6]{};
|
||||
@@ -2916,7 +3225,9 @@ std::vector<uint8_t> GatewayKnxTpIpRouter::buildExtendedDeviceInfoDib() const {
|
||||
dib[2] = 0x01;
|
||||
dib[3] = 0x00;
|
||||
WriteBe16(dib.data() + 4, 254);
|
||||
WriteBe16(dib.data() + 6, kKnxDeviceDescriptor);
|
||||
WriteBe16(dib.data() + 6,
|
||||
advertisedMedium() == kKnxMediumIp ? kKnxIpOnlyDeviceDescriptor
|
||||
: kKnxTpIpInterfaceDeviceDescriptor);
|
||||
return dib;
|
||||
}
|
||||
|
||||
@@ -2947,7 +3258,7 @@ std::vector<uint8_t> GatewayKnxTpIpRouter::buildKnxAddressesDib() const {
|
||||
std::vector<uint8_t> dib(4 + kMaxTunnelClients * 2U, 0);
|
||||
dib[0] = static_cast<uint8_t>(dib.size());
|
||||
dib[1] = kKnxDibKnxAddresses;
|
||||
WriteBe16(dib.data() + 2, effectiveIndividualAddress());
|
||||
WriteBe16(dib.data() + 2, effectiveIpInterfaceIndividualAddress());
|
||||
size_t offset = 4;
|
||||
for (size_t slot = 0; slot < kMaxTunnelClients; ++slot) {
|
||||
WriteBe16(dib.data() + offset, effectiveTunnelAddressForSlot(slot));
|
||||
@@ -3007,12 +3318,17 @@ std::vector<uint8_t> GatewayKnxTpIpRouter::buildSupportedServiceDib() const {
|
||||
|
||||
void GatewayKnxTpIpRouter::sendSearchResponse(uint16_t service, const sockaddr_in& remote,
|
||||
const std::set<uint8_t>& requested_dibs) {
|
||||
if (udp_sock_ < 0) {
|
||||
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;
|
||||
}
|
||||
const auto hpai = localHpaiForRemote(remote);
|
||||
std::vector<uint8_t> body;
|
||||
body.insert(body.end(), hpai.begin(), hpai.end());
|
||||
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) {
|
||||
@@ -3055,17 +3371,17 @@ void GatewayKnxTpIpRouter::sendSearchResponse(uint16_t service, const sockaddr_i
|
||||
}
|
||||
}
|
||||
const auto packet = KnxNetIpPacket(service, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
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>((*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) {
|
||||
if (udp_sock_ < 0 && active_tcp_sock_ < 0) {
|
||||
return;
|
||||
}
|
||||
auto device = buildDeviceInfoDib(remote);
|
||||
@@ -3075,14 +3391,15 @@ void GatewayKnxTpIpRouter::sendDescriptionResponse(const sockaddr_in& remote) {
|
||||
body.insert(body.end(), device.begin(), device.end());
|
||||
body.insert(body.end(), services.begin(), services.end());
|
||||
const auto packet = KnxNetIpPacket(kServiceDescriptionResponse, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
ESP_LOGI(kTag, "sent KNXnet/IP description response namespace=%s to %s:%u",
|
||||
openknx_namespace_.c_str(), Ipv4String(remote.sin_addr.s_addr).c_str(),
|
||||
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 (udp_sock_ < 0 || data == nullptr || len == 0) {
|
||||
if (data == nullptr || len == 0) {
|
||||
return;
|
||||
}
|
||||
for (auto& client : tunnel_clients_) {
|
||||
@@ -3094,7 +3411,7 @@ void GatewayKnxTpIpRouter::sendTunnelIndication(const uint8_t* data, size_t len)
|
||||
|
||||
void GatewayKnxTpIpRouter::sendTunnelIndicationToClient(TunnelClient& client, const uint8_t* data,
|
||||
size_t len) {
|
||||
if (!client.connected || udp_sock_ < 0 || data == nullptr || len == 0) {
|
||||
if (!client.connected || data == nullptr || len == 0) {
|
||||
return;
|
||||
}
|
||||
const uint16_t service = TunnelServiceForCemi(data, len);
|
||||
@@ -3106,7 +3423,7 @@ void GatewayKnxTpIpRouter::sendTunnelIndicationToClient(TunnelClient& client, co
|
||||
body.push_back(0x00);
|
||||
body.insert(body.end(), data, data + len);
|
||||
const auto packet = KnxNetIpPacket(service, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), client.data_remote);
|
||||
sendPacketToTunnelClient(client, 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>(body[2]), static_cast<unsigned>(data[0]),
|
||||
@@ -3117,14 +3434,14 @@ void GatewayKnxTpIpRouter::sendConnectionStateResponse(uint8_t channel_id, uint8
|
||||
const sockaddr_in& remote) {
|
||||
const std::vector<uint8_t> body{channel_id, status};
|
||||
const auto packet = KnxNetIpPacket(kServiceConnectionStateResponse, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
sendPacket(packet, remote);
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::sendDisconnectResponse(uint8_t channel_id, uint8_t status,
|
||||
const sockaddr_in& remote) {
|
||||
const std::vector<uint8_t> body{channel_id, status};
|
||||
const auto packet = KnxNetIpPacket(kServiceDisconnectResponse, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
sendPacket(packet, remote);
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::sendConnectResponse(uint8_t channel_id, uint8_t status,
|
||||
@@ -3137,14 +3454,22 @@ void GatewayKnxTpIpRouter::sendConnectResponse(uint8_t channel_id, uint8_t statu
|
||||
body.push_back(status);
|
||||
if (status != kKnxNoError) {
|
||||
const auto packet = KnxNetIpPacket(kServiceConnectResponse, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
sendPacket(packet, remote);
|
||||
ESP_LOGI(kTag, "sent KNXnet/IP connect error channel=%u status=0x%02x to %s",
|
||||
static_cast<unsigned>(channel_id), static_cast<unsigned>(status),
|
||||
EndpointString(remote).c_str());
|
||||
return;
|
||||
}
|
||||
const auto data_endpoint = localHpaiForRemote(remote);
|
||||
body.insert(body.end(), data_endpoint.begin(), data_endpoint.end());
|
||||
const auto data_endpoint = localHpaiForRemote(remote, currentTransportAllowsTcpHpai());
|
||||
if (!data_endpoint.has_value()) {
|
||||
ESP_LOGW(kTag, "cannot accept KNXnet/IP connect from %s: no active IPv4 interface",
|
||||
EndpointString(remote).c_str());
|
||||
const auto packet = KnxNetIpPacket(kServiceConnectResponse,
|
||||
std::vector<uint8_t>{channel_id, kKnxErrorConnectionType});
|
||||
sendPacket(packet, remote);
|
||||
return;
|
||||
}
|
||||
body.insert(body.end(), data_endpoint->begin(), data_endpoint->end());
|
||||
body.push_back(connection_type == kKnxConnectionTypeTunnel ? 0x04 : 0x02);
|
||||
body.push_back(connection_type);
|
||||
if (connection_type == kKnxConnectionTypeTunnel) {
|
||||
@@ -3152,12 +3477,12 @@ void GatewayKnxTpIpRouter::sendConnectResponse(uint8_t channel_id, uint8_t statu
|
||||
body.push_back(static_cast<uint8_t>(tunnel_address & 0xff));
|
||||
}
|
||||
const auto packet = KnxNetIpPacket(kServiceConnectResponse, body);
|
||||
SendAll(udp_sock_, packet.data(), packet.size(), remote);
|
||||
sendPacket(packet, remote);
|
||||
ESP_LOGI(kTag, "sent KNXnet/IP connect response channel=%u type=0x%02x to %s endpoint=%u.%u.%u.%u:%u",
|
||||
static_cast<unsigned>(channel_id), static_cast<unsigned>(connection_type),
|
||||
EndpointString(remote).c_str(), static_cast<unsigned>(data_endpoint[2]),
|
||||
static_cast<unsigned>(data_endpoint[3]), static_cast<unsigned>(data_endpoint[4]),
|
||||
static_cast<unsigned>(data_endpoint[5]), static_cast<unsigned>(config_.udp_port));
|
||||
EndpointString(remote).c_str(), static_cast<unsigned>((*data_endpoint)[2]),
|
||||
static_cast<unsigned>((*data_endpoint)[3]), static_cast<unsigned>((*data_endpoint)[4]),
|
||||
static_cast<unsigned>((*data_endpoint)[5]), static_cast<unsigned>(config_.udp_port));
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::sendRoutingIndication(const uint8_t* data, size_t len) {
|
||||
@@ -3247,6 +3572,10 @@ bool GatewayKnxTpIpRouter::shouldRouteDaliApplicationFrames() const {
|
||||
return openknx_configured_.load();
|
||||
}
|
||||
|
||||
uint8_t GatewayKnxTpIpRouter::advertisedMedium() const {
|
||||
return (config_.tunnel_enabled || tp_uart_online_) ? kKnxMediumTp1 : kKnxMediumIp;
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
|
||||
if (ets_device_ == nullptr) {
|
||||
return;
|
||||
@@ -3285,7 +3614,15 @@ void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
|
||||
bridge_.setConfig(config_);
|
||||
}
|
||||
|
||||
uint16_t GatewayKnxTpIpRouter::effectiveIndividualAddress() const {
|
||||
uint16_t GatewayKnxTpIpRouter::effectiveIpInterfaceIndividualAddress() const {
|
||||
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) {
|
||||
@@ -3296,17 +3633,16 @@ uint16_t GatewayKnxTpIpRouter::effectiveIndividualAddress() const {
|
||||
}
|
||||
|
||||
uint16_t GatewayKnxTpIpRouter::effectiveTunnelAddress() const {
|
||||
if (ets_device_ != nullptr) {
|
||||
const uint16_t address = ets_device_->tunnelClientAddress();
|
||||
if (address != 0 && address != 0xffff) {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
uint16_t device = static_cast<uint16_t>((config_.individual_address & 0x00ff) + 1);
|
||||
const uint16_t interface_address = effectiveIpInterfaceIndividualAddress();
|
||||
uint16_t device = static_cast<uint16_t>((interface_address & 0x00ff) + 1);
|
||||
if (device == 0 || device > 0xff) {
|
||||
device = 1;
|
||||
}
|
||||
return static_cast<uint16_t>((config_.individual_address & 0xff00) | device);
|
||||
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;
|
||||
}
|
||||
|
||||
void GatewayKnxTpIpRouter::pollTpUart() {
|
||||
|
||||
@@ -23,7 +23,9 @@ class EtsDeviceRuntime {
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response)>;
|
||||
|
||||
EtsDeviceRuntime(std::string nvs_namespace, uint16_t fallback_individual_address);
|
||||
EtsDeviceRuntime(std::string nvs_namespace,
|
||||
uint16_t fallback_individual_address,
|
||||
uint16_t tunnel_client_address = 0);
|
||||
~EtsDeviceRuntime();
|
||||
|
||||
uint16_t individualAddress() const;
|
||||
|
||||
@@ -146,7 +146,7 @@ void EspIdfPlatform::fatalError() {
|
||||
|
||||
void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||
closeMultiCast();
|
||||
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (udp_sock_ < 0) {
|
||||
ESP_LOGE(kTag, "failed to create UDP socket: errno=%d", errno);
|
||||
return;
|
||||
@@ -157,7 +157,8 @@ void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||
|
||||
sockaddr_in bind_addr{};
|
||||
bind_addr.sin_family = AF_INET;
|
||||
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
const uint32_t local_address = currentIpAddress();
|
||||
bind_addr.sin_addr.s_addr = local_address == 0 ? htonl(INADDR_ANY) : local_address;
|
||||
bind_addr.sin_port = htons(port);
|
||||
if (bind(udp_sock_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
|
||||
ESP_LOGE(kTag, "failed to bind UDP socket: errno=%d", errno);
|
||||
@@ -172,11 +173,20 @@ void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||
|
||||
ip_mreq mreq{};
|
||||
mreq.imr_multiaddr.s_addr = htonl(addr);
|
||||
mreq.imr_interface.s_addr = currentIpAddress();
|
||||
mreq.imr_interface.s_addr = local_address == 0 ? htonl(INADDR_ANY) : local_address;
|
||||
if (setsockopt(udp_sock_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
|
||||
ESP_LOGW(kTag, "failed to join KNX multicast group: errno=%d", errno);
|
||||
}
|
||||
|
||||
if (local_address != 0) {
|
||||
in_addr multicast_interface{};
|
||||
multicast_interface.s_addr = local_address;
|
||||
if (setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_IF, &multicast_interface,
|
||||
sizeof(multicast_interface)) < 0) {
|
||||
ESP_LOGW(kTag, "failed to select KNX multicast interface: errno=%d", errno);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t loop = 0;
|
||||
setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
} // namespace
|
||||
|
||||
EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
||||
uint16_t fallback_individual_address)
|
||||
uint16_t fallback_individual_address,
|
||||
uint16_t tunnel_client_address)
|
||||
: nvs_namespace_(std::move(nvs_namespace)),
|
||||
platform_(nullptr, nvs_namespace_.c_str()),
|
||||
device_(platform_) {
|
||||
@@ -75,7 +76,10 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
if (auto* server = device_.getCemiServer()) {
|
||||
server->clientAddress(DefaultTunnelClientAddress(device_.deviceObject().individualAddress()));
|
||||
server->clientAddress(IsUsableIndividualAddress(tunnel_client_address)
|
||||
? tunnel_client_address
|
||||
: DefaultTunnelClientAddress(
|
||||
device_.deviceObject().individualAddress()));
|
||||
server->tunnelFrameCallback(&EtsDeviceRuntime::EmitTunnelFrame, this);
|
||||
}
|
||||
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
|
||||
|
||||
Reference in New Issue
Block a user