#pragma once #include "bridge.hpp" #include "model_value.hpp" #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; } constexpr uint16_t kGatewayKnxDefaultUdpPort = 3671; constexpr const char* kGatewayKnxDefaultMulticastAddress = "224.0.23.12"; constexpr uint32_t kGatewayKnxDefaultTpBaudrate = 19200; 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 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 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}; 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}; std::vector ets_associations; GatewayKnxTpUartConfig tp_uart; }; enum class GatewayKnxDaliDataType : uint8_t { kUnknown = 0, kSwitch = 1, kBrightness = 2, kColorTemperature = 3, kRgb = 4, }; 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}; }; std::optional GatewayKnxConfigFromValue(const DaliValue* value); DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& 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); void setConfig(const GatewayKnxConfig& config); const GatewayKnxConfig& config() const; size_t etsBindingCount() const; std::vector describeDaliBindings() const; bool matchesCemiFrame(const uint8_t* data, size_t len) const; bool matchesGroupAddress(uint16_t group_address) const; DaliBridgeResult handleCemiFrame(const uint8_t* data, size_t len); DaliBridgeResult handleGroupWrite(uint16_t group_address, 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 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); DaliBridgeEngine& engine_; GatewayKnxConfig config_; std::map> ets_bindings_by_group_address_; bool commissioning_scan_done_{true}; bool commissioning_assign_done_{true}; std::vector commissioning_found_ballasts_; }; class GatewayKnxTpIpRouter { public: using CemiFrameHandler = std::function; using GroupWriteHandler = std::function; GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler, std::string openknx_namespace = "openknx"); ~GatewayKnxTpIpRouter(); void setConfig(const GatewayKnxConfig& config); void setCommissioningOnly(bool enabled); 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(); bool started() const; const std::string& lastError() const; bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level); private: 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}; uint16_t individual_address{0}; int tcp_sock{-1}; TickType_t last_activity_tick{0}; ::sockaddr_in control_remote{}; ::sockaddr_in data_remote{}; }; 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); bool configureTpUart(); bool initializeTpUart(); 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* body, size_t len, const ::sockaddr_in& remote); void handleDescriptionRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleRoutingIndication(const uint8_t* body, size_t len); void handleTunnellingRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleDeviceConfigurationRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleConnectRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleConnectionStateRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleDisconnectRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote); void handleSecureService(uint16_t service, 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); 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); void sendSearchResponse(uint16_t service, const ::sockaddr_in& remote, const std::set& requested_dibs = {}); void sendDescriptionResponse(const ::sockaddr_in& remote); bool sendPacket(const std::vector& packet, const ::sockaddr_in& remote) const; bool sendPacketToTunnelClient(const TunnelClient& client, const std::vector& packet) const; bool currentTransportAllowsTcpHpai() const; std::optional> localHpaiForRemote(const ::sockaddr_in& remote, bool tcp = false) const; 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); 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); bool handleOpenKnxBusFrame(const uint8_t* data, size_t len); 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 effectiveIpInterfaceIndividualAddress() const; uint16_t effectiveKnxDeviceIndividualAddress() const; uint16_t effectiveTunnelAddress() const; void pollTpUart(); void pollProgrammingButton(); void updateProgrammingLed(); void setProgrammingLed(bool on); void handleTpUartControlByte(uint8_t byte); void handleTpTelegram(const uint8_t* data, size_t len); void forwardCemiToTp(const uint8_t* data, size_t len); GatewayKnxBridge& bridge_; CemiFrameHandler handler_; GroupWriteHandler group_write_handler_; std::string openknx_namespace_; GatewayKnxConfig config_; std::unique_ptr ets_device_; 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_{}; uint8_t last_tunnel_channel_id_{0}; std::vector tp_rx_frame_; std::vector tp_last_sent_telegram_; TickType_t tp_uart_last_byte_tick_{0}; bool tp_uart_extended_frame_{false}; bool tp_uart_online_{false}; bool commissioning_only_{false}; std::atomic_bool openknx_configured_{false}; bool programming_button_last_pressed_{false}; bool programming_led_state_{false}; TickType_t programming_button_last_toggle_tick_{0}; std::string last_error_; }; } // namespace gateway