From be9ff9c2c9e0f7c57be566deb073ed2d60c4d0d1 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 13 Jun 2026 01:47:36 +0800 Subject: [PATCH] feat: add KNX gateway snapshot and command transaction handling Signed-off-by: Tony --- apps/gateway/main/app_main.cpp | 40 +++ .../gateway_bridge/include/gateway_bridge.hpp | 2 + .../gateway_bridge/src/gateway_bridge.cpp | 2 + .../include/gateway_controller.hpp | 32 +++ .../src/gateway_controller.cpp | 129 ++++++++- .../gateway_knx/include/gateway_knx.hpp | 53 +++- .../gateway_knx/src/gateway_knx_bridge.cpp | 261 +++++++++++++++++- .../gateway_knx/src/gateway_knx_private.hpp | 14 + 8 files changed, 527 insertions(+), 6 deletions(-) diff --git a/apps/gateway/main/app_main.cpp b/apps/gateway/main/app_main.cpp index 586b0e4..e6cc38a 100644 --- a/apps/gateway/main/app_main.cpp +++ b/apps/gateway/main/app_main.cpp @@ -1148,6 +1148,46 @@ extern "C" void app_main(void) { bridge_config.gateway_device_name_provider = []() { return s_runtime == nullptr ? std::string() : s_runtime->deviceName(); }; + bridge_config.knx_gateway_snapshot_provider = []() { + gateway::GatewayKnxGatewaySnapshot out; + if (s_controller == nullptr) { + return out; + } + const auto snapshot = s_controller->snapshot(); + out.channels.reserve(snapshot.channels.size()); + for (const auto& channel : snapshot.channels) { + out.channels.push_back( + gateway::GatewayKnxGatewayChannelSnapshot{channel.gateway_id}); + } + return out; + }; + bridge_config.knx_gateway_command_transactor = + [](const std::vector& frame, uint32_t timeout_ms) { + gateway::GatewayKnxGatewayTransactionResult out; + if (s_controller == nullptr) { + return out; + } + const auto result = s_controller->transactCommandFrame(frame, timeout_ms); + switch (result.status) { + case gateway::GatewayCommandTransactionStatus::kOk: + out.status = gateway::GatewayKnxGatewayTransactionStatus::kOk; + break; + case gateway::GatewayCommandTransactionStatus::kInvalidFrame: + out.status = gateway::GatewayKnxGatewayTransactionStatus::kInvalidFrame; + break; + case gateway::GatewayCommandTransactionStatus::kQueueRejected: + out.status = gateway::GatewayKnxGatewayTransactionStatus::kQueueRejected; + break; + case gateway::GatewayCommandTransactionStatus::kTimeout: + out.status = gateway::GatewayKnxGatewayTransactionStatus::kTimeout; + break; + case gateway::GatewayCommandTransactionStatus::kNoResponse: + out.status = gateway::GatewayKnxGatewayTransactionStatus::kNoResponse; + break; + } + out.frames = result.frames; + return out; + }; s_bridge = std::make_unique(*s_dali_domain, *s_cache, bridge_config); s_controller->setBridgeService(s_bridge.get()); diff --git a/components/gateway_bridge/include/gateway_bridge.hpp b/components/gateway_bridge/include/gateway_bridge.hpp index 0cff16d..bc1f911 100644 --- a/components/gateway_bridge/include/gateway_bridge.hpp +++ b/components/gateway_bridge/include/gateway_bridge.hpp @@ -43,6 +43,8 @@ struct GatewayBridgeServiceConfig { UBaseType_t knx_task_priority{5}; std::optional default_knx_config; std::function gateway_device_name_provider; + GatewayKnxGatewaySnapshotProvider knx_gateway_snapshot_provider; + GatewayKnxGatewayCommandTransactor knx_gateway_command_transactor; }; struct GatewayBridgeHttpResponse { diff --git a/components/gateway_bridge/src/gateway_bridge.cpp b/components/gateway_bridge/src/gateway_bridge.cpp index 455eaa0..b74770f 100644 --- a/components/gateway_bridge/src/gateway_bridge.cpp +++ b/components/gateway_bridge/src/gateway_bridge.cpp @@ -1715,6 +1715,8 @@ struct GatewayBridgeService::ChannelRuntime { } knx = std::make_unique(*engine); + knx->setGatewayControllerCallbacks(service_config.knx_gateway_snapshot_provider, + service_config.knx_gateway_command_transactor); knx_router = std::make_unique(*knx, openKnxNamespace()); knx_router->setGroupWriteHandler( [this](uint16_t group_address, const uint8_t* data, size_t len) { diff --git a/components/gateway_controller/include/gateway_controller.hpp b/components/gateway_controller/include/gateway_controller.hpp index 84967b1..91f8c7c 100644 --- a/components/gateway_controller/include/gateway_controller.hpp +++ b/components/gateway_controller/include/gateway_controller.hpp @@ -65,6 +65,19 @@ struct GatewayControllerSnapshot { std::vector channels; }; +enum class GatewayCommandTransactionStatus : uint8_t { + kOk = 0, + kInvalidFrame = 1, + kQueueRejected = 2, + kTimeout = 3, + kNoResponse = 4, +}; + +struct GatewayCommandTransactionResult { + GatewayCommandTransactionStatus status{GatewayCommandTransactionStatus::kNoResponse}; + std::vector frames; +}; + class GatewayController { public: using NotificationSink = std::function& frame)>; @@ -79,6 +92,11 @@ class GatewayController { esp_err_t start(); bool enqueueCommandFrame(const std::vector& frame); + GatewayCommandTransactionResult transactCommandFrame( + const std::vector& frame, + uint32_t timeout_ms = 700, + uint32_t idle_ms = 30, + size_t max_response_bytes = 2048); void addNotificationSink(NotificationSink sink); void addBleStateSink(BleStateSink sink); void addWifiStateSink(WifiStateSink sink); @@ -122,6 +140,15 @@ class GatewayController { uint8_t short_address{0}; }; + struct TransactionWaiter { + uint8_t gateway_id{0}; + uint8_t opcode{0}; + SemaphoreHandle_t signal{nullptr}; + std::vector frames; + size_t max_response_bytes{0}; + bool overflow{false}; + }; + static void TaskEntry(void* arg); void taskLoop(); void dispatchCommand(const std::vector& command); @@ -144,6 +171,9 @@ class GatewayController { void refreshRuntimeGatewayNames(); void publishPayload(uint8_t gateway_id, const std::vector& payload); void publishFrame(const std::vector& frame); + bool transactionFrameMatches(const TransactionWaiter& waiter, + const std::vector& frame) const; + void captureTransactionFrame(const std::vector& frame); void handleDaliRawFrame(const DaliRawFrame& frame); bool handleApplicationControllerFrame(const DaliRawFrame& frame); std::optional applicationControllerResponse(uint8_t gateway_id, uint8_t first, @@ -203,11 +233,13 @@ class GatewayController { GatewayControllerConfig config_; TaskHandle_t task_handle_{nullptr}; SemaphoreHandle_t maintenance_lock_{nullptr}; + SemaphoreHandle_t transaction_lock_{nullptr}; std::vector notification_sinks_; std::vector ble_state_sinks_; std::vector wifi_state_sinks_; std::vector gateway_name_sinks_; std::map bridge_transport_requests_; + std::vector transaction_waiters_; std::map reconciliation_jobs_; std::map cache_refresh_jobs_; std::atomic maintenance_activity_gateway_{-1}; diff --git a/components/gateway_controller/src/gateway_controller.cpp b/components/gateway_controller/src/gateway_controller.cpp index da94848..a54e2d7 100644 --- a/components/gateway_controller/src/gateway_controller.cpp +++ b/components/gateway_controller/src/gateway_controller.cpp @@ -282,9 +282,14 @@ GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain_(dali_domain), cache_(cache), config_(config), - maintenance_lock_(xSemaphoreCreateMutex()) {} + maintenance_lock_(xSemaphoreCreateMutex()), + transaction_lock_(xSemaphoreCreateMutex()) {} GatewayController::~GatewayController() { + if (transaction_lock_ != nullptr) { + vSemaphoreDelete(transaction_lock_); + transaction_lock_ = nullptr; + } if (maintenance_lock_ != nullptr) { vSemaphoreDelete(maintenance_lock_); maintenance_lock_ = nullptr; @@ -341,6 +346,74 @@ bool GatewayController::enqueueCommandFrame(const std::vector& frame) { return true; } +GatewayCommandTransactionResult GatewayController::transactCommandFrame( + const std::vector& frame, + uint32_t timeout_ms, + uint32_t idle_ms, + size_t max_response_bytes) { + GatewayCommandTransactionResult result; + if (!GatewayRuntime::isGatewayCommandFrame(frame) || !GatewayRuntime::hasValidChecksum(frame) || + frame.size() < 5) { + result.status = GatewayCommandTransactionStatus::kInvalidFrame; + return result; + } + + TransactionWaiter waiter; + waiter.gateway_id = frame[2]; + waiter.opcode = frame[3]; + waiter.max_response_bytes = max_response_bytes; + waiter.signal = xSemaphoreCreateBinary(); + if (waiter.signal == nullptr) { + result.status = GatewayCommandTransactionStatus::kQueueRejected; + return result; + } + + { + LockGuard guard(transaction_lock_); + transaction_waiters_.push_back(&waiter); + } + + const bool enqueued = enqueueCommandFrame(frame); + if (!enqueued) { + { + LockGuard guard(transaction_lock_); + transaction_waiters_.erase(std::remove(transaction_waiters_.begin(), + transaction_waiters_.end(), &waiter), + transaction_waiters_.end()); + } + vSemaphoreDelete(waiter.signal); + result.status = GatewayCommandTransactionStatus::kQueueRejected; + return result; + } + + const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); + const BaseType_t first = xSemaphoreTake(waiter.signal, timeout_ticks); + if (first == pdTRUE && idle_ms > 0) { + while (xSemaphoreTake(waiter.signal, pdMS_TO_TICKS(idle_ms)) == pdTRUE) { + } + } + + { + LockGuard guard(transaction_lock_); + transaction_waiters_.erase(std::remove(transaction_waiters_.begin(), + transaction_waiters_.end(), &waiter), + transaction_waiters_.end()); + result.frames = waiter.frames; + } + vSemaphoreDelete(waiter.signal); + + if (waiter.overflow) { + result.status = GatewayCommandTransactionStatus::kQueueRejected; + } else if (!result.frames.empty()) { + result.status = GatewayCommandTransactionStatus::kOk; + } else if (first == pdTRUE) { + result.status = GatewayCommandTransactionStatus::kNoResponse; + } else { + result.status = GatewayCommandTransactionStatus::kTimeout; + } + return result; +} + void GatewayController::addNotificationSink(NotificationSink sink) { if (sink) { notification_sinks_.push_back(std::move(sink)); @@ -1109,11 +1182,65 @@ void GatewayController::publishBridgeTransportResponse(uint8_t gateway_id, uint8 } void GatewayController::publishFrame(const std::vector& frame) { + captureTransactionFrame(frame); for (const auto& sink : notification_sinks_) { sink(frame); } } +bool GatewayController::transactionFrameMatches(const TransactionWaiter& waiter, + const std::vector& frame) const { + if (frame.size() < 3 || frame[0] != 0x22) { + return false; + } + const uint8_t response_opcode = frame[1]; + const uint8_t response_gateway = frame.size() > 2 ? frame[2] : 0; + const bool gateway_matches = response_gateway == waiter.gateway_id || + waiter.gateway_id == 0 || + response_gateway == 0xff; + switch (waiter.opcode) { + case 0x06: + return response_opcode == 0x03 && gateway_matches; + case 0x09: + return (response_opcode == 0x09 && waiter.gateway_id == 0) || + ((response_opcode == 0x03 || response_opcode == 0x04) && gateway_matches); + case 0x14: + return (response_opcode == 0x03 || response_opcode == 0x04) && gateway_matches; + case kDali103QueryOpcode: + return (response_opcode == kDali103QueryResponseOpcode || + response_opcode == kDali103NoResponseOpcode) && + gateway_matches; + case 0x05: + case 0x0A: + case 0x30: + case kGatewayCacheOpcode: + case 0xA0: + case 0xA2: + return response_opcode == waiter.opcode && gateway_matches; + case kBridgeTransportRequestOpcode: + return response_opcode == kBridgeTransportResponseOpcode && gateway_matches; + default: + return response_opcode == waiter.opcode && gateway_matches; + } +} + +void GatewayController::captureTransactionFrame(const std::vector& frame) { + LockGuard guard(transaction_lock_); + for (auto* waiter : transaction_waiters_) { + if (waiter == nullptr || waiter->signal == nullptr || + !transactionFrameMatches(*waiter, frame)) { + continue; + } + if (waiter->frames.size() + frame.size() > waiter->max_response_bytes) { + waiter->overflow = true; + xSemaphoreGive(waiter->signal); + continue; + } + waiter->frames.insert(waiter->frames.end(), frame.begin(), frame.end()); + xSemaphoreGive(waiter->signal); + } +} + std::optional GatewayController::applicationControllerResponse( uint8_t gateway_id, uint8_t first, uint8_t instance, uint8_t opcode) const { const uint8_t gateway_short = diff --git a/components/gateway_knx/include/gateway_knx.hpp b/components/gateway_knx/include/gateway_knx.hpp index 3db5807..5d0aabf 100644 --- a/components/gateway_knx/include/gateway_knx.hpp +++ b/components/gateway_knx/include/gateway_knx.hpp @@ -169,6 +169,32 @@ struct GatewayKnxReg1ScanOptions { bool assign{false}; }; +struct GatewayKnxGatewayChannelSnapshot { + uint8_t gateway_id{0}; +}; + +struct GatewayKnxGatewaySnapshot { + std::vector channels; +}; + +enum class GatewayKnxGatewayTransactionStatus : uint8_t { + kOk = 0, + kInvalidFrame = 1, + kQueueRejected = 2, + kTimeout = 3, + kNoResponse = 4, +}; + +struct GatewayKnxGatewayTransactionResult { + GatewayKnxGatewayTransactionStatus status{GatewayKnxGatewayTransactionStatus::kNoResponse}; + std::vector frames; +}; + +using GatewayKnxGatewaySnapshotProvider = std::function; +using GatewayKnxGatewayCommandTransactor = + std::function& frame, + uint32_t timeout_ms)>; + std::optional GatewayKnxConfigFromValue(const DaliValue* value); DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config); std::optional GatewayKnxOamRouterConfigFromValue( @@ -194,6 +220,8 @@ class GatewayKnxBridge { void setConfig(const GatewayKnxConfig& config); void setRuntimeContext(const openknx::EtsDeviceRuntime* runtime); + void setGatewayControllerCallbacks(GatewayKnxGatewaySnapshotProvider snapshot_provider, + GatewayKnxGatewayCommandTransactor command_transactor); const GatewayKnxConfig& config() const; size_t etsBindingCount() const; @@ -211,6 +239,13 @@ class GatewayKnxBridge { std::vector* response); private: + struct GatewayTransferState { + uint16_t total_length{0}; + uint8_t total_chunks{0}; + std::map> request_chunks; + std::vector response_bytes; + }; + DaliBridgeResult executeForDecodedWrite(uint16_t group_address, GatewayKnxDaliDataType data_type, GatewayKnxDaliTarget target, @@ -244,13 +279,29 @@ class GatewayKnxBridge { std::vector* response); bool handleReg1FoundEvgsState(const uint8_t* data, size_t len, std::vector* response); + bool handleDalimasterDiscoveryState(const uint8_t* data, size_t len, + std::vector* response); + bool handleDalimasterTransferCommand(const uint8_t* data, size_t len, + std::vector* response); + bool handleDalimasterTransferState(const uint8_t* data, size_t len, + std::vector* response); + bool executeDalimasterTransfer(uint16_t transaction_id, uint32_t timeout_ms, + std::vector* response); + bool appendDalimasterTransferResponse(uint8_t transfer_status, + uint16_t transaction_id, + const std::vector& response_bytes, + uint8_t chunk_index, + std::vector* response) const; static void CommissioningScanTaskEntry(void* arg); void runCommissioningScanTask(); DaliBridgeEngine& engine_; + GatewayKnxGatewaySnapshotProvider gateway_snapshot_provider_; + GatewayKnxGatewayCommandTransactor gateway_command_transactor_; GatewayKnxConfig config_; const openknx::EtsDeviceRuntime* runtime_{nullptr}; std::map> ets_bindings_by_group_address_; + std::map gateway_transfer_states_; SemaphoreHandle_t commissioning_lock_{nullptr}; TaskHandle_t commissioning_scan_task_{nullptr}; std::atomic_bool commissioning_scan_cancel_requested_{false}; @@ -536,4 +587,4 @@ class GatewayKnxTpIpRouter { std::string last_error_; }; -} // namespace gateway \ No newline at end of file +} // namespace gateway diff --git a/components/gateway_knx/src/gateway_knx_bridge.cpp b/components/gateway_knx/src/gateway_knx_bridge.cpp index 8990ff2..0b104d8 100644 --- a/components/gateway_knx/src/gateway_knx_bridge.cpp +++ b/components/gateway_knx/src/gateway_knx_bridge.cpp @@ -418,6 +418,13 @@ void GatewayKnxBridge::setRuntimeContext(const openknx::EtsDeviceRuntime* runtim runtime_ = runtime; } +void GatewayKnxBridge::setGatewayControllerCallbacks( + GatewayKnxGatewaySnapshotProvider snapshot_provider, + GatewayKnxGatewayCommandTransactor command_transactor) { + gateway_snapshot_provider_ = std::move(snapshot_provider); + gateway_command_transactor_ = std::move(command_transactor); +} + const GatewayKnxConfig& GatewayKnxBridge::config() const { return config_; } size_t GatewayKnxBridge::etsBindingCount() const { @@ -615,8 +622,14 @@ DaliBridgeResult GatewayKnxBridge::handleGroupObjectWrite(uint16_t group_object_ bool GatewayKnxBridge::handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len, std::vector* response) { - if (object_index != kReg1DaliFunctionObjectIndex || property_id != kReg1DaliFunctionPropertyId || - data == nullptr || len == 0 || response == nullptr) { + if (object_index != kReg1DaliFunctionObjectIndex || data == nullptr || len == 0 || + response == nullptr) { + return false; + } + if (property_id == kDalimasterTransferFunctionPropertyId) { + return handleDalimasterTransferCommand(data, len, response); + } + if (property_id != kReg1DaliFunctionPropertyId) { return false; } switch (data[0]) { @@ -644,8 +657,16 @@ bool GatewayKnxBridge::handleFunctionPropertyCommand(uint8_t object_index, uint8 bool GatewayKnxBridge::handleFunctionPropertyState(uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len, std::vector* response) { - if (object_index != kReg1DaliFunctionObjectIndex || property_id != kReg1DaliFunctionPropertyId || - data == nullptr || len == 0 || response == nullptr) { + if (object_index != kReg1DaliFunctionObjectIndex || response == nullptr) { + return false; + } + if (property_id == kDalimasterDiscoveryFunctionPropertyId) { + return handleDalimasterDiscoveryState(data, len, response); + } + if (property_id == kDalimasterTransferFunctionPropertyId) { + return handleDalimasterTransferState(data, len, response); + } + if (property_id != kReg1DaliFunctionPropertyId || data == nullptr || len == 0) { return false; } switch (data[0]) { @@ -661,6 +682,238 @@ bool GatewayKnxBridge::handleFunctionPropertyState(uint8_t object_index, uint8_t } } +bool GatewayKnxBridge::handleDalimasterDiscoveryState(const uint8_t*, size_t, + std::vector* response) { + if (response == nullptr) { + return false; + } + response->clear(); + response->reserve(32); + response->push_back(0x00); + response->push_back('D'); + response->push_back('M'); + response->push_back('K'); + response->push_back('G'); + response->push_back(0x01); + response->push_back(kDalimasterTransferChunkBytes); + uint16_t feature_flags = 0x0100 | 0x0080; + if (config_.tunnel_enabled) { + feature_flags |= 0x0001; + } + if (config_.multicast_enabled) { + feature_flags |= 0x0002; + } + if (config_.oam_router.secure_tunnel_enabled) { + feature_flags |= 0x0004; + } + if (config_.oam_router.secure_routing_enabled) { + feature_flags |= 0x0008; + } + response->push_back(static_cast(feature_flags & 0xff)); + response->push_back(static_cast((feature_flags >> 8) & 0xff)); + response->push_back(kReg1DaliFunctionObjectIndex); + response->push_back(kDalimasterDiscoveryFunctionPropertyId); + response->push_back(kDalimasterTransferFunctionPropertyId); + const uint16_t individual_address = + runtime_ != nullptr ? runtime_->individualAddress() : config_.individual_address; + const uint16_t tunnel_address = + runtime_ != nullptr ? runtime_->tunnelClientAddress() : config_.ip_interface_individual_address; + response->push_back(static_cast((individual_address >> 8) & 0xff)); + response->push_back(static_cast(individual_address & 0xff)); + response->push_back(static_cast((tunnel_address >> 8) & 0xff)); + response->push_back(static_cast(tunnel_address & 0xff)); + + const auto snapshot = gateway_snapshot_provider_ ? gateway_snapshot_provider_() + : GatewayKnxGatewaySnapshot{}; + const auto channel_count = std::min(snapshot.channels.size(), 32); + response->push_back(static_cast(channel_count)); + for (size_t index = 0; index < channel_count; ++index) { + response->push_back(snapshot.channels[index].gateway_id); + } + return true; +} + +bool GatewayKnxBridge::handleDalimasterTransferCommand(const uint8_t* data, size_t len, + std::vector* response) { + if (data == nullptr || len == 0 || response == nullptr) { + return false; + } + const auto read_le16 = [](const uint8_t* value) -> uint16_t { + return static_cast(value[0] | (static_cast(value[1]) << 8)); + }; + const uint8_t op = data[0]; + if (op == kDalimasterTransferOpSingle) { + if (len < 6) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0, + response); + } + const uint16_t transaction_id = read_le16(data + 1); + const uint32_t timeout_ms = read_le16(data + 3); + gateway_transfer_states_[transaction_id] = GatewayTransferState{}; + gateway_transfer_states_[transaction_id].request_chunks[0] = + std::vector(data + 5, data + len); + gateway_transfer_states_[transaction_id].total_length = + static_cast(len - 5); + gateway_transfer_states_[transaction_id].total_chunks = 1; + return executeDalimasterTransfer(transaction_id, timeout_ms, response); + } + if (op == kDalimasterTransferOpBegin) { + if (len < 6) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0, + response); + } + const uint16_t transaction_id = read_le16(data + 1); + GatewayTransferState state; + state.total_length = read_le16(data + 3); + state.total_chunks = data[5]; + gateway_transfer_states_[transaction_id] = std::move(state); + return appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id, {}, 0, + response); + } + if (op == kDalimasterTransferOpChunk) { + if (len < 6) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0, + response); + } + const uint16_t transaction_id = read_le16(data + 1); + const uint8_t chunk_index = data[3]; + const uint8_t chunk_len = data[4]; + if (len < static_cast(5 + chunk_len) || + chunk_len > kDalimasterTransferChunkBytes || + gateway_transfer_states_.find(transaction_id) == gateway_transfer_states_.end()) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, + transaction_id, {}, chunk_index, response); + } + gateway_transfer_states_[transaction_id].request_chunks[chunk_index] = + std::vector(data + 5, data + 5 + chunk_len); + return appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id, {}, + chunk_index, response); + } + if (op == kDalimasterTransferOpExecute) { + if (len < 5) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0, + response); + } + return executeDalimasterTransfer(read_le16(data + 1), read_le16(data + 3), response); + } + if (op == kDalimasterTransferOpReadResponse) { + return handleDalimasterTransferState(data, len, response); + } + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0, + response); +} + +bool GatewayKnxBridge::handleDalimasterTransferState(const uint8_t* data, size_t len, + std::vector* response) { + if (data == nullptr || len < 4 || response == nullptr) { + return false; + } + const uint16_t transaction_id = + static_cast(data[1] | (static_cast(data[2]) << 8)); + const uint8_t chunk_index = data[3]; + const auto it = gateway_transfer_states_.find(transaction_id); + if (it == gateway_transfer_states_.end()) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, + transaction_id, {}, chunk_index, response); + } + const auto response_bytes = it->second.response_bytes; + const bool ok = appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id, + response_bytes, chunk_index, response); + const size_t total_chunks = + std::max(1, (response_bytes.size() + kDalimasterTransferChunkBytes - 1) / + kDalimasterTransferChunkBytes); + if (chunk_index + 1 >= total_chunks) { + gateway_transfer_states_.erase(it); + } + return ok; +} + +bool GatewayKnxBridge::executeDalimasterTransfer(uint16_t transaction_id, uint32_t timeout_ms, + std::vector* response) { + const auto it = gateway_transfer_states_.find(transaction_id); + if (it == gateway_transfer_states_.end() || response == nullptr) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, + transaction_id, {}, 0, response); + } + std::vector frame; + frame.reserve(it->second.total_length); + for (uint8_t index = 0; index < it->second.total_chunks; ++index) { + const auto chunk = it->second.request_chunks.find(index); + if (chunk == it->second.request_chunks.end()) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, + transaction_id, {}, 0, response); + } + frame.insert(frame.end(), chunk->second.begin(), chunk->second.end()); + } + if (it->second.total_length != frame.size() || frame.size() < 4 || + frame[0] != 0x28 || frame[1] != 0x01) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, + transaction_id, {}, 0, response); + } + if (!gateway_command_transactor_) { + return appendDalimasterTransferResponse(kDalimasterTransferStatusNoController, + transaction_id, {}, 0, response); + } + const auto transaction = + gateway_command_transactor_(frame, timeout_ms == 0 ? 700 : timeout_ms); + uint8_t status = kDalimasterTransferStatusOk; + switch (transaction.status) { + case GatewayKnxGatewayTransactionStatus::kOk: + status = kDalimasterTransferStatusOk; + break; + case GatewayKnxGatewayTransactionStatus::kInvalidFrame: + status = kDalimasterTransferStatusInvalidFrame; + break; + case GatewayKnxGatewayTransactionStatus::kQueueRejected: + status = kDalimasterTransferStatusQueueRejected; + break; + case GatewayKnxGatewayTransactionStatus::kTimeout: + status = kDalimasterTransferStatusTimeout; + break; + case GatewayKnxGatewayTransactionStatus::kNoResponse: + status = kDalimasterTransferStatusNoResponse; + break; + } + it->second.response_bytes = transaction.frames; + return appendDalimasterTransferResponse(status, transaction_id, it->second.response_bytes, 0, + response); +} + +bool GatewayKnxBridge::appendDalimasterTransferResponse( + uint8_t transfer_status, uint16_t transaction_id, const std::vector& response_bytes, + uint8_t chunk_index, std::vector* response) const { + if (response == nullptr) { + return false; + } + const size_t total_chunks = + std::max(1, (response_bytes.size() + kDalimasterTransferChunkBytes - 1) / + kDalimasterTransferChunkBytes); + if (chunk_index >= total_chunks) { + response->assign({0x00, kDalimasterTransferStatusInvalidFrame, + static_cast(transaction_id & 0xff), + static_cast((transaction_id >> 8) & 0xff), 0x00, 0x00, + static_cast(total_chunks & 0xff), chunk_index, 0x00}); + return true; + } + const size_t start = static_cast(chunk_index) * kDalimasterTransferChunkBytes; + const size_t chunk_len = + std::min(kDalimasterTransferChunkBytes, response_bytes.size() - start); + response->clear(); + response->reserve(9 + chunk_len); + response->push_back(0x00); + response->push_back(transfer_status); + response->push_back(static_cast(transaction_id & 0xff)); + response->push_back(static_cast((transaction_id >> 8) & 0xff)); + response->push_back(static_cast(response_bytes.size() & 0xff)); + response->push_back(static_cast((response_bytes.size() >> 8) & 0xff)); + response->push_back(static_cast(total_chunks & 0xff)); + response->push_back(chunk_index); + response->push_back(static_cast(chunk_len & 0xff)); + response->insert(response->end(), response_bytes.begin() + start, + response_bytes.begin() + start + chunk_len); + return true; +} + bool GatewayKnxBridge::handleReg1TypeCommand(const uint8_t* data, size_t len, std::vector* response) { if (len < 2 || response == nullptr) { diff --git a/components/gateway_knx/src/gateway_knx_private.hpp b/components/gateway_knx/src/gateway_knx_private.hpp index 899c11d..c9dc694 100644 --- a/components/gateway_knx/src/gateway_knx_private.hpp +++ b/components/gateway_knx/src/gateway_knx_private.hpp @@ -138,6 +138,20 @@ constexpr uint8_t kDaliCmdOnStepUp = 0x08; constexpr uint8_t kDaliCmdStopFade = 0xff; constexpr uint8_t kReg1DaliFunctionObjectIndex = 160; constexpr uint8_t kReg1DaliFunctionPropertyId = 1; +constexpr uint8_t kDalimasterDiscoveryFunctionPropertyId = 210; +constexpr uint8_t kDalimasterTransferFunctionPropertyId = 211; +constexpr uint8_t kDalimasterTransferChunkBytes = 192; +constexpr uint8_t kDalimasterTransferOpSingle = 0x01; +constexpr uint8_t kDalimasterTransferOpBegin = 0x02; +constexpr uint8_t kDalimasterTransferOpChunk = 0x03; +constexpr uint8_t kDalimasterTransferOpExecute = 0x04; +constexpr uint8_t kDalimasterTransferOpReadResponse = 0x05; +constexpr uint8_t kDalimasterTransferStatusOk = 0x00; +constexpr uint8_t kDalimasterTransferStatusInvalidFrame = 0x01; +constexpr uint8_t kDalimasterTransferStatusNoController = 0x02; +constexpr uint8_t kDalimasterTransferStatusQueueRejected = 0x03; +constexpr uint8_t kDalimasterTransferStatusTimeout = 0x04; +constexpr uint8_t kDalimasterTransferStatusNoResponse = 0x05; constexpr uint8_t kReg1FunctionType = 2; constexpr uint8_t kReg1FunctionScan = 3; constexpr uint8_t kReg1FunctionAssign = 4;