Skip to content

Add four clock faces (Diagnostics, Battery/Uptime, Rainbow, Smiley), display performance/stability fixes, and per-machine PlatformIO config - #170

Open
0m4r wants to merge 7 commits into
ktomy:mainfrom
0m4r:main
Open

Add four clock faces (Diagnostics, Battery/Uptime, Rainbow, Smiley), display performance/stability fixes, and per-machine PlatformIO config#170
0m4r wants to merge 7 commits into
ktomy:mainfrom
0m4r:main

Conversation

@0m4r

@0m4r 0m4r commented Jul 25, 2026

Copy link
Copy Markdown

Summary

This branch adds four new clock faces, a set of display performance and stability
fixes, and a per-machine PlatformIO configuration workflow so contributors don't
have to edit tracked files to flash their own board. It builds on ulanzi_debug
(pio run -e ulanzi_debug) and has been verified on hardware (Ulanzi TC001).

PlatformIO configuration

  • Per-machine overrides via extra_configsplatformio.ini now merges an
    untracked platformio.local.ini on top of the tracked config, so your serial
    port and upload speed never end up in a commit. (platformio.ini)
  • New template documenting upload_port / upload_speed per OS; copy it to
    platformio.local.ini to get started. (platformio.local.ini.example)
  • Safer defaultsupload_speed lowered to a conservative 115200 (override
    to 921600 locally once stable), and the hard-coded upload_port removed so
    PlatformIO auto-detects the port. (platformio.ini)
  • Ignore the local override so it's never committed. (.gitignore)
  • Helper scripts read the local override first and fall back to
    platformio.ini; added --fs and --all flows for filesystem/full-device
    flashing. (scripts/build.sh, scripts/upload.sh, scripts/reset.sh)
  • Emulator usage docs — a docstring covering prerequisites, CLI modes, handy
    test values, and a curl equivalent for driving the device's API source.
    (scripts/ns_emulator.py)

Code changes

Display performance

  • Removed the redundant blank-frame blit on every refresh (each WS2812
    matrix->show() is a ~7–8 ms interrupts-disabled blit), eliminating a visible
    flash and wasted work. (src/BGDisplayManager.cpp, src/DisplayManager.cpp)
  • Diagnostics/text faces refresh at a frame rate only while content actually
    scrolls, and fall back to a static, idle per-minute refresh when it fits.
    (src/BGDisplayFaceDiagnostics.cpp/.h, src/BGDisplayFaceTextBase.cpp/.h)
  • Cache the date/time string off the frame hot path
    (src/BGDisplayFaceDiagnostics.cpp/.h); single map lookup in getTextWidth
    (src/DisplayManager.cpp); unit-specific refresh-time tracking
    (src/BGDisplayManager.cpp/.h).

Display correctness / shared state

  • Reset the global font on every face switch so faces that render text without
    pinning a font no longer inherit a leaked LARGE font and clip; added an
    onActivate() hook to reset per-face view state on (re)selection, including the
    boot/default face. (src/BGDisplayManager.cpp, src/BGDisplayFace.cpp/.h,
    src/BGDisplayFaceDiagnostics.cpp/.h)
  • Pin the compact font for standalone/status messages so they stay centered
    (src/DisplayManager.cpp fatal errors, src/BGSource.cpp "To API" prompt);
    fixed a missing break in setFont. (src/DisplayManager.cpp)

Peripherals

  • Low-battery beep no longer cuts off an active melody; lowered buzzer volume.
    (src/PeripheryManager.cpp)

Docs

  • Documents the new Smiley face and the ns_emulator.py testing flow. (README.md)
  • Notes the platformio.local.ini workflow and build/flash scripts.
    (CONTRIBUTING.md)
  • Repository guidelines updated for the local-override config and emulator.
    (AGENTS.md)

New screens

Four new clock faces, registered in src/BGDisplayManager.cpp/.h and selectable
via the default-clock-face dropdown in the web UI (data/index.html):

  • Diagnostics (6) — scrolling date/time alongside the BG value and trend
    arrow; scrolls only when the content overflows, otherwise static.
    (src/BGDisplayFaceDiagnostics.cpp/.h)
  • Battery and uptime (7) — device battery percentage and uptime.
    (src/BGDisplayFaceBatteryUptime.cpp/.h)
  • Rainbow big text (8) — the large BG value cycling through rainbow colors.
    (src/BGDisplayFaceBigTextRainbow.cpp/.h)
  • Smiley (9) — a round mood face beside a big centered reading: happy/green
    in range, sad/blue below the low limit, angry/red above the high limit, and a
    grey neutral face for no/stale data. Wide mmol/L readings fall back to a compact
    font so they never clip. (src/BGDisplayFaceSmiley.cpp/.h)

