#pragma once #include "configuration.h" #include #include /** * TransmitHistory persists the last broadcast transmit time (epoch seconds) per portnum * to the filesystem so that throttle checks survive reboots/crashes. * * On boot, modules call getLastSentToMeshMillis() to recover a millis()-relative timestamp * from the stored epoch time, which plugs directly into existing throttle logic. * * On every broadcast transmit, modules call setLastSentToMesh() which updates the * in-memory cache and flushes to disk. * * Keys are meshtastic_PortNum values (one entry per portnum). */ #include "mesh/generated/meshtastic/portnums.pb.h" class TransmitHistory { public: static TransmitHistory *getInstance(); /** * Load persisted transmit times from disk. Call once during init after filesystem is ready. */ void loadFromDisk(); /** * Record that a broadcast was sent for the given key right now. * Stores epoch seconds and flushes to disk. */ void setLastSentToMesh(uint16_t key); #ifdef PIO_UNIT_TESTING /** * Directly set the stored epoch for a key without touching the runtime lastMillis map. * Intended for testing purposes: lets tests simulate "the last broadcast happened N * seconds ago" without needing to fake the system clock. */ void setLastSentAtEpoch(uint16_t key, uint32_t epochSeconds); /** * Directly set a boot-relative timestamp (seconds since boot) for testing. */ void setLastSentAtBootRelative(uint16_t key, uint32_t secondsSinceBoot); #endif /** * Get the raw persisted timestamp seconds for a given key, or 0 if unknown. * * The returned value is an absolute epoch when persisted with valid RTC/NTP/GPS time, * or boot-relative seconds when ENTRY_FLAG_BOOT_RELATIVE is set. */ uint32_t getLastSentToMeshEpoch(uint16_t key) const; /** * Convert a stored epoch timestamp into a millis()-relative timestamp suitable * for use with Throttle::isWithinTimespanMs(). * * Returns 0 if no valid time is stored or if the stored time is in the future * (which shouldn't happen but guards against clock weirdness). * * Example: if the stored epoch is 300 seconds ago, and millis() is currently 10000, * this returns 10000 - 300000 (wrapped appropriately for uint32_t arithmetic). */ uint32_t getLastSentToMeshMillis(uint16_t key) const; /** * Flush dirty entries to disk. Called periodically or on demand. * * @return true if the data is persisted (or there was nothing to write), false on write/open failure. */ bool saveToDisk(); /** * Wipe in-memory throttle state + remove the on-disk file. Required * alongside rmDir("/prefs") in factoryReset — otherwise the 5-min * auto-flush resurrects the file from the still-populated maps. */ void clear(); private: TransmitHistory() = default; static constexpr const char *FILENAME = "/prefs/transmit_history.dat"; static constexpr uint32_t MAGIC = 0x54485354; // "THST" static constexpr uint8_t VERSION = 2; static constexpr uint8_t MAX_ENTRIES = 16; static constexpr uint32_t SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes static constexpr uint32_t BOOT_RELATIVE_RECOVERY_WINDOW_SEC = 2 * 60; static constexpr uint32_t LEGACY_BOOT_RELATIVE_MAX_SEC = 365UL * 24 * 60 * 60; enum EntryFlags : uint8_t { ENTRY_FLAG_NONE = 0, ENTRY_FLAG_BOOT_RELATIVE = 0x01, }; struct StoredTimestamp { uint32_t seconds = 0; uint8_t flags = ENTRY_FLAG_NONE; }; struct __attribute__((packed)) Entry { uint16_t key; uint32_t epochSeconds; uint8_t flags; }; struct __attribute__((packed)) LegacyEntry { uint16_t key; uint32_t epochSeconds; }; struct __attribute__((packed)) FileHeader { uint32_t magic; uint8_t version; uint8_t count; }; uint32_t getLastSentAbsoluteMillis(uint32_t storedEpoch) const; uint32_t getLastSentBootRelativeMillis(uint32_t storedSeconds) const; static StoredTimestamp makeStoredTimestamp(uint32_t seconds, uint8_t flags = ENTRY_FLAG_NONE); static StoredTimestamp decodeLegacyTimestamp(uint32_t seconds); std::map history; // key -> persisted transmit time std::map lastMillis; // key -> millis() value (for runtime throttle) bool dirty = false; uint32_t lastDiskSave = 0; // millis() of last disk flush }; extern TransmitHistory *transmitHistory;