#pragma once #include #include #include #include #include #include #include #include "cleanup_plan.h" #include "service.h" #include "storage_monitor.h" namespace roro { // Owns the microSD card. All card access happens on this Service's own task (the SD driver isn't // safe across tasks, and a failed mount blocks for seconds): it mounts and watches usage, writes // queued Log lines in batches, lists files for Storage Clean-up, deletes, and formats. class StorageService : public Service { public: // The lock exists from the start, so a Service that's never started (Safe Mode) can still be asked. explicit StorageService(EventBus& bus) : monitor_(bus), bus_(bus), lock_(xSemaphoreCreateMutex()) {} const char* name() const override { return "storage"; } void start() override; void stop() override; StorageState state() const; // Queues one line for a Log file (created with its folders as needed). Dropped while Logs are // paused (over 90 % full, or no card) or if too much is already waiting. A Capture (or a Track, // something the user started) keeps going past 90 %, until the card is full. void appendLine(const std::string& path, const std::string& line, bool capture = false); // The same, for binary files (a pcap Capture): the bytes as they are, no newline. void appendBytes(const std::string& path, const std::string& bytes, bool capture = false); uint32_t droppedLines() const { return dropped_; } // Storage Clean-up: list every file of every category, then delete a selection. void requestListing(); bool listingReady() const; std::vector> listing() const; // one vector per kCleanupCategories entry void requestDelete(std::vector paths); // Runs `job` on the storage task, where card access is safe (e.g. reading an Update File). void runJob(std::function job); // The same, and waits for it (from another task, never the storage task itself). False if it // never started within 5 s: no card mounted, and then it never will run. bool runAndWait(std::function job); // Erases the whole card: one partition spanning the card, formatted FAT32. bool requestFormat(); bool formatting() const { return formatRequested_; } private: static constexpr uint32_t kWakeMs = 1000; static constexpr uint32_t kPollEvery = 15; // wake-ups between usage checks static constexpr size_t kMaxPendingBytes = 16 * 1024; static constexpr uint32_t kSdHz = 20000000; // the card's SPI clock; 10 MHz changed nothing (ADR 0007) static void taskEntry(void* self); void loop(); void poll(); bool mount(); void format(); void writePending(); void list(); void remove(const std::vector& paths); void lock() const { xSemaphoreTake(lock_, portMAX_DELAY); } void unlock() const { xSemaphoreGive(lock_); } StorageMonitor monitor_; EventBus& bus_; bool mounted_ = false; TaskHandle_t task_ = nullptr; mutable SemaphoreHandle_t lock_ = nullptr; // Shared with other tasks, guarded by lock_. struct Pending { std::string path, data; bool newline; }; std::deque pending_; void append(const std::string& path, const std::string& data, bool capture, bool newline); size_t pendingBytes_ = 0; uint32_t dropped_ = 0; bool listingRequested_ = false; bool listingReady_ = false; std::vector> listing_; std::vector toDelete_; std::vector> jobs_; volatile bool formatRequested_ = false; }; } // namespace roro