Skip to content

eddyCG42/treadmill-sensor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

18 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸƒ Treadmill Sensor

DIY speed, cadence & heart-rate dashboard for the NordicTrack Commercial 2450 β€” feeds a Garmin Fenix 8 with speed, cadence and grade over BLE, adds live BLE heart rate, ToF-based incline sensing, auto-pause, virtual GPS routes and one-tap Strava upload.

License Platform Python Firmware PCB


Run screen

The in-run cockpit on the 800Γ—480 kiosk β€” distance, time, HR zone, pace, live cadence and grade. (Rendered mock-up of the actual UI.)

✨ Features

Feature Details
Live Speed Tracking DRV5023 Hall-effect sensor (separate PCB) β†’ Feather nRF52840 β†’ Pi via USB serial
Real Cadence Footfall cadence detected on the Pi from the frame accelerometer (band-pass + windowed autocorrelation), no foot pod β€” validated Β±4 spm from walk to run, pushed to the Fenix over BLE
Incline / Grade VL53L4CD time-of-flight sensor reads deck tilt; 6-point calibration maps it to % grade, mirrored to the Fenix so watch grade matches the dashboard
Heart Rate Garmin HRM-Pro Plus chest strap β†’ Pi via BLE (bleak) + Fenix 8 via ANT+ simultaneously; Karvonen HR zones
Garmin Fenix 8 Integration Feather broadcasts speed + cadence + grade over BLE to the Fenix 8; HRM-Pro Plus sends HR over ANT+
Web Dashboard Python HTTP server + React/JSX kiosk UI on a HyperPixel touchscreen, mounted on the treadmill arm
Virtual Routes Fixed 400 m athletics track or a random pick from your GPX pool (anti-repeat) for nicer Strava maps
Auto-Pause Detects belt stop β†’ yellow "AUTO-PAUSE" state
Strava Upload One-tap SAVE β†’ export .tcx/.fit and auto-upload via the Strava API, with an offline retry queue
Custom PCBs Hall sensor PCB (60 mm, JLCPCB) + main sensor PCB (Feather + LSM303DLHC + VL53L4CD)
3D-Printable Enclosures OpenSCAD: Pi + HyperPixel arm clamp & Feather sensor mount

πŸ“ Architecture

Architecture

Over BLE the Feather sends the Fenix 8 speed, cadence and grade. The Pi computes cadence (from the frame accelerometer) and grade (from the ToF sensor) and pushes them to the Feather, which relays them to the watch; the Feather keeps its own onboard estimates as a fallback if the Pi push goes stale.


🧰 Hardware

See the full Bill of Materials for part numbers and sourcing.

Core Components

  • Raspberry Pi (3B+ or 4) β€” dashboard host (EddyPi), receives speed data via USB serial, HR via BLE
  • Adafruit Feather nRF52840 β€” sensor MCU on main PCB, running S340 SoftDevice (BLE + ANT+), communicates with Pi over USB serial and broadcasts speed to Garmin Fenix 8 over BLE
  • LSM303DLHC β€” accelerometer/magnetometer on the main PCB; the accelerometer doubles as the cadence pickup (footfall vibration through the frame)
  • VL53L4CD β€” time-of-flight distance sensor on the main PCB, reads deck tilt for incline / grade (6-point calibration β†’ % grade)
  • DRV5023 Hall-Effect Sensor β€” on a separate 60 mm PCB, wired to the Feather, detects belt magnets
  • HyperPixel Display β€” SPI touchscreen dashboard, mounted on treadmill arm
  • Garmin HRM-Pro Plus β€” sends HR to Pi via BLE and to Garmin Fenix 8 via ANT+ simultaneously
  • Garmin Fenix 8 β€” receives speed from Feather (BLE) and HR from HRM-Pro Plus (ANT+)

Custom PCBs

Hall Sensor PCB (60 mm) β€” designed in EasyEDA Pro, manufactured by JLCPCB:

  • ENIG surface finish
  • SMT assembly (DRV5023 bottom-side, Standard PCBA tier)
  • J1 screw terminal for wiring to main PCB
  • Status LED: NationStar NCD0603C1 (0603, yellow-green)

