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

display.h (11812B)


      1 #pragma once
      2 #include "peers.h"
      3 #include "encounters.h"
      4 #include "points.h"
      5 #include "points_history.h"
      6 
      7 // ─────────────────────────────────────────────────────────────────────────
      8 //  Display abstraction. The board-independent core calls these; the concrete
      9 //  implementation is chosen at build time via a -D flag in platformio.ini:
     10 //
     11 //    BADGE_DISPLAY_SERIAL  → display_serial.cpp   (works on ANY ESP32 today;
     12 //                                                  renders to the serial monitor)
     13 //    BADGE_DISPLAY_EINK    → display_eink.cpp     (LilyGo T5 4.7")
     14 //
     15 //  Swapping screens = swapping one .cpp file. The protocol never changes.
     16 // ─────────────────────────────────────────────────────────────────────────
     17 
     18 void display_init();
     19 
     20 // Power the panel down before deep sleep. The SSD1309 draws ~10-20 mA and keeps drawing
     21 // with the SoC asleep — without this the sleep saves almost nothing. One-way by design:
     22 // waking is a full restart through display_init(), so there is no matching _wake().
     23 void display_sleep();
     24 
     25 // Boot splash / one-line status.
     26 void display_boot(const char* line);
     27 
     28 // Animated boot splash, shown once at power-on before the mode screen. OLED
     29 // animates (style = one of BOOT_SPLASH_* in config.h); serial prints a static
     30 // ASCII banner; e-ink draws a static logo. Blocks ~1–1.5s (boot only).
     31 void display_boot_splash(int style);
     32 
     33 // Logo card, shown once at boot immediately AFTER display_boot_splash() and before the
     34 // disclaimer — so it plays whichever splash style the user picked, rather than replacing
     35 // one. The OLED blits the baked bitmaps from logos.h as full-screen frames, sharing the
     36 // BOOT_LOGO_TOTAL_MS budget between them; serial prints a one-line credit. Blocking
     37 // (boot only), ~4.9s on the OLED.
     38 void display_boot_logos();
     39 
     40 // A light-hearted heat + LiPo-safety disclaimer, shown once at boot right after the
     41 // splash. A static full-screen text card; blocking (boot only) — the caller holds it
     42 // on screen (DISCLAIMER_HOLD_MS × BOOT_TIMING_SCALE) then continues the boot sequence.
     43 void display_disclaimer();
     44 
     45 // Provisioning mode: tell the user to connect over BLE, show the device name and the
     46 // 3-char pairing `code` they must enter in the web app to unlock provisioning.
     47 void display_provisioning(const char* device_name, const char* code);
     48 
     49 // Whoami: a read-only identity screen — the badge's handle (username) + eFuse MAC.
     50 void display_whoami(const char* handle, const char* mac);
     51 
     52 // Home view — the at-a-glance default/initial screen: the user's handle, pet face,
     53 // total points, battery %, and wall-clock time. `clock_str` is preformatted by the
     54 // caller ("HH:MM", or "--:--" when the clock is unsynced/stale) so the backends stay
     55 // clock-agnostic; `batt_low` flags the warning threshold for an inline marker.
     56 // `disk_pct` is LittleFS % USED (same polarity as the STORAGE view), printed as a second
     57 // header readout beside the battery — as text; the disk glyph itself is static, unlike the
     58 // battery's proportional one. The caller supplies it from its cached figures — usedBytes()
     59 // must never be walked from a live redraw.
     60 // `radio_on` is the Badge Settings → Stealth switch (inverted: stealth ON = radio off).
     61 // When it's false the badge is NOT broadcasting, and HOME says so with an inverse STEALTH
     62 // chip along the bottom: a silent badge with no on-screen tell is indistinguishable from a
     63 // broken one, and this is the default view.
     64 void display_home(const char* handle, const char* pet_face, uint32_t points,
     65                   uint8_t batt_pct, bool batt_low, uint8_t disk_pct, const char* clock_str,
     66                   bool radio_on);
     67 
     68 // The finder list renders this many peers per screen (one "page"); main.cpp uses it for
     69 // the page count + wrap math, the OLED backend for its row slice. Fixed by the 64px panel.
     70 #define FINDER_PAGE_ROWS 5
     71 
     72 // Finder mode: render the sorted peer list (closest first). The OLED header is a
     73 // static "// finder" title (matching the other view headers); each peer row shows
     74 // that peer's handle. `my_handle` is used only by the serial backend's diagnostic
     75 // line. show_rssi toggles the OLED right column: false = peer's pet face, true =
     76 // dBm. `mult` is the active group points-multiplier (1 = none; 2/3 show an
     77 // indicator chip in the header — see POINTS_X2_PEERS / POINTS_X3_PEERS). `page` is the
     78 // 0-based page of the list (FINDER_PAGE_ROWS per screen); the OLED renders that slice
     79 // and shows "p/P" in the header when P>1. The serial backend lists all peers, ignoring it.
     80 void display_peers(const char* my_handle, const Peer* peers, int n, bool show_rssi,
     81                    uint8_t mult, int page);
     82 
     83 // Report mode: the "social graph of your con" — aggregated friend stats.
     84 void display_report(const char* my_handle, const Report& r);
     85 
     86 // Points mode: gamified proximity score. `points` is 1 per full minute spent
     87 // with crew close enough to clear the RSSI gate; `prox_seconds` is the
     88 // underlying tally (its sub-minute remainder drives a live countdown to the
     89 // next point). `peers_now` is the current in-range crew count; `earning` is
     90 // whether the tally is accruing right now (≥1 crew AND within the RSSI gate AND not
     91 // stealthed) — so the view can distinguish "no crew" from "crew here but too far".
     92 // `stealth` says WHY it's paused when that's the reason: stealth stops scoring, and crew
     93 // can be in range and in-gate at the time, so "too far" would be an outright lie. Check
     94 // it before the distance case.
     95 // `hist`/`hist_n` are the timestamped points-over-time samples (oldest→newest)
     96 // for the line graph on this view; `hist_n` may be 0 (nothing sampled yet).
     97 // `mult` is the active group multiplier (1/2/3): ≥2 shows the indicator chip and
     98 // scales the "+1 pt in Ns" countdown (the tally ticks ×mult per wall second).
     99 void display_points(const char* my_handle, uint32_t points,
    100                     uint32_t prox_seconds, int peers_now, bool earning,
    101                     const PointSample* hist, int hist_n,
    102                     uint32_t graph_span_s, const char* window_label, bool anchor_newest,
    103                     uint8_t mult, bool stealth);
    104 
    105 // Best-friends mode: the top crew by close-time shared with you, most first.
    106 // `top[i].seconds / 60` is the minutes you spent near friend i; `total_points`
    107 // is your whole score. Each friend is ≤ the total, but they overlap (a group
    108 // minute credits everyone), so they don't sum to it. `n` is how many are in
    109 // `top` (may be < 3 early on, 0 before you've scored anything).
    110 void display_friends(const char* my_handle, const PointFriend* top, int n,
    111                      uint32_t total_points);
    112 
    113 // Battery mode: coarse LiPo fuel gauge for "charge tonight?" decisions. `mv` is
    114 // the divider-corrected battery voltage in millivolts, `pct` the 0..100 charge
    115 // estimate (linear on voltage — slightly pessimistic mid-range, the safe way),
    116 // `low` flags that we're at/below the warning threshold.
    117 void display_battery(const char* my_handle, uint16_t mv, uint8_t pct, bool low);
    118 
    119 // STORAGE readout — the BATTERY view's single-tap alternate. `used`/`total` are LittleFS
    120 // bytes, cached by the caller (usedBytes() walks the filesystem, so it must not be re-read
    121 // on every live redraw). Reports USED, per disk convention — the bar fills as the disk
    122 // fills, the opposite polarity to the battery face it otherwise mirrors — with the
    123 // remaining space shown alongside as a free-bytes figure. `full` is the caller's
    124 // disk-guard verdict (same >=90% trip that trims the log), drawn as a warning.
    125 void display_storage(const char* my_handle, size_t used, size_t total, bool full);
    126 
    127 // Pet selection screen — the badge companion: name header + a big ASCII face (no
    128 // stats readout). `face` is the current state-face (idle / reaction / lonely /
    129 // low-batt), already resolved by the caller. Tap swaps the pet.
    130 void display_pet(const char* name, const char* face);
    131 
    132 // Settings MENU — currently the boot-splash picker. `splash_sel` is
    133 // 0..BOOT_SPLASH_COUNT-1 (radar / terminal / glitch / matrix).
    134 void display_menu(int splash_sel);
    135 
    136 // Transient reaction pop-up: a big pet face + a reason line (line1), with an
    137 // optional detail line (line2 — pass nullptr for a single-line toast). Briefly
    138 // overlays the current view when an event fires off the PET view (cooldown by caller).
    139 void display_pet_popup(const char* face, const char* line1, const char* line2,
    140                        uint16_t hold_ms);
    141 
    142 // NON-BLOCKING toast — same look as display_pet_popup (big face + 1–2 lines) but it
    143 // draws and returns immediately (no hold/delay). The caller owns its lifetime: it
    144 // stays on screen until the caller redraws (dismissed by a button press / timeout in
    145 // the main loop). Used for reminder alerts, which must not freeze beaconing/scoring.
    146 void display_toast(const char* face, const char* line1, const char* line2);
    147 
    148 // OPTIONS menus. `screen` selects which one to draw: ROOT (Back · User Settings ·
    149 // Badge Settings), USER (Back · Pet · Finder View · Reminders), BADGE (Back · Display ·
    150 // Stealth · Developer Options · Power), DISPLAY (Back · Splash), DEVOPTS (Back · Whoami ·
    151 // Debug · Provisioning Mode), REMIND (Back · Global · Shower), or POWER (Back · Reboot ·
    152 // Deep sleep). A cursor list with Back first (the default row). OptView carries the
    153 // dynamic row values; each screen reads only the fields it renders — beats a positional
    154 // const char* per row as the tree grows.
    155 // NOTE: BADGE is at 5 rows, which is the renderer's hard cap (`rows[5]` + the y0/dy
    156 // ladder). A sixth row there needs the array and the ladder widened first — which is why
    157 // POWER exists as a sub-menu: it holds Reboot + Deep sleep in the slot Reboot alone used
    158 // to occupy, instead of growing BADGE past the cap.
    159 enum OptMenu { OPTM_ROOT = 0, OPTM_USER, OPTM_BADGE, OPTM_DISPLAY, OPTM_DEVOPTS, OPTM_REMIND,
    160                OPTM_POWER };
    161 struct OptView {
    162   const char* pet;             // USER    — active pet name
    163   const char* splash;          // DISPLAY — boot-splash style name
    164   const char* bright;          // DISPLAY — "LOW"/"MED"/"HIGH"
    165   const char* test_state;      // DEVOPTS — "ON"/"OFF"
    166   const char* finder_mode;     // USER    — "pet"/"dBm"
    167   const char* remind_global;   // REMIND  — "ON"/"OFF"
    168   const char* remind_shower;   // REMIND  — "ON"/"OFF"
    169   // BADGE — "ON"/"OFF" for STEALTH, which is the INVERSE of the internal g_radio_on:
    170   // stealth ON = not broadcasting. The caller does the inversion; don't re-flip it here.
    171   const char* stealth;
    172 };
    173 void display_options_menu(int screen, int cursor, const OptView& v);
    174 
    175 // Factory-reset confirmation. Its own screen rather than a row in DEVOPTS because the
    176 // menu's activate gesture is a double-tap: one fumbled press on a bare "Factory Reset" row
    177 // would wipe someone's whole con with no way back. Spells out what goes, and `cursor` 0 is
    178 // Cancel — the default landing row, so the destructive option is never the one under the
    179 // cursor when you arrive. 1 = confirm.
    180 void display_reset_confirm(int cursor);
    181 
    182 // Screen brightness. `level` indexes the SCREEN_BRIGHT_* table (0=LOW, 1=MED, 2=HIGH);
    183 // out-of-range falls back to SCREEN_BRIGHT_DEFAULT. The OLED backend maps it to the
    184 // panel's contrast register; serial has no panel and no-ops. Must be called AFTER the
    185 // display is up (display_init() claims the I2C bus) — and, for the saved pick, after
    186 // NVS is open, which is later than display_init() in setup().
    187 void display_set_brightness(uint8_t level);