Skip to content

feat: health stats grid as an alternative to the calendar - #174

Open
unki wants to merge 3 commits into
mattrossman:mainfrom
unki:feat/health-stats-view
Open

unki wants to merge 3 commits into
mattrossman:mainfrom
unki:feat/health-stats-view

Conversation

@unki

@unki unki commented Sep 13, 2026

Copy link
Copy Markdown

Really enjoying this watchface on my PT2. Personally, I don't get much use out of the calendar, but I'd love to see some health metrics in its place. Inspired by dataload, this PR adds an option to replace the calendar with a metrics grid, with a configurable choice of data points (steps, heart rate, distance, etc.).

Not sure if there is interest in such bigger change, as it adds quite a bit that wasn't there before: new settings, infrastructure to retrieve the data points, and the grid itself. It also extracts a few existing pieces, like the battery indicator and the status icons, so the grid can reuse them. The calendar keeps using the same code and looks unchanged.

Summary

  • Opt-in health stats grid that replaces the 3-week calendar in the top band
  • Configurable tiles: steps, distance, heart rate, activity and more
  • Calendar stays the default
  • Enable via Settings → Top band → Show in top band → Health stats grid

Screenshots

basalt emery
2026-09-13T19-30-53-basalt 2026-09-13T19-31-00-emery

Features

  • Grid size
    • emery: 3×2 tiles
    • basalt, diorite, flint: 2×2 tiles
    • aplite: not available (no health API), settings section hidden
  • Metrics per slot: Activity, Battery, Calories, Date, Distance, Heart rate,
    Steps, Sunrise, Sunset, Temperature, UV index, Weekday, Empty
  • Indicator bars (emery)
    • Calories: vs. 500 kcal
    • Activity: % of the user's typical activity by this time of day
    • UV index, battery level
  • Heart rate graph (emery): last hour as a thin line behind the BPM value
  • Status icons in the grid's top row
    • Quiet time / bluetooth left, battery right
    • Aligned with the tile titles; titles move aside only as far as needed
    • Battery gauge and tile update immediately on battery changes
    • Disconnect vibration works in both modes
  • Distance unit follows the Pebble app's Health setting, falling back to the
    temperature unit
  • Settings only offer what the watch supports
    • Metrics the watch can't supply are hidden (e.g. heart rate without sensor)
    • Only as many slot pickers as the watch displays
    • Slots numbered left to right, top to bottom, with the watch's grid size shown (2x2 / 3x2)
    • Calendar settings greyed out while the stats grid is selected (values kept)
  • Missing or stale data shows --

Implementation

  • New stats grid layer and metrics module on the watch, slot settings in Clay
  • Refactor: battery gauge, status icons and hatch fill shared between calendar
    and grid (calendar renders pixel-identical)
  • Heart rate history is battery-friendly
    • Only reads what the firmware already logs, no extra sensor sampling
    • Read at most every 5 min, cached in between
  • UV index taken from the existing weather response, no extra requests
  • Existing persistence keys untouched, new ones appended
  • Settings saved by older versions upgrade safely: the new fields start at their defaults
  • Adds the health capability: the Pebble app asks users for health permission
  • Integer-only math (see perf: reduce aplite heap usage by avoiding software math #163)
  • New stats fixture for emulator screenshots

unki and others added 3 commits September 13, 2026 21:36
Move the night hatch helpers out of forecast_layer.c into appendix/draw,
the battery gauge out of battery_layer.c into appendix/battery_indicator,
and the quiet-time/bluetooth icons out of calendar_status_layer.c into
appendix/status_icons, so another top-band view can draw them too.

The connection service subscription moves from calendar_status_layer to
main_window, keeping the disconnect vibration independent of which layer
is visible. Rendering is unchanged: calendar-mode fixture screenshots are
pixel-identical on aplite, basalt, diorite, emery and flint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6abcjuotEL5NKg6nVavm2
Add a "top band" setting that swaps the calendar and its status row for a
grid of configurable stat tiles (3x2 on emery, 2x2 on 144px watches):
steps, distance, active calories, activity vs. typical, heart rate, UV
index, battery, current temperature, sunrise, sunset, date and weekday.
Calories, activity, UV and battery draw an indicator bar. aplite has no
health API and keeps the calendar only.

- The watch reports which metrics it can supply, so the settings page only
  offers real options (e.g. heart rate only with a sensor).
- Bluetooth/quiet-time icons and the battery gauge sit in the grid's top
  row, aligned with the tile titles; titles stay centred on their tile and
  only move aside as far as an icon (or the charging bolt) requires.
- Distance follows the unit picked in the Pebble app's Health settings,
  falling back to the temperature unit when none was picked.
- The UV index comes from the already-fetched weather response.
- Fixtures gain watch.health and weather.uvIndex for emulator screenshots,
  plus a new fixtures/stats.json.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6abcjuotEL5NKg6nVavm2
Draw the last hour of heart rate as a thin dark-red line behind the
HEART tile's title and value on emery, like dataload2 does.

The history comes from health_service_get_minute_history, i.e. only what
the firmware already logs: no sample-period request, no health event
subscription. The 60 minute records are read at most every 5 minutes, only
while the grid is drawn with a heart rate tile, folded into 30 averaged
points and cached; the raw records live on the heap for that call only.
Gaps between readings are bridged, and the vertical scale spans at least
20 bpm so a steady resting pulse stays a calm line.

Fixtures gain an optional watch.health.heart_rate_history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6abcjuotEL5NKg6nVavm2
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