Main Sensor PCB β€” carries the Feather nRF52840 + LSM303DLHC + VL53L4CD:

  • Connects to Hall sensor PCB via wiring
  • USB connection to Raspberry Pi for serial data
  • Designed in EasyEDA Pro

Enclosures (OpenSCAD)

⏳ Planned β€” not yet in this repo (the OpenSCAD sources are still WIP; see Roadmap).

Enclosure Description
pi-hyperpixel-clamp.scad Clamps Pi + HyperPixel to treadmill arm
feather-sensor-mount.scad 30 mm max profile, strap/zip-tie mount for sensor PCB

πŸš€ Getting Started

Prerequisites

  • Raspberry Pi running Raspberry Pi OS (Bookworm or later)
  • Python 3.9+
  • Feather nRF52840 with S340 SoftDevice flashed
  • Strava API application (for upload feature)

1. Clone the Repo

git clone https://github.com/eddyCG42/treadmill-sensor.git
cd treadmill-sensor

2. Install Dependencies

pip install -r dashboard/requirements.txt

3. Configure Strava (optional)

Create ~/strava_config.json on your Pi:

{
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET"
}

⚠️ Never commit your Strava tokens. They are excluded via .gitignore.

4. Pair the HRM-Pro Plus

The dashboard connects via BLE using bleak. Set your strap's MAC as hr_mac in ~/treadmill_config.json (with hr_max / hr_rest for the Karvonen zones).

5. Install & Run (dashboard + kiosk)

cd dashboard/web_dashboard
bash deploy.sh   # installs the systemd services, fetches vendored React/Babel, starts everything

deploy.sh generates and enables the treadmill-server (system) and treadmill-kiosk (user) units. See dashboard/DEPLOY_v12.md for the full coupled firmware + Pi deployment guide.

6. Flash the Feather Firmware

See Firmware Notes below and the Adafruit nRF52 Bootloader S340 PR for Arduino IDE build instructions and S340 SoftDevice setup.


πŸ“ Project Structure

treadmill-sensor/
β”œβ”€β”€ README.md
β”œβ”€β”€ LICENSE
β”œβ”€β”€ .gitignore / .gitattributes
β”œβ”€β”€ docs/
β”‚   └── BOM.md                       ← Bill of materials
β”œβ”€β”€ firmware/
β”‚   └── feather-nrf52840/
β”‚       └── treadmill_sensor_v11_21.ino   ← Arduino sketch (internal v12.1)
β”œβ”€β”€ dashboard/
β”‚   β”œβ”€β”€ treadmill_cadence.py         ← Pi-side footfall / cadence detector
β”‚   β”œβ”€β”€ treadmill_metrics.py         ← shared speed / incline / elevation math
β”‚   β”œβ”€β”€ treadmill_routes.py          ← GPX virtual routes (random + fixed track)
β”‚   β”œβ”€β”€ treadmill_export.py          ← TCX / FIT activity export
β”‚   β”œβ”€β”€ treadmill_hr.py              ← BLE heart-rate monitor
β”‚   β”œβ”€β”€ treadmill_strava.py          ← Strava OAuth + upload
β”‚   β”œβ”€β”€ treadmill_strava_queue.py    ← offline upload queue
β”‚   β”œβ”€β”€ cadence_calibrate.py         ← offline cadence-calibration tool
β”‚   β”œβ”€β”€ requirements.txt
β”‚   β”œβ”€β”€ DEPLOY_v12.md / RECORD_CADENCE.md
β”‚   └── web_dashboard/
β”‚       β”œβ”€β”€ treadmill_server.py      ← HTTP server + run state machine
β”‚       β”œβ”€β”€ deploy.sh / setup.sh
β”‚       β”œβ”€β”€ static/                  ← dashboard.html + *.jsx (React, in-browser Babel)
β”‚       └── kiosk/                   ← systemd units + Chromium kiosk launcher
β”œβ”€β”€ pcb/
β”‚   └── easyeda/                     ← Gerber exports, schematic PDF
└── strava_config.json.example       ← Template (no secrets)

πŸ“Š Dashboard

Home / mode select Cadence calibration
Home screen Cadence calibration

Left: pick the fixed 400 m track or a random GPX route, with a live sensor check before starting. Right: the CADENCE tab — the "VERROUILLAGE DÉTECTEUR" bar shows the Pi footfall detector locking onto your rhythm (green = locked).

