onlyn00bs-badge

OnlyN00bs: a DEF CON 34 friend-finder badge. ESP32 firmware, Web Bluetooth setup app, printable case
git clone https://git.virtualshack.io/onlyn00bs-badge.git
Log | Files | Refs | README | LICENSE

clock.h (3815B)


      1 #pragma once
      2 #include <Arduino.h>
      3 #include <Preferences.h>
      4 #include "config.h"
      5 
      6 // ─────────────────────────────────────────────────────────────────────────
      7 //  Soft real-time clock.
      8 //
      9 //  The FireBeetle 2 ESP32-E badge board has no RTC, so we seed wall-clock time from the
     10 //  phone over BLE during provisioning, then free-run off millis(). To survive
     11 //  reboots without re-syncing, we checkpoint the current epoch to NVS every
     12 //  CLOCK_PERSIST_MS and resume from there on boot (downtime isn't counted —
     13 //  fine for a badge that runs continuously through a con).
     14 //
     15 //  If never synced, now() returns 0 and synced() is false → the report labels
     16 //  timestamps as relative rather than wall-clock.
     17 //
     18 //  A brownout reset means we were dark for an unknown span, so the resumed epoch is
     19 //  skewed by that downtime. setup() then calls mark_stale(): synced() goes false
     20 //  (report falls back to relative) and stale() drives a "re-sync" nudge, until a
     21 //  fresh BLE sync (OPTIONS → Provisioning Mode → hosted page) restores trust. The stale
     22 //  flag is persisted, so a later clean power-cycle can't silently un-stale it.
     23 // ─────────────────────────────────────────────────────────────────────────
     24 class SoftClock {
     25 public:
     26   void begin() {
     27     _p.begin("clock", false);
     28     _base_epoch = _p.getUInt("epoch", 0);
     29     _stale = _p.getUChar("stale", 0) != 0;
     30     _base_ms = millis();
     31     _last_persist = millis();
     32   }
     33 
     34   // "synced" = we hold a wall-clock we TRUST. False if never seeded, OR if marked
     35   // stale (e.g. resumed-from-NVS after a brownout, when the badge was dark for an
     36   // unknown span) — callers then fall back to relative time. A BLE re-sync restores it.
     37   bool synced() const { return _base_epoch != 0 && !_stale; }
     38 
     39   // Epoch is present but no longer trustworthy (downtime-skewed). Drives the nudge.
     40   bool stale() const { return _base_epoch != 0 && _stale; }
     41 
     42   uint32_t now() const {
     43     if (!_base_epoch) return 0;
     44     return _base_epoch + (millis() - _base_ms) / 1000;
     45   }
     46 
     47   // Called from the BLE time characteristic — a fresh sync restores trust.
     48   void set(uint32_t epoch) {
     49     _base_epoch = epoch;
     50     _base_ms = millis();
     51     _stale = false;
     52     _p.putUInt("epoch", epoch);
     53     _p.putUChar("stale", 0);
     54   }
     55 
     56   // Mark the persisted epoch untrusted. Persisted so it survives further reboots
     57   // until a re-sync clears it — a clean power-cycle after a brownout must not
     58   // silently "un-stale" a clock that's still skewed by the dark period.
     59   void mark_stale() {
     60     if (!_base_epoch || _stale) return;
     61     _stale = true;
     62     _p.putUChar("stale", 1);
     63   }
     64 
     65   // Factory reset: wipe the persisted epoch + stale flag. The clock lives in its OWN NVS
     66   // namespace ("clock"), so main.cpp's prefs.clear() on the "badge" namespace does NOT
     67   // reach it — without this a "wiped" badge still carries the previous owner's wall clock.
     68   // Caller is expected to restart immediately; the in-RAM state is reset too regardless.
     69   void factory_clear() {
     70     _p.clear();
     71     _base_epoch = 0;
     72     _base_ms = millis();
     73     _stale = false;
     74   }
     75 
     76   // Call often; checkpoints to NVS at most once per CLOCK_PERSIST_MS.
     77   void tick() {
     78     if (!_base_epoch) return;
     79     if (millis() - _last_persist >= CLOCK_PERSIST_MS) {
     80       _last_persist = millis();
     81       _p.putUInt("epoch", now());
     82     }
     83   }
     84 
     85 private:
     86   Preferences _p;
     87   uint32_t _base_epoch = 0;
     88   uint32_t _base_ms = 0;
     89   uint32_t _last_persist = 0;
     90   bool     _stale = false;       // epoch present but downtime-skewed → untrusted
     91 };