#pragma once #include #include #include "event_bus.h" #include "nmea_parser.h" #include "service.h" #include "services/clock_service.h" #include "services/storage_service.h" #include "settings.h" #include "track.h" namespace roro { // The GNSS receiver on the Cap LoRa-1262 (see CONTEXT.md and docs/milestones/M2.md): reads its // NMEA from the main loop's tick (about 450 bytes/s, so a 512-byte UART buffer covers a second), // holds the current Fix, and sets the clock from it. Settings → GNSS switches it on and off. class GnssService : public Service { public: static constexpr int kRxPin = 15, kTxPin = 13; // measured: M2 step 1 static constexpr uint32_t kBaud = 115200; static constexpr uint32_t kSilentAfterMs = 3000; // no sentence for this long: receiver silent static constexpr uint32_t kClockEveryMs = 600000; // refresh the clock from the Fix this often // Off (Q58, measured): "$PCAS12," puts the receiver in standby for that long, and any // command wakes it. Standby is renewed while off, in case the duration is clamped. static constexpr uint32_t kStandbyRenewMs = 3600000; GnssService(const Settings& settings, ClockService& clock, StorageService& storage, EventBus& bus); const char* name() const override { return "gnss"; } uint32_t tickIntervalMs() const override { return 50; } void start() override; void stop() override; void tick(uint32_t nowMs) override; bool on() const { return active_; } // Standby while the LoRa radio listens, when the user chose that (issue #20): the receiver // raises the radio's noise floor by 8 dB while it runs. Never asked for during a Track. void holdForLora(bool hold) { held_ = hold; } bool heldForLora() const { return held_ && !active_; } bool receiving(uint32_t nowMs) const { return active_ && lastLineMs_ && nowMs - lastLineMs_ < kSilentAfterMs; } const gnss::GnssState& state() const { return parser_.state(); } uint32_t onSinceMs() const { return openedMs_; } // when the receiver was last woken // Debug aids (Q68): raw sentences to the console, a status report, and sending a command // ("PCAS10,0" becomes "$PCAS10,0*1C\r\n"). void setEcho(bool echo) { echo_ = echo; } void printStatus(Print& out, uint32_t nowMs) const; bool send(const std::string& body); // Tracks (Q63): GPX on the SD card, a point every 5 s once moved 5 m, recorded in the // background whatever App is open. startTrack() returns why it can't, or "". std::string startTrack(uint32_t nowMs); void stopTrack(); bool tracking() const { return !trackPath_.empty(); } const std::string& trackPath() const { return trackPath_; } int trackPoints() const { return trackPoints_; } uint32_t trackStartMs() const { return trackStartMs_; } void restart(uint32_t nowMs) { close(); open(nowMs); } private: void open(uint32_t nowMs); // the UART; the receiver's state follows the setting in tick() void close(); void wake(uint32_t nowMs); void recordPoint(uint32_t nowMs); void closeUnfinishedTracks(); // on the storage task void notify(const std::string& text); void standby(uint32_t nowMs); const Settings& settings_; ClockService& clock_; StorageService& storage_; EventBus& bus_; gnss::NmeaParser parser_; bool open_ = false, active_ = false, echo_ = false, held_ = false; uint32_t bytes_ = 0, standbyMs_ = 0; uint32_t openedMs_ = 0, lastLineMs_ = 0, firstFixMs_ = 0, clockSetMs_ = 0; gnss::FixType lastFix_ = gnss::FixType::None; std::string trackPath_; int trackPoints_ = 0; uint32_t trackStartMs_ = 0; gnss::TrackRule trackRule_; }; } // namespace roro