From ca6dac655fa268bc8646ead80b01efde1bde1b6e Mon Sep 17 00:00:00 2001 From: liu weikai Date: Sat, 11 Jul 2026 19:24:32 +0800 Subject: [PATCH] Pause messaging while route images download --- docs/wifi_access_resource_policy.md | 1 + .../include/platform/ui/wifi_access_runtime.h | 6 + .../src/platform_ui_route_storage.cpp | 57 ++++++- .../src/platform_ui_wifi_access_runtime.cpp | 157 +++++++++++++++++- 4 files changed, 219 insertions(+), 2 deletions(-) diff --git a/docs/wifi_access_resource_policy.md b/docs/wifi_access_resource_policy.md index ae77b00f..c7b760f8 100644 --- a/docs/wifi_access_resource_policy.md +++ b/docs/wifi_access_resource_policy.md @@ -102,6 +102,7 @@ Trail Mate is a low-frequency terminal, not a desktop router node. | Screen on | Connected messaging is allowed with normal terminal budget. | Direct gateway traffic is allowed with terminal budget. | User foreground work is allowed; background work is deferred. | | Wake protected | Existing sockets may use minimal budget. New reconnects and HTTP starts are deferred. | Existing sockets may use minimal budget. New reconnects are deferred. | Deferred. | | Screen off | MQTT may connect and normally send/receive. | Gateway may connect and receive with expanded terminal budget. | Queued maintenance/background work may run, one transaction at a time. | +| Foreground HTTP pending | Suspended. No connect, read, write, publish, or downlink bridge budget. | Suspended. Existing gateway socket may be closed by its budget handler. | The pending foreground download owns the next HTTP opportunity while it waits for memory/SD/UI resources. | | HTTP active | MQTT and Reticulum are reduced to minimal messaging budget. | Reduced to minimal gateway budget. | Exactly one HTTP transaction owns the HTTP resource. | | OTA active | Protocol pump budget is suspended. | Protocol pump budget is suspended. | OTA is exclusive. | | Reticulum call active | Suspended. MQTT sockets must not connect, pump, publish, or consume RX budget. | Exclusive `call.audio` link traffic only. Discovery, public announce ingest, route/path background noise, and non-call link work are deferred. | Denied. No HTTP, OTA, catalog, pack, language, image, or route download may start. | diff --git a/modules/core_sys/include/platform/ui/wifi_access_runtime.h b/modules/core_sys/include/platform/ui/wifi_access_runtime.h index f0529aaa..d92c90dd 100644 --- a/modules/core_sys/include/platform/ui/wifi_access_runtime.h +++ b/modules/core_sys/include/platform/ui/wifi_access_runtime.h @@ -95,16 +95,22 @@ struct RuntimeStatus { bool http_active = false; bool ota_active = false; + bool foreground_download_pending = false; Client owner = Client::Unknown; AccessKind active_kind = AccessKind::WifiConnect; + Client pending_owner = Client::Unknown; + AccessKind pending_kind = AccessKind::WifiConnect; ScreenPhase screen_phase = ScreenPhase::Unknown; std::uint32_t active_since_ms = 0; + std::uint32_t pending_since_ms = 0; std::uint32_t wake_protected_until_ms = 0; }; bool ensure_connected(const Request& request, Decision* out_decision = nullptr); Lease acquire(const Request& request); void release(const Lease& lease); +bool begin_foreground_download(const Request& request, Decision* out_decision = nullptr); +void end_foreground_download(Client client, AccessKind kind); TrafficBudget traffic_budget(Client client, Priority priority); RuntimeStatus status(); diff --git a/platform/esp/arduino_common/src/platform_ui_route_storage.cpp b/platform/esp/arduino_common/src/platform_ui_route_storage.cpp index e96b97ba..e0edec18 100644 --- a/platform/esp/arduino_common/src/platform_ui_route_storage.cpp +++ b/platform/esp/arduino_common/src/platform_ui_route_storage.cpp @@ -3,6 +3,7 @@ #include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/ui/device_runtime.h" #include "platform/ui/http_client_runtime.h" +#include "platform/ui/wifi_access_runtime.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -41,7 +42,7 @@ constexpr uint16_t kRouteViewHeight = 180; constexpr std::size_t kRouteCacheInternalReserveBytes = 96 * 1024; constexpr std::size_t kRouteCacheInternalSlackBytes = 8 * 1024; constexpr std::size_t kRouteJpegDecoderWorkBytes = 3100; -constexpr std::size_t kRouteHttpInternalFreeTargetBytes = 24 * 1024; +constexpr std::size_t kRouteHttpInternalFreeTargetBytes = 18 * 1024; constexpr std::size_t kRouteHttpInternalLargestTargetBytes = 8 * 1024; constexpr std::size_t kRouteImageDownloadAttempts = 2; constexpr int kRouteImageMemoryWaitAttempts = 16; @@ -892,6 +893,58 @@ struct RouteImageProgressContext TickType_t last_tick = 0; }; +class RouteImageForegroundDownloadScope +{ + public: + RouteImageForegroundDownloadScope() = default; + ~RouteImageForegroundDownloadScope() + { + end(); + } + + RouteImageForegroundDownloadScope(const RouteImageForegroundDownloadScope&) = delete; + RouteImageForegroundDownloadScope& operator=(const RouteImageForegroundDownloadScope&) = delete; + + void begin() + { + if (active_) + { + return; + } + + platform::ui::wifi_access::Request request{}; + request.client = platform::ui::wifi_access::Client::RouteStorage; + request.kind = platform::ui::wifi_access::AccessKind::HttpDownload; + request.priority = platform::ui::wifi_access::Priority::UserForeground; + request.reason = "route_image_pending"; + platform::ui::wifi_access::Decision decision = + platform::ui::wifi_access::Decision::Granted; + active_ = platform::ui::wifi_access::begin_foreground_download( + request, + &decision); + if (!active_) + { + std::printf("[RouteImage][wifi] pending denied decision=%s\n", + platform::ui::wifi_access::decision_name(decision)); + } + } + + void end() + { + if (!active_) + { + return; + } + platform::ui::wifi_access::end_foreground_download( + platform::ui::wifi_access::Client::RouteStorage, + platform::ui::wifi_access::AccessKind::HttpDownload); + active_ = false; + } + + private: + bool active_ = false; +}; + void route_image_download_progress(std::uint32_t current_bytes, std::uint32_t total_bytes, void* context) @@ -1093,6 +1146,8 @@ void route_image_worker_task(void* param) } else { + RouteImageForegroundDownloadScope foreground_download; + foreground_download.begin(); for (std::size_t index = 0; index < total;) { const RouteImageDownloadItem& item = download_items[index]; diff --git a/platform/esp/arduino_common/src/platform_ui_wifi_access_runtime.cpp b/platform/esp/arduino_common/src/platform_ui_wifi_access_runtime.cpp index 61e5638b..bc3237eb 100644 --- a/platform/esp/arduino_common/src/platform_ui_wifi_access_runtime.cpp +++ b/platform/esp/arduino_common/src/platform_ui_wifi_access_runtime.cpp @@ -25,9 +25,13 @@ struct RuntimeState { bool http_active = false; bool ota_active = false; + bool foreground_download_pending = false; Client owner = Client::Unknown; AccessKind active_kind = AccessKind::WifiConnect; + Client pending_owner = Client::Unknown; + AccessKind pending_kind = AccessKind::WifiConnect; std::uint32_t active_since_ms = 0; + std::uint32_t pending_since_ms = 0; std::uint32_t foreground_download_settle_until_ms = 0; std::uint32_t wake_protected_until_ms = 0; bool saw_screen_sample = false; @@ -135,6 +139,18 @@ bool foreground_download_active_for_other(const Request& request) return active; } +bool foreground_download_pending_for_other(const Request& request) +{ + bool pending = false; + portENTER_CRITICAL(&s_lock); + pending = s_state.foreground_download_pending && + foreground_download_owner(s_state.pending_owner) && + !(s_state.pending_owner == request.client && + s_state.pending_kind == request.kind); + portEXIT_CRITICAL(&s_lock); + return pending; +} + bool foreground_download_settle_active_for(Client client, std::uint32_t now_ms) { if (!messaging_client(client)) @@ -175,6 +191,15 @@ bool acquire_http(const Request& request, ScreenPhase phase, Lease& out) portEXIT_CRITICAL(&s_lock); return false; } + if (s_state.foreground_download_pending && + foreground_download_owner(s_state.pending_owner) && + !(s_state.pending_owner == request.client && + s_state.pending_kind == request.kind)) + { + out.decision = Decision::Busy; + portEXIT_CRITICAL(&s_lock); + return false; + } s_state.http_active = true; s_state.ota_active = request.kind == AccessKind::OtaDownload; s_state.owner = request.client; @@ -220,6 +245,12 @@ bool long_lived_allowed(const Request& request, ScreenPhase phase, Lease& out) out.decision = Decision::Busy; return false; } + if (foreground_download_pending_for_other(request) && + messaging_client(request.client)) + { + out.decision = Decision::Busy; + return false; + } if (foreground_download_settle_active_for(request.client, sys::millis_now())) { out.decision = Decision::Busy; @@ -310,6 +341,11 @@ bool ensure_connected(const Request& request, Decision* out_decision) { decision = Decision::Busy; } + else if (foreground_download_pending_for_other(request) && + messaging_client(request.client)) + { + decision = Decision::Busy; + } else if (foreground_download_settle_active_for(request.client, now_ms)) { decision = Decision::Busy; @@ -445,6 +481,117 @@ void release(const Lease& lease) static_cast(held_ms)); } +bool begin_foreground_download(const Request& request, Decision* out_decision) +{ + const std::uint32_t now_ms = sys::millis_now(); + const ScreenPhase phase = sample_screen_phase(now_ms); + Decision decision = Decision::Granted; + + if (request.client == Client::Unknown || + request.kind != AccessKind::HttpDownload || + request.priority != Priority::UserForeground || + !foreground_download_owner(request.client)) + { + decision = Decision::InvalidRequest; + } + else if (::platform::ui::reticulum_call::realtime_mode_active()) + { + decision = Decision::CallExclusive; + } + else if (phase == ScreenPhase::WakeProtected) + { + decision = Decision::DeferredForWake; + } + else + { + portENTER_CRITICAL(&s_lock); + if (s_state.ota_active) + { + decision = Decision::OtaExclusive; + } + else if (s_state.http_active && + !(s_state.owner == request.client && + s_state.active_kind == request.kind)) + { + decision = Decision::Busy; + } + else if (s_state.foreground_download_pending && + !(s_state.pending_owner == request.client && + s_state.pending_kind == request.kind)) + { + decision = Decision::Busy; + } + else + { + s_state.foreground_download_pending = true; + s_state.pending_owner = request.client; + s_state.pending_kind = request.kind; + if (s_state.pending_since_ms == 0) + { + s_state.pending_since_ms = now_ms; + } + } + portEXIT_CRITICAL(&s_lock); + } + + if (out_decision) + { + *out_decision = decision; + } + if (decision == Decision::Granted) + { + std::printf("[WiFiAccess] foreground pending client=%s kind=%s phase=%s reason=%s\n", + client_name(request.client), + access_kind_name(request.kind), + screen_phase_name(phase), + request.reason ? request.reason : ""); + } + else + { + std::printf("[WiFiAccess] foreground pending denied client=%s kind=%s phase=%s decision=%s reason=%s\n", + client_name(request.client), + access_kind_name(request.kind), + screen_phase_name(phase), + decision_name(decision), + request.reason ? request.reason : ""); + } + return decision == Decision::Granted; +} + +void end_foreground_download(Client client, AccessKind kind) +{ + const std::uint32_t now_ms = sys::millis_now(); + std::uint32_t held_ms = 0; + bool cleared = false; + + portENTER_CRITICAL(&s_lock); + if (s_state.foreground_download_pending && + s_state.pending_owner == client && + s_state.pending_kind == kind) + { + held_ms = s_state.pending_since_ms == 0 ? 0 : now_ms - s_state.pending_since_ms; + s_state.foreground_download_pending = false; + s_state.pending_owner = Client::Unknown; + s_state.pending_kind = AccessKind::WifiConnect; + s_state.pending_since_ms = 0; + if (foreground_download_owner(client) && kind == AccessKind::HttpDownload) + { + s_state.foreground_download_settle_until_ms = + now_ms + kForegroundDownloadSettleMs; + } + cleared = true; + } + portEXIT_CRITICAL(&s_lock); + + if (cleared) + { + std::printf("[WiFiAccess] foreground released client=%s kind=%s held_ms=%lu\n", + client_name(client), + access_kind_name(kind), + static_cast(held_ms)); + } +} + TrafficBudget traffic_budget(Client client, Priority priority) { (void)priority; @@ -457,6 +604,8 @@ TrafficBudget traffic_budget(Client client, Priority priority) const bool ota = s_state.ota_active; const Client owner = s_state.owner; const AccessKind active_kind = s_state.active_kind; + const bool foreground_pending = s_state.foreground_download_pending && + foreground_download_owner(s_state.pending_owner); const std::uint32_t foreground_settle_until_ms = s_state.foreground_download_settle_until_ms; portEXIT_CRITICAL(&s_lock); @@ -471,7 +620,9 @@ TrafficBudget traffic_budget(Client client, Priority priority) static_cast(foreground_settle_until_ms - now_ms) > 0; if (ota || - ((foreground_download_active || foreground_download_settle_active) && + ((foreground_download_active || + foreground_pending || + foreground_download_settle_active) && messaging_client(client))) { budget.allow_connect = false; @@ -506,9 +657,13 @@ RuntimeStatus status() portENTER_CRITICAL(&s_lock); out.http_active = s_state.http_active; out.ota_active = s_state.ota_active; + out.foreground_download_pending = s_state.foreground_download_pending; out.owner = s_state.owner; out.active_kind = s_state.active_kind; + out.pending_owner = s_state.pending_owner; + out.pending_kind = s_state.pending_kind; out.active_since_ms = s_state.active_since_ms; + out.pending_since_ms = s_state.pending_since_ms; out.wake_protected_until_ms = s_state.wake_protected_until_ms; portEXIT_CRITICAL(&s_lock); out.screen_phase = sample_screen_phase(now_ms);