0m4r added 7 commits March 28, 2026 14:41
- add a scrolling diagnostics face and a separate battery/uptime face
- support smoother face refreshes and batched display updates
- color BG trend arrows by glucose severity
- add low-battery warning beeps
- expose new clock faces in the web UI
- make upload/reset scripts use platformio.ini port and speed settings
… override (#1)

The tracked platformio.ini hardcoded a specific serial device path
(upload_port) and a machine-tuned upload_speed, which broke flashing on
other machines and created merge noise against upstream.

Now:
- platformio.ini drops the hardcoded upload_port (PlatformIO auto-detects
  it) and keeps a conservative universal upload_speed = 115200. It pulls
  per-machine values from an untracked platformio.local.ini via
  extra_configs.
- platformio.local.ini.example documents the local file; .gitignore
  excludes platformio.local.ini so local settings never get committed.
- scripts/upload.sh and scripts/reset.sh read platformio.local.ini first
  and fall back to platformio.ini, with a helpful message when upload_port
  is unset.
- CONTRIBUTING.md and AGENTS.md updated to describe the new workflow.

scripts/upload.sh and scripts/reset.sh now print which config file each
value was read from (platformio.local.ini vs platformio.ini) and the
resolved values, so it's obvious the correct settings are being used.
read_ini emits "value|sourcefile" (the previous global-variable approach
didn't work because the function runs in a command-substitution subshell).
Small, self-contained improvements to the shared display path and the new
clock faces, plus a new face. Builds clean for ulanzi_debug.

Performance & stabilization:
- Avoid a blank-frame blit on each display refresh (clearMatrix(false) +
  single trailing update); removes a ~7-8ms WS2812 blit and a visible flash
  per frame.
- Track refresh time in unit-specific fields (lastRefreshMillis /
  lastRefreshEpochSec) so a face switch never compares millis against epoch
  seconds and underflows.
- Cache the Diagnostics date/time string: read the clock at most ~1x/sec and
  rebuild only on minute change, removing per-frame getLocalTime() stalls and
  String heap churn.
- Halve Diagnostics redraw load (~60fps -> ~30fps, scroll step rescaled so
  on-screen speed is unchanged) and fall back to per-minute refresh when the
  content fits instead of redrawing 30x/s.
- Use a single map find() in getTextWidth instead of count() + operator[]
  (avoids a double traversal and a non-const insert in the frame hot path).
- Don't let the low-battery beep cut off an active melody: only beep when the
  shared MelodyPlayer is idle, retry on the next poll otherwise.
- Fix standalone messages (showFatalError, the "To API" connecting text)
  appearing shifted to the top: pin FONT_TYPE::SMALL before rendering so the
  y=6 baseline stays centered even after a big-text face left the large font
  active.

New feature:
- Smiley clock face (ktomy#9): an 8x8 face whose mood reflects the reading vs the
  configured limits - happy/green in range, sad/blue below the low limit,
  angry/red above the high limit, neutral/grey when there's no data - with
  the numeric reading beside it. Added to the default-clock-face selector in
  the web UI.
Draw the smiley as a filled disc in the mood color (green/blue/red/grey)
with the eyes and mouth punched out as dark cutouts, so it reads as a
round face rather than loose features. Render the glucose value in the
large font, centered in the space beside the face, instead of the small
right-aligned text.

Scale the disc's mood color down (~60%) via a small RGB565 fade helper so
the round face reads as a soft badge behind the crisp cutout features and
the reading, which stay at full brightness.

The Smiley and big-text faces leave the large font active in the global
currentFont. Diagnostics measured and drew its datetime line without
setting a font, so the first frame after switching from one of those
faces was computed and rendered in the large font (yAdvance 8) at the
y=7 baseline and clipped. Pin FONT_TYPE::SMALL at the top of both render
paths.

Faces are constructed once and reused, so the Diagnostics contentScrolls
flag (which gates needsFrequentRefresh) persisted across switches: if the
face was last shown with static content it stayed false, so returning to
it could schedule the first frame at the per-minute rate instead of the
scroll rate. Add a virtual onActivate() hook, called from setFace, and
re-arm contentScrolls=true in the Diagnostics override.

The reading is centered in the ~24px beside the face. A 4-character
mmol/L value (e.g. "22.3") in the large font (~27px) overflowed and was
clipped off the right edge. Measure the value in the large font and, only
when it does not fit, drop to the small font so it is always centered and
fully visible. mg/dL and short mmol/L values keep the large font.

Add the Smiley face to the clockfaces table and the feature list, and
update the clockface count. The table image is left as a placeholder
until a device screenshot is available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant