From 44de78ea62546edf339988f5f2f29e5a28ed164c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Martin?= Date: Fri, 2 Oct 2026 10:14:27 +0200 Subject: [PATCH] Storage: poll the SD card on its own task A failed mount retries for ~2.5 s, which froze the UI every 15 s while no card was inserted. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT --- src/main.cpp | 4 +-- src/services/storage_service.cpp | 45 +++++++++++++++++++++++++------- src/services/storage_service.h | 15 ++++++++--- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7d4af22..60d93d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,7 +35,7 @@ static void draw() { d.setTextColor(TFT_WHITE); const auto& b = battery->estimator(); - const auto& s = storage->state(); + const auto s = storage->state(); int y = 16; auto line = [&](const String& text) { d.drawString(text, 4, y); @@ -122,7 +122,7 @@ void loop() { if (millis() - lastLog > 5000) { lastLog = millis(); - const auto& s = storage->state(); + const auto s = storage->state(); Serial.printf("status: batt %d%% %dmV raw %d | sd %s %d%% total %lluMB | heap %u min %u | screen %d\n", battery->estimator().percent(), battery->estimator().millivolts(), battery->lastRawMillivolts(), s.present ? "yes" : "no", s.usedPercent, diff --git a/src/services/storage_service.cpp b/src/services/storage_service.cpp index f694e45..8a4354b 100644 --- a/src/services/storage_service.cpp +++ b/src/services/storage_service.cpp @@ -7,11 +7,40 @@ namespace roro { +void StorageService::start() { + if (task_) return; + lock_ = xSemaphoreCreateMutex(); + xTaskCreate(taskEntry, "storage", 4096, this, 1, &task_); +} + +void StorageService::stop() { + if (!task_) return; + vTaskDelete(task_); + task_ = nullptr; + if (mounted_) SD.end(); + mounted_ = false; +} + +StorageState StorageService::state() const { + xSemaphoreTake(lock_, portMAX_DELAY); + StorageState copy = monitor_.state(); + xSemaphoreGive(lock_); + return copy; +} + +void StorageService::taskEntry(void* self) { + auto* service = static_cast(self); + for (;;) { + service->poll(); + vTaskDelay(pdMS_TO_TICKS(kPollMs)); + } +} + bool StorageService::mount() { return SD.begin(pins::kSdCs, sharedSpi(), 20000000, "/sd", 5, false); } -void StorageService::tick(uint32_t) { +void StorageService::poll() { if (mounted_) { // Detect removal: the root can no longer be opened. File root = SD.open("/"); @@ -22,15 +51,13 @@ void StorageService::tick(uint32_t) { } if (!mounted_) mounted_ = mount(); - if (mounted_) - monitor_.update(true, SD.totalBytes(), SD.usedBytes()); - else - monitor_.update(false, 0, 0); -} + bool present = mounted_; + uint64_t total = present ? SD.totalBytes() : 0; + uint64_t used = present ? SD.usedBytes() : 0; -void StorageService::stop() { - if (mounted_) SD.end(); - mounted_ = false; + xSemaphoreTake(lock_, portMAX_DELAY); + monitor_.update(present, total, used); + xSemaphoreGive(lock_); } } // namespace roro diff --git a/src/services/storage_service.h b/src/services/storage_service.h index 90f5ca1..bcfc39c 100644 --- a/src/services/storage_service.h +++ b/src/services/storage_service.h @@ -1,25 +1,34 @@ #pragma once +#include +#include + #include "service.h" #include "storage_monitor.h" namespace roro { // Mounts the microSD card (retrying while absent) and feeds its usage to the StorageMonitor. +// Runs on its own task: a failed mount blocks for seconds and must never stall the UI. class StorageService : public Service { public: explicit StorageService(EventBus& bus) : monitor_(bus) {} const char* name() const override { return "storage"; } - uint32_t tickIntervalMs() const override { return 15000; } - void tick(uint32_t nowMs) override; + void start() override; void stop() override; - const StorageState& state() const { return monitor_.state(); } + StorageState state() const; private: + static constexpr uint32_t kPollMs = 15000; + static void taskEntry(void* self); + void poll(); bool mount(); + StorageMonitor monitor_; bool mounted_ = false; + TaskHandle_t task_ = nullptr; + mutable SemaphoreHandle_t lock_ = nullptr; }; } // namespace roro