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);