#pragma once #include #include #include "event_bus.h" #include "key_value_store.h" #include "service.h" #include "services/storage_service.h" #include "services/wifi_service.h" namespace roro { // Firmware Updates (see CONTEXT.md and docs/milestones/OTA.md): listens for signed Update Files on // TCP 3232 while Wi-Fi is Connected, installs them from the SD card on request, keeps new firmware // on Probation until it proves healthy, and reports a Rollback after the reboot. class UpdateService : public Service { public: enum class Phase { Idle, Receiving, Installed, Failed }; static constexpr uint16_t kPort = 3232; // Call first in setup(): rolls back new firmware that already died once on Probation. static void bootGuard(KeyValueStore& store); UpdateService(KeyValueStore& store, WifiService& wifi, SavedNetworks& saved, StorageService& storage, EventBus& bus, const Settings& settings); const char* name() const override { return "update"; } uint32_t tickIntervalMs() const override { return 1000; } void start() override; void tick(uint32_t nowMs) override; // For the progress screen and the Firmware page. Phase phase() const { return phase_; } int percent() const { return percent_; } std::string incomingVersion() const; bool onProbation() const { return probation_; } // Installs an Update File from the SD card (runs on the storage task). void installFromSd(const std::string& path); // The main loop calls this once it has drawn a frame (part of Probation). void firstFrameDrawn() { firstFrame_ = true; } // True when an installed update is waiting to reboot; the main loop reboots when it's safe. bool rebootPending() const { return phase_ == Phase::Installed; } private: static void taskEntry(void* self); void listen(); void install(class UpdateSource& source, const char* via); void notify(const std::string& text, NotificationLevel level); KeyValueStore& store_; WifiService& wifi_; SavedNetworks& saved_; StorageService& storage_; EventBus& bus_; const Settings& settings_; TaskHandle_t task_ = nullptr; volatile Phase phase_ = Phase::Idle; volatile int percent_ = 0; std::string incoming_; bool probation_ = false; volatile bool firstFrame_ = false; }; } // namespace roro