Pause messaging while route images download

This commit is contained in:
liu weikai committed 2026-07-11 19:24:32 +08:00
1 parent 5c1a99fd49
commit ca6dac655f
4 files changed
+219 -2

No files matched your search

+1
View File
@@ -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. |
@@ -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();
@@ -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];
@@ -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<unsigned long>(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<unsigned long>(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<std::int32_t>(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);