Speed calibration Incline calibration (6 points)
Speed calibration Incline calibration

Speed: hold the belt at a known km/h and tap CALIBRER 10s to learn the pace factor. Incline: capture the raw ToF reading at βˆ’3/0/3/6/9/12 % for a piecewise grade map (the firmware mirrors the 0 and 12 % points as a fallback).

End-of-run activity summary (review, then save + auto-upload to Strava):

Activity summary

The web dashboard (dashboard/web_dashboard/treadmill_server.py, a lightweight Python HTTP server + React/JSX kiosk UI) provides:

  • Live speed from the Hall-effect sensor (via Feather β†’ USB serial)
  • Real cadence β€” band-pass + windowed-autocorrelation footfall detection on the Pi from the frame accelerometer, pushed to Garmin over BLE (validated Β±4 spm)
  • Heart rate from HRM-Pro Plus (BLE via bleak) with Karvonen HR zones
  • Incline β€” 6-point ToF calibration, mirrored to Garmin so grade matches
  • Distance & duration tracking, elevation from live grade
  • Auto-pause with delay and yellow visual indicator
  • Virtual routes β€” fixed athletics track or a random pick from your GPX pool
  • SAVE button β†’ exports .tcx/.fit and auto-uploads to Strava (offline queue)

πŸ”§ Treadmill Notes

NordicTrack Commercial 2450 (NTL19124)

  • Uses sensorless speed control via MC1648DLS (back-EMF estimation)
  • The displayed speed is a commanded setpoint, not a direct measurement
  • Accuracy is approximately Β±2.3%
  • This project adds independent speed measurement via belt-mounted magnets and a Hall-effect sensor

πŸ› οΈ Firmware Notes

The Feather nRF52840 runs the S340 SoftDevice (not S140), which provides both BLE and ANT+ support. Key points:

  • Application flash starts at 0x31000 (S340 memory map)
  • Communicates with Raspberry Pi over USB serial (not BLE)
  • Broadcasts speed, cadence and grade over BLE to Garmin Fenix 8 (the Pi pushes cadence/grade via BLE_CAD / BLE_INCLIN; the firmware keeps onboard fallbacks)
  • LSM303DLHC (accel/mag) + VL53L4CD (ToF) share the main sensor PCB
  • Arduino IDE with Adafruit nRF52 BSP (modified for S340)
  • J-Link EDU Mini used for initial SoftDevice flashing and recovery
  • UF2 uploads work normally after initial setup

S340 SoftDevice Setup

The S340 is a closed-source SoftDevice distributed by Garmin Canada. To flash it, follow the instructions in this merged PR on the Adafruit nRF52 Bootloader repo:

feat: add support for SoftDevice S340 β€” PR #359

In short:

  1. Register an ANT+ Adopter account at thisisant.com (access granted within ~1 business day)
  2. Download the S340 v7.0.1 SoftDevice from the ANT+ resources page
  3. Follow the PR's readme.md to place and rename the files in the bootloader repo
  4. Uncomment the evaluation key in nrf_sdm.h (line 191)
  5. Flash the bootloader with SD_NAME=s340 using a J-Link

⚠️ The evaluation key is for development only. A commercial license key from Garmin/Dynastream is required for any product release.


πŸ—ΊοΈ Roadmap

  • Random GPS route generation for non-interval Strava exports (Quebec + worldwide)
  • Web-based dashboard (Python HTTP server + React kiosk UI)
  • Real cadence detection from the frame accelerometer (Garmin footpod parity)
  • Improved enclosure design with snap-fit

πŸ“ License

This project is licensed under the GNU General Public License v3.0 β€” see the LICENSE file for details.


πŸ™ Acknowledgements

  • Adafruit β€” Feather nRF52840, sensors, and learning resources
  • JLCPCB β€” PCB fabrication and SMT assembly
  • bleak β€” Python BLE library
  • Strava API β€” Activity upload integration

Built with ❀️ on a treadmill in Montréal

About

DIY speed sensor, heart-rate dashboard & Strava uploader for the NordicTrack 2450, custom PCBs, Feather nRF52840, Raspberry Pi, and Garmin Fenix 8 integration.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors