#pragma once #include "bridge.hpp" #include "model_value.hpp" #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "lwip/sockets.h" #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}; }; 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 individual_address{0x1101}; 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); 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; 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; GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler, std::string openknx_namespace = "openknx"); ~GatewayKnxTpIpRouter(); void setConfig(const GatewayKnxConfig& config); const GatewayKnxConfig& config() const; 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; private: static void TaskEntry(void* arg); void taskLoop(); void finishTask(); void closeSockets(); bool configureSocket(); bool configureTpUart(); bool initializeTpUart(); void handleUdpDatagram(const uint8_t* data, 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 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 sendTunnellingAck(uint8_t channel_id, uint8_t sequence, uint8_t status, const ::sockaddr_in& remote); void sendTunnelIndication(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); void sendRoutingIndication(const uint8_t* data, size_t len); bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len); void syncOpenKnxConfigFromDevice(); uint16_t effectiveIndividualAddress() const; uint16_t effectiveTunnelAddress() const; void pollTpUart(); 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_; std::string openknx_namespace_; GatewayKnxConfig config_; std::unique_ptr ets_device_; TaskHandle_t task_handle_{nullptr}; std::atomic_bool stop_requested_{false}; std::atomic_bool started_{false}; int udp_sock_{-1}; int tp_uart_port_{-1}; uint8_t tunnel_channel_id_{1}; uint8_t expected_tunnel_sequence_{0}; uint8_t tunnel_send_sequence_{0}; bool tunnel_connected_{false}; ::sockaddr_in tunnel_remote_{}; 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}; std::string last_error_; }; } // namespace gateway