#pragma once #include "bridge.hpp" #include "model_value.hpp" #include "knx/ip_parameter_object.h" #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "lwip/sockets.h" #include #include #include #include #include #include #include #include #include #include #include namespace gateway { namespace openknx { class EtsDeviceRuntime; class OamRouterRuntime; class TpuartUartInterface; } constexpr uint16_t kGatewayKnxDefaultUdpPort = 3671; constexpr const char* kGatewayKnxDefaultMulticastAddress = "224.0.23.12"; constexpr uint32_t kGatewayKnxDefaultTpBaudrate = 19200; constexpr uint32_t kGatewayKnxDefaultTpStartupTimeoutMs = 2000; struct GatewayKnxTpUartConfig { int uart_port{1}; int tx_pin{-1}; int rx_pin{-1}; uint32_t baudrate{kGatewayKnxDefaultTpBaudrate}; size_t rx_buffer_size{1024}; size_t tx_buffer_size{1024}; uint32_t startup_timeout_ms{kGatewayKnxDefaultTpStartupTimeoutMs}; uint32_t read_timeout_ms{20}; bool nine_bit_mode{true}; }; enum class GatewayKnxMappingMode : uint8_t { kFormula = 0, kGwReg1Direct = 1, kManual = 2, kEtsDatabase = 3, }; struct GatewayKnxEtsAssociation { uint16_t group_address{0}; uint16_t group_object_number{0}; }; struct GatewayKnxCloudRemoteConfig { bool enabled{false}; std::string mode{"mqtt"}; std::string relay_endpoint; std::string mqtt_topic_prefix; std::string auth_token_ref; bool require_secure_tunnel{true}; bool udp_punch_enabled{false}; }; struct GatewayKnxOamRouterConfig { bool enabled{false}; bool ets_database_enabled{true}; bool secure_tunnel_enabled{true}; bool secure_routing_enabled{true}; uint16_t individual_address{0xff02}; uint16_t tunnel_address_base{0xff10}; int programming_button_gpio{-1}; bool programming_button_active_low{true}; int programming_led_gpio{-1}; bool programming_led_active_high{true}; GatewayKnxCloudRemoteConfig cloud_remote; }; struct GatewayKnxIpSecureCredentialMaterial { bool activated{false}; bool backbone_key_available{false}; std::array backbone_key{}; bool device_authentication_key_available{false}; std::array device_authentication_key{}; std::vector> tunnel_user_keys; uint64_t routing_sequence{0}; }; struct GatewayKnxConfig { bool dali_router_enabled{true}; bool ip_router_enabled{false}; bool tunnel_enabled{true}; bool multicast_enabled{true}; bool ets_database_enabled{true}; GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula}; uint8_t main_group{0}; uint8_t dali_bus_id{0}; uint16_t udp_port{kGatewayKnxDefaultUdpPort}; std::string multicast_address{kGatewayKnxDefaultMulticastAddress}; 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}; bool programming_led_active_high{true}; GatewayKnxOamRouterConfig oam_router; std::vector ets_associations; GatewayKnxTpUartConfig tp_uart; }; enum class GatewayKnxDaliDataType : uint8_t { kUnknown = 0, kSwitch = 1, kBrightness = 2, kColorTemperature = 3, kRgb = 4, kBrightnessRelative = 5, kScene = 6, }; enum class GatewayKnxDaliTargetKind : uint8_t { kNone = 0, kBroadcast = 1, kShortAddress = 2, kGroup = 3, }; struct GatewayKnxDaliTarget { GatewayKnxDaliTargetKind kind{GatewayKnxDaliTargetKind::kNone}; int address{-1}; }; struct GatewayKnxDaliBinding { uint16_t group_address{0}; uint8_t main_group{0}; uint8_t middle_group{0}; uint8_t sub_group{0}; GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula}; int group_object_number{-1}; int channel_index{-1}; std::string address; std::string name; std::string object_role; std::string datapoint_type; GatewayKnxDaliDataType data_type{GatewayKnxDaliDataType::kUnknown}; GatewayKnxDaliTarget target; }; struct GatewayKnxCommissioningBallast { uint8_t high{0}; uint8_t middle{0}; uint8_t low{0}; uint8_t short_address{0xff}; }; struct GatewayKnxReg1ScanOptions { bool only_new{false}; bool randomize{false}; bool delete_all{false}; bool assign{false}; }; std::optional GatewayKnxConfigFromValue(const DaliValue* value); DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config); std::optional GatewayKnxOamRouterConfigFromValue( const DaliValue* value); DaliValue GatewayKnxOamRouterConfigToValue(const GatewayKnxOamRouterConfig& config); bool GatewayKnxConfigUsesTpUart(const GatewayKnxConfig& config); const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode); GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value); const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type); const char* GatewayKnxTargetKindToString(GatewayKnxDaliTargetKind kind); std::optional GatewayKnxDaliDataTypeForMiddleGroup( uint8_t middle_group); std::optional GatewayKnxDaliTargetForSubgroup(uint8_t sub_group); uint16_t GatewayKnxGroupAddress(uint8_t main_group, uint8_t middle_group, uint8_t sub_group); std::string GatewayKnxGroupAddressString(uint16_t group_address); class GatewayKnxBridge { public: explicit GatewayKnxBridge(DaliBridgeEngine& engine); ~GatewayKnxBridge(); void setConfig(const GatewayKnxConfig& config); void setRuntimeContext(const openknx::EtsDeviceRuntime* runtime); const GatewayKnxConfig& config() const; size_t etsBindingCount() const; std::vector describeDaliBindings() const; bool matchesGroupAddress(uint16_t group_address) const; DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data, size_t len); DaliBridgeResult handleGroupObjectWrite(uint16_t group_object_number, const uint8_t* data, size_t len); bool handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len, std::vector* response); bool handleFunctionPropertyState(uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len, std::vector* response); private: DaliBridgeResult executeForDecodedWrite(uint16_t group_address, GatewayKnxDaliDataType data_type, GatewayKnxDaliTarget target, const uint8_t* data, size_t len); DaliBridgeResult executeReg1SceneWrite(uint16_t group_address, const uint8_t* data, size_t len); DaliBridgeResult executeEtsBindings(uint16_t group_address, const std::vector& bindings, const uint8_t* data, size_t len); void rebuildEtsBindings(); bool handleReg1TypeCommand(const uint8_t* data, size_t len, std::vector* response); bool handleReg1ScanCommand(const uint8_t* data, size_t len, std::vector* response); bool handleReg1AssignCommand(const uint8_t* data, size_t len, std::vector* response); bool handleReg1EvgWriteCommand(const uint8_t* data, size_t len, std::vector* response); bool handleReg1EvgReadCommand(const uint8_t* data, size_t len, std::vector* response); bool handleReg1SetSceneCommand(const uint8_t* data, size_t len, std::vector* response); bool handleReg1GetSceneCommand(const uint8_t* data, size_t len, std::vector* response); bool handleReg1IdentifyCommand(const uint8_t* data, size_t len, std::vector* response); bool handleReg1ScanState(const uint8_t* data, size_t len, std::vector* response); bool handleReg1AssignState(const uint8_t* data, size_t len, std::vector* response); bool handleReg1FoundEvgsState(const uint8_t* data, size_t len, std::vector* response); static void CommissioningScanTaskEntry(void* arg); void runCommissioningScanTask(); DaliBridgeEngine& engine_; GatewayKnxConfig config_; const openknx::EtsDeviceRuntime* runtime_{nullptr}; std::map> ets_bindings_by_group_address_; SemaphoreHandle_t commissioning_lock_{nullptr}; TaskHandle_t commissioning_scan_task_{nullptr}; std::atomic_bool commissioning_scan_cancel_requested_{false}; GatewayKnxReg1ScanOptions commissioning_scan_options_; bool commissioning_scan_done_{true}; bool commissioning_assign_done_{true}; std::vector commissioning_found_ballasts_; }; class GatewayKnxTpIpRouter { public: using GroupWriteHandler = std::function; using GroupObjectWriteHandler = std::function; using RoutingSequenceStoreHandler = std::function; using CloudCemiPublisher = std::function; struct CloudCemiStats { bool enabled{false}; uint64_t uplink_frames{0}; uint64_t downlink_frames{0}; }; GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, std::string openknx_namespace = "openknx"); ~GatewayKnxTpIpRouter(); void setConfig(const GatewayKnxConfig& config); void setCommissioningOnly(bool enabled); void setGroupWriteHandler(GroupWriteHandler handler); void setGroupObjectWriteHandler(GroupObjectWriteHandler handler); void setOamIpSecureCredentials(const GatewayKnxIpSecureCredentialMaterial& credentials); void setOamIpSecureRoutingSequenceStoreHandler(RoutingSequenceStoreHandler handler); void setCloudCemiPublisher(CloudCemiPublisher publisher); const GatewayKnxConfig& config() const; bool injectCloudCemiFrame(const uint8_t* data, size_t len); CloudCemiStats cloudCemiStats() const; bool tpUartOnline() const; bool programmingMode(); esp_err_t setProgrammingMode(bool enabled); esp_err_t toggleProgrammingMode(); bool oamProgrammingMode(); esp_err_t setOamProgrammingMode(bool enabled); esp_err_t toggleOamProgrammingMode(); esp_err_t start(uint32_t task_stack_size, UBaseType_t task_priority); esp_err_t stop(); bool started() const; const std::string& lastError() const; bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level); private: bool handleFunctionPropertyExtCommand(uint16_t object_type, uint8_t object_instance, uint8_t property_id, const uint8_t* data, size_t len, std::vector* response); bool handleFunctionPropertyExtState(uint16_t object_type, uint8_t object_instance, uint8_t property_id, const uint8_t* data, size_t len, std::vector* response); static constexpr size_t kMaxTunnelClients = 16; static constexpr size_t kMaxTcpClients = 4; struct TcpClient { int sock{-1}; ::sockaddr_in remote{}; std::vector rx_buffer; TickType_t last_activity_tick{0}; }; struct TunnelClient { bool connected{false}; uint8_t channel_id{0}; uint8_t connection_type{0}; uint8_t received_sequence{255}; uint8_t send_sequence{0}; uint8_t last_tunnel_confirmation_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{}; std::vector last_received_cemi; std::vector last_tunnel_confirmation_packet; uint16_t secure_session_id{0}; bool oam_router_persona{false}; }; struct SecureSession { bool active{false}; bool authenticated{false}; uint16_t session_id{0}; int tcp_sock{-1}; ::sockaddr_in remote{}; std::array client_public_key{}; std::array server_public_key{}; std::array shared_secret{}; std::array session_key{}; uint64_t send_sequence{0}; uint64_t receive_sequence{0}; uint8_t user_id{0}; TickType_t last_activity_tick{0}; }; static void TaskEntry(void* arg); esp_err_t initializeRuntime(); void taskLoop(); void finishTask(); void closeSockets(); bool configureSocket(); void handleTcpAccept(); void handleTcpClient(TcpClient& client); void closeTcpClient(TcpClient& client); void closeSecureSessionsForTcp(int sock); std::unique_ptr createOpenKnxTpUartInterface(); bool configureTpUart(); bool configureProgrammingGpio(); void refreshNetworkInterfaces(bool force_log = false); void handleUdpDatagram(const uint8_t* data, size_t len, const ::sockaddr_in& remote); void handleSearchRequest(uint16_t service, const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote); void handleDescriptionRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote); void handleRoutingIndication(const uint8_t* packet_data, size_t len); void handleTunnellingRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote); void handleDeviceConfigurationRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote); void handleConnectRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote); void handleConnectionStateRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote); void handleDisconnectRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote); void handleSecureService(uint16_t service, const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleSecureSessionRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleSecureWrapper(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleSecureGroupSync(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void sendTunnellingAck(uint8_t channel_id, uint8_t sequence, uint8_t status, const ::sockaddr_in& remote); void sendDeviceConfigurationAck(uint8_t channel_id, uint8_t sequence, uint8_t status, const ::sockaddr_in& remote); void sendConnectionHeaderAck(uint16_t service, uint8_t channel_id, uint8_t sequence, uint8_t status, const ::sockaddr_in& remote); void sendSecureSessionStatus(uint8_t status, const ::sockaddr_in& remote); void sendTunnelIndication(const uint8_t* data, size_t len); void sendTunnelIndicationToClient(TunnelClient& client, const uint8_t* data, size_t len); bool sendCemiFrameToClient(TunnelClient& client, uint16_t service, const uint8_t* data, size_t len); void sendConnectionStateResponse(uint8_t channel_id, uint8_t status, const ::sockaddr_in& remote); void sendDisconnectResponse(uint8_t channel_id, uint8_t status, const ::sockaddr_in& remote); void sendConnectResponse(uint8_t channel_id, uint8_t status, const ::sockaddr_in& remote, uint8_t connection_type, uint16_t tunnel_address); void sendRoutingIndication(const uint8_t* data, size_t len); bool sendPacket(const std::vector& packet, const ::sockaddr_in& remote); bool sendPacketToTunnelClient(const TunnelClient& client, const std::vector& packet); bool currentTransportAllowsTcpHpai() const; std::optional> localHpaiForRemote(const ::sockaddr_in& remote, bool tcp = false) const; // --- OpenKNX-backed DIB construction (uses KnxIpSearchResponse / KnxIpDescriptionResponse) --- std::vector buildOpenKnxSearchResponse(const ::sockaddr_in& remote) const; std::vector buildOpenKnxDescriptionResponse(const ::sockaddr_in& remote) const; // --- Hand-rolled DIB builders (fallback when OpenKNX is unavailable) --- std::vector buildDeviceInfoDib(const ::sockaddr_in& remote) const; std::vector buildSupportedServiceDib() const; std::vector buildExtendedDeviceInfoDib() const; std::vector buildIpConfigDib(const ::sockaddr_in& remote, bool current) const; std::vector buildKnxAddressesDib() const; std::vector buildTunnelingInfoDib() const; TunnelClient* findTunnelClient(uint8_t channel_id); const TunnelClient* findTunnelClient(uint8_t channel_id) const; TunnelClient* allocateTunnelClient(const ::sockaddr_in& control_remote, const ::sockaddr_in& data_remote, uint8_t connection_type); void resetTunnelClient(TunnelClient& client); SecureSession* findSecureSession(uint16_t session_id, const ::sockaddr_in& remote); const SecureSession* findSecureSession(uint16_t session_id, const ::sockaddr_in& remote) const; SecureSession* allocateSecureSession(const ::sockaddr_in& remote); SecureSession* activeSecureSession(); bool wrapSecurePacket(SecureSession& session, const std::vector& inner, std::vector* wrapped); bool wrapSecureRoutingPacket(const std::vector& inner, std::vector* wrapped); bool decryptSecureWrapper(SecureSession& session, const uint8_t* body, size_t len, std::vector* inner); bool decryptSecureRoutingWrapper(const uint8_t* body, size_t len, std::vector* inner); bool verifySecureSessionAuth(SecureSession& session, const uint8_t* packet, size_t len, uint8_t* status); bool secureCredentialsReady() const; uint8_t nextTunnelChannelId() const; uint16_t effectiveTunnelAddressForSlot(size_t slot) const; void pruneStaleTunnelClients(); bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len, TunnelClient* response_client, uint16_t response_service, const uint8_t* suppress_routing_echo = nullptr, size_t suppress_routing_echo_len = 0); bool handleOamRouterTunnelFrame(const uint8_t* data, size_t len, TunnelClient* response_client, uint16_t response_service, const uint8_t* suppress_routing_echo = nullptr, size_t suppress_routing_echo_len = 0); bool handleOpenKnxBusFrame(const uint8_t* data, size_t len); bool transmitOpenKnxTpFrame(const uint8_t* data, size_t len); void publishCloudCemiFrame(const uint8_t* data, size_t len); void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote); bool routeOpenKnxGroupWrite(const uint8_t* data, size_t len, const char* context); bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len); bool shouldRouteDaliApplicationFrames() const; uint8_t advertisedMedium() const; void syncOpenKnxConfigFromDevice(); uint16_t effectiveIpInterfaceIndividualAddress() const; uint16_t effectiveKnxDeviceIndividualAddress() const; uint16_t effectiveTunnelAddress() const; void pollProgrammingButton(); void updateProgrammingLed(); void setProgrammingLed(bool on); void setOamProgrammingLed(bool on); GatewayKnxBridge& bridge_; GroupWriteHandler group_write_handler_; GroupObjectWriteHandler group_object_write_handler_; RoutingSequenceStoreHandler routing_sequence_store_handler_; CloudCemiPublisher cloud_cemi_publisher_; std::string openknx_namespace_; GatewayKnxConfig config_; std::unique_ptr ets_device_; std::unique_ptr oam_router_; TaskHandle_t task_handle_{nullptr}; SemaphoreHandle_t openknx_lock_{nullptr}; SemaphoreHandle_t startup_semaphore_{nullptr}; esp_err_t startup_result_{ESP_OK}; 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}; int tp_uart_tx_pin_{-1}; int tp_uart_rx_pin_{-1}; std::vector multicast_joined_interfaces_; TickType_t network_refresh_tick_{0}; std::array tcp_clients_{}; std::array tunnel_clients_{}; std::array secure_sessions_{}; std::unique_ptr knx_ip_parameters_; uint8_t last_tunnel_channel_id_{0}; uint16_t last_secure_session_id_{0}; uint16_t active_secure_session_id_{0}; GatewayKnxIpSecureCredentialMaterial oam_ip_secure_credentials_{}; bool tp_uart_online_{false}; bool commissioning_only_{false}; std::atomic_bool openknx_configured_{false}; std::atomic cloud_cemi_uplink_frames_{0}; std::atomic cloud_cemi_downlink_frames_{0}; bool programming_button_last_pressed_{false}; bool programming_led_state_{false}; TickType_t programming_button_last_toggle_tick_{0}; bool oam_programming_mode_{false}; bool oam_programming_button_last_pressed_{false}; bool oam_programming_led_state_{false}; TickType_t oam_programming_button_last_toggle_tick_{0}; std::string last_error_; }; } // namespace gateway