diff --git a/apps/gateway/sdkconfig b/apps/gateway/sdkconfig index 3911172..e1fc65d 100644 --- a/apps/gateway/sdkconfig +++ b/apps/gateway/sdkconfig @@ -1075,12 +1075,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0 # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set -CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set -CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11 +CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y +CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15 CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 diff --git a/apps/gateway/sdkconfig.old b/apps/gateway/sdkconfig.old index f8cb68f..23459a2 100644 --- a/apps/gateway/sdkconfig.old +++ b/apps/gateway/sdkconfig.old @@ -656,6 +656,26 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y # CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set # CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60 +CONFIG_GATEWAY_ETHERNET_SUPPORTED=y +CONFIG_GATEWAY_START_ETHERNET_ENABLED=y +CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y + +# +# Gateway Wired Ethernet +# +CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=1 +CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=14 +CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=13 +CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=12 +CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=15 +CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=4 +CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0 +CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=36 +CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=5 +CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1 +CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=3072 +# end of Gateway Wired Ethernet + CONFIG_GATEWAY_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set @@ -666,7 +686,24 @@ CONFIG_GATEWAY_MODBUS_TCP_PORT=1502 CONFIG_GATEWAY_MODBUS_UNIT_ID=1 CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set -# CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED is not set +CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y +CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y +CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y +# CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED is not set +# CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS is not set +CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y +CONFIG_GATEWAY_KNX_MAIN_GROUP=0 +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_TP_UART_PORT=0 +CONFIG_GATEWAY_KNX_TP_TX_PIN=-1 +CONFIG_GATEWAY_KNX_TP_RX_PIN=-1 +CONFIG_GATEWAY_KNX_TP_BAUDRATE=19200 +CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=8192 +CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5 CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144 @@ -675,16 +712,7 @@ CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192 CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5 CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y # CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set -CONFIG_GATEWAY_485_CONTROL_ENABLED=y -CONFIG_GATEWAY_485_CONTROL_BAUDRATE=9600 -CONFIG_GATEWAY_485_CONTROL_TX_PIN=-1 -CONFIG_GATEWAY_485_CONTROL_RX_PIN=-1 -CONFIG_GATEWAY_485_CONTROL_RX_BUFFER=256 -CONFIG_GATEWAY_485_CONTROL_TX_BUFFER=256 -CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS=20 -CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS=20 -CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE=4096 -CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY=4 +# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set # end of Gateway Startup Services # diff --git a/components/gateway_bridge/include/gateway_bridge.hpp b/components/gateway_bridge/include/gateway_bridge.hpp index 981fb3e..d559f96 100644 --- a/components/gateway_bridge/include/gateway_bridge.hpp +++ b/components/gateway_bridge/include/gateway_bridge.hpp @@ -16,6 +16,7 @@ namespace gateway { class DaliDomainService; +struct DaliRawFrame; class GatewayCache; struct GatewayBridgeServiceConfig { @@ -65,6 +66,7 @@ class GatewayBridgeService { ChannelRuntime* findRuntime(uint8_t gateway_id); const ChannelRuntime* findRuntime(uint8_t gateway_id) const; + void handleDaliRawFrame(const DaliRawFrame& frame); void collectUsedRuntimeResources(uint8_t except_gateway_id, std::set* modbus_tcp_ports, std::set* knx_udp_ports, diff --git a/components/gateway_bridge/src/gateway_bridge.cpp b/components/gateway_bridge/src/gateway_bridge.cpp index 183b368..7856b2a 100644 --- a/components/gateway_bridge/src/gateway_bridge.cpp +++ b/components/gateway_bridge/src/gateway_bridge.cpp @@ -57,6 +57,10 @@ constexpr uint32_t kBacnetMaxObjectInstance = 4194303; constexpr uint32_t kBacnetReliabilityNoFaultDetected = 0; constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12; constexpr const char* kModbusManagementPrefix = "@DALIGW"; +constexpr uint8_t kDaliGroupRawMin = 0x80; +constexpr uint8_t kDaliGroupRawMax = 0x9F; +constexpr uint8_t kDaliCmdOff = 0x00; +constexpr uint8_t kDaliCmdRecallMax = 0x05; struct GatewayBridgeStoredConfig { BridgeRuntimeConfig bridge; @@ -71,6 +75,11 @@ struct BridgeDiscoveryEntry { DaliDomainSnapshot discovery; }; +struct DaliKnxStatusUpdate { + GatewayKnxDaliTarget target; + uint8_t actual_level{0}; +}; + using BridgeDiscoveryInventory = std::map; class LockGuard { @@ -220,6 +229,51 @@ bool ValidDaliAddress(int address) { return address >= 0 && address <= 127; } +std::optional DecodeKnxDaliTarget(uint8_t raw_addr) { + if (raw_addr <= 0x7F) { + return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress, + static_cast(raw_addr >> 1)}; + } + if (raw_addr >= kDaliGroupRawMin && raw_addr <= kDaliGroupRawMax) { + return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup, + static_cast((raw_addr - kDaliGroupRawMin) >> 1)}; + } + return std::nullopt; +} + +std::optional DecodeDaliKnxStatusUpdate(const DaliRawFrame& frame) { + if (frame.data.size() != 2 && frame.data.size() != 3) { + return std::nullopt; + } + uint8_t raw_addr = 0; + uint8_t command = 0; + if (frame.data.size() == 2) { + raw_addr = frame.data[0]; + command = frame.data[1]; + if (raw_addr == 0xBE) { + return std::nullopt; + } + } else { + raw_addr = frame.data[1]; + command = frame.data[2]; + } + auto target = DecodeKnxDaliTarget(raw_addr); + if (!target.has_value()) { + return std::nullopt; + } + if ((raw_addr & 0x01U) == 0) { + if (command > 254) { + return std::nullopt; + } + return DaliKnxStatusUpdate{*target, command}; + } + if (command == kDaliCmdOff || command == kDaliCmdRecallMax) { + return DaliKnxStatusUpdate{*target, + static_cast(command == kDaliCmdOff ? 0 : 254)}; + } + return std::nullopt; +} + bool ValidShortAddress(int address) { return address >= 0 && address <= kMaxDaliShortAddress; } @@ -3652,6 +3706,9 @@ esp_err_t GatewayBridgeService::start() { runtimes_.push_back(std::move(runtime)); } + dali_domain_.addRawFrameSink( + [this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); }); + std::set used_serial_uarts; if (config_.modbus_enabled && config_.modbus_startup_enabled) { std::set used_modbus_ports; @@ -3708,6 +3765,22 @@ const GatewayBridgeService::ChannelRuntime* GatewayBridgeService::findRuntime( return nullptr; } +void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) { + const auto update = DecodeDaliKnxStatusUpdate(frame); + if (!update.has_value()) { + return; + } + auto* runtime = findRuntime(frame.gateway_id); + if (runtime == nullptr) { + return; + } + LockGuard guard(runtime->lock); + if (!runtime->knx_started || runtime->knx_router == nullptr) { + return; + } + runtime->knx_router->publishDaliStatus(update->target, update->actual_level); +} + void GatewayBridgeService::collectUsedRuntimeResources( uint8_t except_gateway_id, std::set* modbus_tcp_ports, diff --git a/components/gateway_knx/include/gateway_knx.hpp b/components/gateway_knx/include/gateway_knx.hpp index 54f8b33..424f431 100644 --- a/components/gateway_knx/include/gateway_knx.hpp +++ b/components/gateway_knx/include/gateway_knx.hpp @@ -5,6 +5,7 @@ #include "esp_err.h" #include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #include "freertos/task.h" #include "lwip/sockets.h" @@ -197,6 +198,7 @@ class GatewayKnxTpIpRouter { esp_err_t stop(); bool started() const; const std::string& lastError() const; + bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level); private: static void TaskEntry(void* arg); @@ -225,6 +227,8 @@ class GatewayKnxTpIpRouter { const ::sockaddr_in& remote); void sendRoutingIndication(const uint8_t* data, size_t len); bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len); + bool handleOpenKnxBusFrame(const uint8_t* data, size_t len); + bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len); void syncOpenKnxConfigFromDevice(); uint16_t effectiveIndividualAddress() const; uint16_t effectiveTunnelAddress() const; @@ -239,6 +243,7 @@ class GatewayKnxTpIpRouter { GatewayKnxConfig config_; std::unique_ptr ets_device_; TaskHandle_t task_handle_{nullptr}; + SemaphoreHandle_t openknx_lock_{nullptr}; std::atomic_bool stop_requested_{false}; std::atomic_bool started_{false}; int udp_sock_{-1}; diff --git a/components/gateway_knx/src/gateway_knx.cpp b/components/gateway_knx/src/gateway_knx.cpp index c932e57..e88a816 100644 --- a/components/gateway_knx/src/gateway_knx.cpp +++ b/components/gateway_knx/src/gateway_knx.cpp @@ -64,6 +64,8 @@ constexpr uint16_t kGwReg1AppKoBroadcastDimm = 2; constexpr uint8_t kGwReg1KoSwitch = 0; constexpr uint8_t kGwReg1KoDimmAbsolute = 3; constexpr uint8_t kGwReg1KoColor = 6; +constexpr uint8_t kGwReg1KoSwitchState = 1; +constexpr uint8_t kGwReg1KoDimmState = 4; constexpr uint8_t kReg1DaliFunctionObjectIndex = 160; constexpr uint8_t kReg1DaliFunctionPropertyId = 1; constexpr uint8_t kReg1FunctionType = 2; @@ -84,6 +86,31 @@ struct DecodedGroupWrite { std::vector data; }; +class SemaphoreGuard { + public: + explicit SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore_(semaphore) { + if (semaphore_ != nullptr) { + xSemaphoreTake(semaphore_, portMAX_DELAY); + locked_ = true; + } + } + + ~SemaphoreGuard() { + if (locked_) { + xSemaphoreGive(semaphore_); + } + } + + private: + SemaphoreHandle_t semaphore_{nullptr}; + bool locked_{false}; +}; + +uint8_t DaliArcLevelToDpt5(uint8_t actual_level) { + return static_cast( + std::clamp(static_cast(std::lround(actual_level * 255.0 / 254.0)), 0, 255)); +} + uint16_t ReadBe16(const uint8_t* data) { return static_cast((static_cast(data[0]) << 8) | data[1]); } @@ -309,6 +336,22 @@ std::optional DecodeCemiGroupWrite(const uint8_t* data, size_ return out; } +bool IsCemiGroupFrame(const uint8_t* data, size_t len) { + if (data == nullptr || len < 10) { + return false; + } + const uint8_t message_code = data[0]; + if (message_code != kCemiLDataReq && message_code != kCemiLDataInd && + message_code != kCemiLDataCon) { + return false; + } + const size_t base = 2U + data[1]; + if (len < base + 8U) { + return false; + } + return (data[base + 1] & 0x80) != 0; +} + uint8_t Reg1PercentToArc(uint8_t value) { if (value == 0 || value == 0xff) { return value; @@ -1528,9 +1571,17 @@ GatewayKnxTpIpRouter::GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHa std::string openknx_namespace) : bridge_(bridge), handler_(std::move(handler)), - openknx_namespace_(std::move(openknx_namespace)) {} + openknx_namespace_(std::move(openknx_namespace)) { + openknx_lock_ = xSemaphoreCreateMutex(); +} -GatewayKnxTpIpRouter::~GatewayKnxTpIpRouter() { stop(); } +GatewayKnxTpIpRouter::~GatewayKnxTpIpRouter() { + stop(); + if (openknx_lock_ != nullptr) { + vSemaphoreDelete(openknx_lock_); + openknx_lock_ = nullptr; + } +} void GatewayKnxTpIpRouter::setConfig(const GatewayKnxConfig& config) { config_ = config; } @@ -1559,6 +1610,13 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task std::vector* response) { return bridge_.handleFunctionPropertyState(object_index, property_id, data, len, response); }); + ets_device_->setGroupWriteHandler( + [this](uint16_t group_address, const uint8_t* data, size_t len) { + const DaliBridgeResult result = bridge_.handleGroupWrite(group_address, data, len); + if (!result.ok && !result.error.empty()) { + ESP_LOGD(kTag, "secure KNX group write not routed to DALI: %s", result.error.c_str()); + } + }); if (!configureTpUart()) { ets_device_.reset(); closeSockets(); @@ -1590,6 +1648,40 @@ bool GatewayKnxTpIpRouter::started() const { return started_; } const std::string& GatewayKnxTpIpRouter::lastError() const { return last_error_; } +bool GatewayKnxTpIpRouter::publishDaliStatus(const GatewayKnxDaliTarget& target, + uint8_t actual_level) { + if (!started_ || !config_.ip_router_enabled) { + return false; + } + uint16_t switch_object = 0; + uint16_t dimm_object = 0; + if (target.kind == GatewayKnxDaliTargetKind::kShortAddress) { + if (target.address < 0 || target.address > 63) { + return false; + } + const uint16_t base = kGwReg1AdrKoOffset + + kGwReg1AdrKoBlockSize * static_cast(target.address); + switch_object = base + kGwReg1KoSwitchState; + dimm_object = base + kGwReg1KoDimmState; + } else if (target.kind == GatewayKnxDaliTargetKind::kGroup) { + if (target.address < 0 || target.address > 15) { + return false; + } + const uint16_t base = kGwReg1GrpKoOffset + + kGwReg1GrpKoBlockSize * static_cast(target.address); + switch_object = base + kGwReg1KoSwitchState; + dimm_object = base + kGwReg1KoDimmState; + } else { + return false; + } + + const uint8_t switch_value = actual_level > 0 ? 1 : 0; + const uint8_t dimm_value = DaliArcLevelToDpt5(actual_level); + bool emitted = emitOpenKnxGroupValue(switch_object, &switch_value, 1); + emitted = emitOpenKnxGroupValue(dimm_object, &dimm_value, 1) || emitted; + return emitted; +} + void GatewayKnxTpIpRouter::TaskEntry(void* arg) { static_cast(arg)->taskLoop(); } @@ -1603,8 +1695,11 @@ void GatewayKnxTpIpRouter::taskLoop() { reinterpret_cast(&remote), &remote_len); if (received <= 0) { pollTpUart(); - if (ets_device_ != nullptr) { - ets_device_->loop(); + { + SemaphoreGuard guard(openknx_lock_); + if (ets_device_ != nullptr) { + ets_device_->loop(); + } } if (!stop_requested_) { vTaskDelay(pdMS_TO_TICKS(10)); @@ -1613,8 +1708,11 @@ void GatewayKnxTpIpRouter::taskLoop() { } handleUdpDatagram(buffer.data(), static_cast(received), remote); pollTpUart(); - if (ets_device_ != nullptr) { - ets_device_->loop(); + { + SemaphoreGuard guard(openknx_lock_); + if (ets_device_ != nullptr) { + ets_device_->loop(); + } } } finishTask(); @@ -1622,7 +1720,10 @@ void GatewayKnxTpIpRouter::taskLoop() { void GatewayKnxTpIpRouter::finishTask() { closeSockets(); - ets_device_.reset(); + { + SemaphoreGuard guard(openknx_lock_); + ets_device_.reset(); + } started_ = false; task_handle_ = nullptr; vTaskDelete(nullptr); @@ -1805,9 +1906,12 @@ void GatewayKnxTpIpRouter::handleRoutingIndication(const uint8_t* body, size_t l if (body == nullptr || len == 0) { return; } - const DaliBridgeResult result = handler_(body, len); - if (!result.ok && !result.error.empty()) { - ESP_LOGD(kTag, "KNX routing indication ignored: %s", result.error.c_str()); + const bool consumed_by_openknx = handleOpenKnxBusFrame(body, len); + if (!consumed_by_openknx) { + const DaliBridgeResult result = handler_(body, len); + if (!result.ok && !result.error.empty()) { + ESP_LOGD(kTag, "KNX routing indication ignored: %s", result.error.c_str()); + } } forwardCemiToTp(body, len); } @@ -1831,8 +1935,12 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* body, size_t l sendTunnellingAck(channel_id, sequence, kKnxNoError, remote); const uint8_t* cemi = body + 4; const size_t cemi_len = len - 4; + const bool group_frame = IsCemiGroupFrame(cemi, cemi_len); const bool consumed_by_openknx = handleOpenKnxTunnelFrame(cemi, cemi_len); if (consumed_by_openknx) { + if (group_frame) { + forwardCemiToTp(cemi, cemi_len); + } return; } const DaliBridgeResult result = handler_(cemi, cemi_len); @@ -1959,6 +2067,7 @@ void GatewayKnxTpIpRouter::sendRoutingIndication(const uint8_t* data, size_t len } bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t len) { + SemaphoreGuard guard(openknx_lock_); if (ets_device_ == nullptr) { return false; } @@ -1970,6 +2079,32 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t return consumed; } +bool GatewayKnxTpIpRouter::handleOpenKnxBusFrame(const uint8_t* data, size_t len) { + SemaphoreGuard guard(openknx_lock_); + if (ets_device_ == nullptr) { + return false; + } + const bool consumed = ets_device_->handleBusFrame(data, len); + syncOpenKnxConfigFromDevice(); + return consumed; +} + +bool GatewayKnxTpIpRouter::emitOpenKnxGroupValue(uint16_t group_object_number, + const uint8_t* data, size_t len) { + SemaphoreGuard guard(openknx_lock_); + if (ets_device_ == nullptr) { + return false; + } + const bool emitted = ets_device_->emitGroupValue( + group_object_number, data, len, [this](const uint8_t* frame_data, size_t frame_len) { + sendRoutingIndication(frame_data, frame_len); + sendTunnelIndication(frame_data, frame_len); + forwardCemiToTp(frame_data, frame_len); + }); + syncOpenKnxConfigFromDevice(); + return emitted; +} + void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() { if (ets_device_ == nullptr) { return; @@ -2122,9 +2257,12 @@ void GatewayKnxTpIpRouter::handleTpTelegram(const uint8_t* data, size_t len) { if (!cemi.has_value()) { return; } - const DaliBridgeResult result = handler_(cemi->data(), cemi->size()); - if (!result.ok && !result.error.empty()) { - ESP_LOGD(kTag, "KNX TP frame not routed to DALI: %s", result.error.c_str()); + const bool consumed_by_openknx = handleOpenKnxBusFrame(cemi->data(), cemi->size()); + if (!consumed_by_openknx) { + const DaliBridgeResult result = handler_(cemi->data(), cemi->size()); + if (!result.ok && !result.error.empty()) { + ESP_LOGD(kTag, "KNX TP frame not routed to DALI: %s", result.error.c_str()); + } } sendTunnelIndication(cemi->data(), cemi->size()); sendRoutingIndication(cemi->data(), cemi->size()); diff --git a/components/openknx_idf/include/openknx_idf/esp_idf_platform.h b/components/openknx_idf/include/openknx_idf/esp_idf_platform.h index e97e2dd..28a668c 100644 --- a/components/openknx_idf/include/openknx_idf/esp_idf_platform.h +++ b/components/openknx_idf/include/openknx_idf/esp_idf_platform.h @@ -14,10 +14,15 @@ namespace gateway::openknx { class EspIdfPlatform : public Platform { public: + using OutboundCemiFrameCallback = bool (*)(CemiFrame& frame, void* context); + explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr, const char* nvs_namespace = "openknx"); ~EspIdfPlatform() override; + void outboundCemiFrameCallback(OutboundCemiFrameCallback callback, void* context); + bool handleOutboundCemiFrame(CemiFrame& frame) override; + void networkInterface(esp_netif_t* netif); esp_netif_t* networkInterface() const; @@ -54,6 +59,8 @@ class EspIdfPlatform : public Platform { std::vector eeprom_; std::string nvs_namespace_; bool eeprom_loaded_{false}; + OutboundCemiFrameCallback outbound_cemi_frame_callback_{nullptr}; + void* outbound_cemi_frame_context_{nullptr}; }; } // namespace gateway::openknx \ No newline at end of file diff --git a/components/openknx_idf/include/openknx_idf/ets_device_runtime.h b/components/openknx_idf/include/openknx_idf/ets_device_runtime.h index 36d8872..301b21d 100644 --- a/components/openknx_idf/include/openknx_idf/ets_device_runtime.h +++ b/components/openknx_idf/include/openknx_idf/ets_device_runtime.h @@ -17,6 +17,8 @@ namespace gateway::openknx { class EtsDeviceRuntime { public: using CemiFrameSender = std::function; + using GroupWriteHandler = std::function; using FunctionPropertyHandler = std::function* response)>; @@ -31,12 +33,19 @@ class EtsDeviceRuntime { void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler, FunctionPropertyHandler state_handler); + void setGroupWriteHandler(GroupWriteHandler handler); bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender); + bool handleBusFrame(const uint8_t* data, size_t len); + bool emitGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len, + CemiFrameSender sender); void loop(); private: + static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context); static void EmitTunnelFrame(CemiFrame& frame, void* context); + static void HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data, + uint8_t data_length, void* context); static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id, uint8_t length, uint8_t* data, uint8_t* result_data, uint8_t& result_length); @@ -48,11 +57,13 @@ class EtsDeviceRuntime { uint8_t property_id, uint8_t length, uint8_t* data, uint8_t* result_data, uint8_t& result_length); bool shouldConsumeTunnelFrame(CemiFrame& frame) const; + bool shouldConsumeBusFrame(CemiFrame& frame) const; std::string nvs_namespace_; EspIdfPlatform platform_; Bau07B0 device_; CemiFrameSender sender_; + GroupWriteHandler group_write_handler_; FunctionPropertyHandler command_handler_; FunctionPropertyHandler state_handler_; }; diff --git a/components/openknx_idf/src/esp_idf_platform.cpp b/components/openknx_idf/src/esp_idf_platform.cpp index ad92fb5..1e2afdb 100644 --- a/components/openknx_idf/src/esp_idf_platform.cpp +++ b/components/openknx_idf/src/esp_idf_platform.cpp @@ -51,6 +51,19 @@ EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* interface, EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); } +void EspIdfPlatform::outboundCemiFrameCallback(OutboundCemiFrameCallback callback, + void* context) { + outbound_cemi_frame_callback_ = callback; + outbound_cemi_frame_context_ = context; +} + +bool EspIdfPlatform::handleOutboundCemiFrame(CemiFrame& frame) { + if (outbound_cemi_frame_callback_ == nullptr) { + return false; + } + return outbound_cemi_frame_callback_(frame, outbound_cemi_frame_context_); +} + void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; } esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; } diff --git a/components/openknx_idf/src/ets_device_runtime.cpp b/components/openknx_idf/src/ets_device_runtime.cpp index 222834a..8b9a4b7 100644 --- a/components/openknx_idf/src/ets_device_runtime.cpp +++ b/components/openknx_idf/src/ets_device_runtime.cpp @@ -1,6 +1,7 @@ #include "openknx_idf/ets_device_runtime.h" #include "knx/cemi_server.h" +#include "knx/secure_application_layer.h" #include "knx/property.h" #include @@ -52,6 +53,7 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace, : nvs_namespace_(std::move(nvs_namespace)), platform_(nullptr, nvs_namespace_.c_str()), device_(platform_) { + platform_.outboundCemiFrameCallback(&EtsDeviceRuntime::HandleOutboundCemiFrame, this); ApplyReg1DaliIdentity(device_, platform_); if (IsUsableIndividualAddress(fallback_individual_address)) { device_.deviceObject().individualAddress(fallback_individual_address); @@ -67,9 +69,16 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace, } device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand); device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState); +#ifdef USE_DATASECURE + device_.secureGroupWriteCallback(&EtsDeviceRuntime::HandleSecureGroupWrite, this); +#endif } EtsDeviceRuntime::~EtsDeviceRuntime() { + platform_.outboundCemiFrameCallback(nullptr, nullptr); +#ifdef USE_DATASECURE + device_.secureGroupWriteCallback(nullptr, nullptr); +#endif device_.functionPropertyCallback(nullptr); device_.functionPropertyStateCallback(nullptr); if (auto* server = device_.getCemiServer()) { @@ -126,6 +135,10 @@ void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler comma state_handler_ = std::move(state_handler); } +void EtsDeviceRuntime::setGroupWriteHandler(GroupWriteHandler handler) { + group_write_handler_ = std::move(handler); +} + bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender) { auto* server = device_.getCemiServer(); @@ -146,8 +159,58 @@ bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len, return consumed; } +bool EtsDeviceRuntime::handleBusFrame(const uint8_t* data, size_t len) { + auto* data_link_layer = device_.getDataLinkLayer(); + if (data_link_layer == nullptr || data == nullptr || len < 2) { + return false; + } + std::vector frame_data(data, data + len); + CemiFrame frame(frame_data.data(), static_cast(frame_data.size())); + const bool consumed = shouldConsumeBusFrame(frame); + if (!consumed) { + return false; + } + data_link_layer->externalFrameReceived(frame); + loop(); + return consumed; +} + +bool EtsDeviceRuntime::emitGroupValue(uint16_t group_object_number, const uint8_t* data, + size_t len, CemiFrameSender sender) { + if (group_object_number == 0 || data == nullptr || !sender || !device_.configured()) { + return false; + } + auto& table = device_.groupObjectTable(); + if (group_object_number > table.entryCount()) { + return false; + } + auto& group_object = table.get(group_object_number); + if (len != group_object.valueSize() || group_object.valueRef() == nullptr) { + return false; + } + if (group_object.sizeInTelegram() == 0) { + group_object.valueRef()[0] = data[0] & 0x01; + } else { + std::copy_n(data, len, group_object.valueRef()); + } + sender_ = std::move(sender); + group_object.objectWritten(); + loop(); + sender_ = nullptr; + return true; +} + void EtsDeviceRuntime::loop() { device_.loop(); } +bool EtsDeviceRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context) { + auto* self = static_cast(context); + if (self == nullptr || !self->sender_) { + return false; + } + self->sender_(frame.data(), frame.dataLength()); + return true; +} + void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) { auto* self = static_cast(context); if (self == nullptr || !self->sender_) { @@ -156,6 +219,15 @@ void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) { self->sender_(frame.data(), frame.dataLength()); } +void EtsDeviceRuntime::HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data, + uint8_t data_length, void* context) { + auto* self = static_cast(context); + if (self == nullptr || !self->group_write_handler_) { + return; + } + self->group_write_handler_(group_address, data, data_length); +} + bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id, uint8_t length, uint8_t* data, uint8_t* result_data, @@ -217,11 +289,27 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const { case M_FuncPropStateRead_req: return true; case L_data_req: - return frame.addressType() == IndividualAddress && - frame.destinationAddress() == individualAddress(); + if (frame.addressType() == IndividualAddress && + frame.destinationAddress() == individualAddress()) { + return true; + } + #ifdef USE_DATASECURE + return frame.addressType() == GroupAddress && frame.apdu().type() == SecureService; + #else + return false; + #endif default: return false; } } +bool EtsDeviceRuntime::shouldConsumeBusFrame(CemiFrame& frame) const { +#ifdef USE_DATASECURE + return frame.messageCode() == L_data_ind && frame.addressType() == GroupAddress && + frame.apdu().type() == SecureService; +#else + return false; +#endif +} + } // namespace gateway::openknx diff --git a/knx b/knx index 5da3b1f..1549366 160000 --- a/knx +++ b/knx @@ -1 +1 @@ -Subproject commit 5da3b1f30ec2b4f3c52938edbb5d7abe328b3a8f +Subproject commit 1549366447435aa24cfe063b4e95e7d2ca8de899