Contents
Motivation
I’ve been building a custom e-paper dashboard for athletes using Strava and Garmin — combining my interests in endurance training, minimalist design, and smart hardware.
The result?
A custom e-paper dashboard powered by a Raspberry Pi and ESP32, designed specifically for athletes who use Strava, Garmin, or both.
Main components
- Waveshare 7.5” e-paper screen
- For the prototype phase
- Waveshare e-Paper ESP32 Driver Board
- Raspberry Pi
I will be updating the list according to the project phases.
What does it do?
Each day, the system generates a visual snapshot of my training and weather data, displayed on a silent, always-on 7.5” e-paper screen. The layout adapts depending on whether I’ve trained that day:
-
Active days: it shows my most recent activity (distance, pace, time) and a 3-day weather forecast.
-
Rest days: it switches to a more reflective view, including weekly/monthly summaries and a small calendar that highlights training days in red.
Everything is generated by a Raspberry Pi backend written in Python, then pulled and displayed by an ESP32 that drives the e-paper display.
Why build this?
Like many athletes, I use both Strava and Garmin. But I’ve always felt that their apps and widgets are noisy, distracting, and sometimes inconsistent across platforms.
I wanted something quieter.
Something calm, visual, and glanceable — not another app or tab I forget to open.
That’s why I built this e-paper dashboard for athletes who rely on both Strava and Garmin, and wanted something quieter and more visual.
This dashboard will live on my desk, silently updating itself every hour. No glowing screen. No popups. Just clean, real-time progress.
Technical aspects
Configuration & Environment
-
python-dotenv: loads secrets (
STRAVA_CLIENT_ID
,STRAVA_CLIENT_SECRET
,OWM_API_KEY
,GOOGLE_API_KEY
,GITHUB_TOKEN
, etc.) from a.env
file. -
packaging.version: compares semantic version strings for the self-update feature. All synchronizaed with the latest Github release available.
Authentication & Token Management
-
stravalib.client.Client: handles Strava OAuth2 flows, token exchange & refresh, and fetching activities/streams.
-
garminconnect + garth: logs into Garmin Connect (OAuth1 under the hood), retrieves training readiness, status, intensity minutes, etc.
-
Tokens persisted in JSON (locally saved); helper functions
load_tokens()
,save_tokens()
, andtokenfresh()
manage expiry and automatic refresh.
Data Retrieval
-
Strava:
-
Latest activity in the last 24 h (filtering out short walks)
-
Aggregated stats (distance, time, elevation, active days) for today, current week, last 4 weeks, and month
-
Streams (
altitude
,heartrate
,distance
) viaget_activity_streams()
-
-
OpenWeatherMap (via
requests
):-
Current conditions (min/max T, wind, sunrise/sunset, rain, and a “fresco/ventoso” summary)
-
3-day forecast with dominant condition and min/max temps
-
-
Google Static Maps + polyline:
-
Decodes Strava’s polyline, requests a styled B&W map, extracts the red path overlay, and applies rounded-corner masking
-
-
Geopy: reverse-geocodes the first coordinate to display the city/town name
Visualization & Composition
-
Pillow (PIL):
-
Canvas setup (800×480) with preselected background color
-
Text, icons, lines, shapes (today as a red-outline circle), and pasting sub-images with rounded borders
-
-
Matplotlib + NumPy + SciPy:
-
Elevation & Heart-Rate Charts: smoothed line plots, filled areas, custom axes/ticks tuned for E-Ink
-
-
Plotly:
-
Gauges for recovery time & ACWR (Angular half-gauges rendered via Kaleido)
-
-
Custom Radar Chart:
-
Distance (black fill) and time (red outline) for Run / Ride / Hike / Walk with numeric labels and legend
-
Self-Update via GitHub
-
Uses GitHub REST API to compare
__version__
vs. the latest release tag in a private repo, auto-download and extract if newer
Modular & Extensible Design
-
Section-based: each widget (map, stats block, calendar, weather, gauges, charts) is drawn in its own function returning a small PIL image, then pasted at configurable coordinates
-
Enums & constants (
THIS_MONTH
,LAST_4_WEEKS
,THIS_WEEK
) drive period-based queries -
Orchestrator
draw_dashboard()
stitches all pieces into the final layout
Deployment & Usage
-
Raspberry Pi (Pi 4 or 5 ): runs the script hourly (cron or scheduler), saving
dashboard.bmp
and serving it via Flask/FastAPI. -
ESP32: fetches
dashboard.bmp
over HTTP and renders it on the E-Ink panel.
By leveraging OAuth-powered data ingestion, multi-library charting, and low-level image composition, this backend delivers a fully automated, multi-source fitness dashboard optimized for ultra-low-power E-Ink displays.
What’s next for this e-paper dashboard?
This e-paper dashboard for Strava and Garmin is still evolving, and I plan to release new templates and customization options soon.
This is just the beginning. I’m currently:
-
Working on customizable templates and layout options.
- Evaluating options on the cloud so I dont use the raspberry pi as backend.
-
Refining the logic for merging Strava and Garmin data.
-
Preparing a lightweight version of the code to release publicly.
-
Design of enclousre for wall mount or as desk portrait.
- Design of a dedicated ESP32 board thet fits smoothly the frame.
- Refine power saving features on the ESP32 side, like I did in my Paper ink Weather station
I’ll also be documenting the process here in future posts — from the software to the hardware, and even the mistakes I’ve made along the way.
Want to support the project?
I’m building this in my spare time, and any support helps me keep going — whether that’s covering hardware costs or fueling the next round of development. Support the project here
Thanks for reading — feel free to leave a comment or reach out if you have questions, ideas, or just want to say hi!