This guide covers hardware setup, firmware building, and device configuration.
| Item | Specification | Notes |
|---|---|---|
| ESP32-S3 DevKitC-1 | N16R8 (16MB Flash, 8MB PSRAM) | Or compatible ESP32-S3 board |
| Rowing Machine | With reed switches | Crivit or similar |
| USB-C Cable | Data capable | For programming and power |
| Item | Purpose |
|---|---|
| Android/Wear OS Watch | Heart rate monitoring via BLE |
| BLE Heart Rate Strap | Alternative HR monitoring |
| Android Phone | Companion app for Health Connect sync |
The ESP32 connects directly to the rowing machine's reed switches. Internal pull-up resistors are used, so no external components are needed.
ESP32-S3 DevKitC Pinout:
╔════════════════════════════════════════════════╗
║ Power: ║
║ USB-C → ESP32 (provides power) ║
║ ║
║ Sensor Inputs: ║
║ GPIO 15 ← Flywheel reed switch ║
║ GPIO 16 ← Seat position reed switch ║
║ GND ← Combined sensor grounds ║
╚════════════════════════════════════════════════╝
- Locate the flywheel reed switch (usually white wire)
- Locate the seat position switch (usually red wire)
- Connect flywheel switch to GPIO 15 and GND
- Connect seat switch to GPIO 16 and GND
- Combine all ground wires to a single ESP32 GND pin
Note: Reed switches are normally open. They close briefly when a magnet passes.
- Install ESP-IDF v6.0+
- Set up the ESP-IDF environment variables
Important: This project requires ESP-IDF 6.0 or later for the modular driver components.
# Clone the repository
git clone https://github.com/j0b333/ESP32RowingMachine.git
cd ESP32RowingMachine
# Set target to ESP32-S3
idf.py set-target esp32s3
# Configure (optional - defaults are in sdkconfig.defaults)
idf.py menuconfig
# Build
idf.py build
# Flash and monitor
idf.py -p /dev/ttyUSB0 flash monitorReplace /dev/ttyUSB0 with your serial port (e.g., COM3 on Windows).
If you encounter build errors after updating:
idf.py fullclean
rm sdkconfig
idf.py set-target esp32s3
idf.py build- Power on the ESP32 (connect USB-C)
- The device creates a WiFi access point:
- SSID:
CrivitRower - Password: None (open network)
- SSID:
- Connect your phone/tablet to this network
- Open a browser and navigate to
http://192.168.4.1
Note: The AP uses an open network for maximum compatibility with ESP32-S3. Once you configure your home WiFi credentials, the device will connect to your router and use its security.
The web interface displays real-time metrics:
| Metric | Description |
|---|---|
| Distance | Total distance rowed (meters) |
| Time | Elapsed workout time |
| Pace /500m | Time per 500 meters |
| Power | Instantaneous power (watts) |
| Stroke Rate | Strokes per minute (SPM) |
| Calories | Estimated energy burned |
| Heart Rate | BPM from connected monitor |
Access settings via the ⚙️ button:
- Weight - Your weight for calorie calculation
- Units - Metric or Imperial
- Display - Show/hide power and calories
- Auto-pause - Pause workout after inactivity
The BLE Heart Rate client feature is disabled by default to reduce build complexity.
-
Edit
main/app_config.h:#define BLE_HR_CLIENT_ENABLED 1
-
Run
idf.py menuconfigand enable:Component config → Bluetooth → NimBLE Options → Roles:- ✅ Enable BLE Central role
- ✅ Enable BLE Observer role
- ✅ Enable BLE GATT Client support
-
Clean rebuild:
idf.py fullclean idf.py build
- Install "Heart for Bluetooth" on your Android/Wear OS watch
- Start the app and go to the Activity screen
- Power on the ESP32 rowing monitor
- The ESP32 automatically scans, connects, and subscribes
- Heart rate appears on the web interface
Pairing Window: You have 120 seconds to pair. Restart the beacon on the watch if needed.
See Heart for Bluetooth for more information.
For use with fitness apps (Kinomap, EXR, MyHomeFit, etc.):
- Start a workout on the ESP32 web interface
- Open your fitness app
- Search for Bluetooth devices
- Connect to "Crivit Rower"
- Rowing data streams via FTMS protocol
The drag coefficient auto-calibrates during rowing. Row for 50+ strokes to stabilize the drag factor reading.
For accurate power readings, calibrate the flywheel inertia:
- Go to Settings → Click "Calibrate Inertia"
- Give the flywheel one strong pull
- Let it coast to a complete stop (don't touch it!)
- The system calculates and saves the moment of inertia
See Physics Model for calibration details.
Sync workouts to Samsung Health / Google Fit:
- Install ESP32RowingMachineCompanionApp
- Connect to the same network as the ESP32
- View and sync workout sessions
- Sessions appear in Samsung Health via Health Connect
- Check wiring connections
- Verify GPIO pins (15 for flywheel, 16 for seat)
- Check serial monitor for debug output
- Ensure device is powered (check LED)
- Verify SSID and password
- Try connecting via IP:
192.168.4.1 - Move closer to the ESP32
- Ensure Bluetooth is enabled on your phone
- Check if another device is already connected (max 3)
- Restart the ESP32
- Row for 50+ strokes to calibrate drag
- Run moment of inertia calibration
- Check reed switch alignment
- Ensure ESP-IDF v6.0+ is installed
- Run clean build:
idf.py fullclean - Delete
sdkconfigand rebuild
Edit main/app_config.h for advanced settings:
| Setting | Default | Description |
|---|---|---|
GPIO_FLYWHEEL_SENSOR |
15 | Flywheel GPIO pin |
GPIO_SEAT_SENSOR |
16 | Seat sensor GPIO pin |
DEFAULT_MOMENT_OF_INERTIA |
0.101 | Flywheel inertia (kg⋅m²) |
WIFI_AP_SSID_DEFAULT |
"CrivitRower" | WiFi network name |
WIFI_AP_PASS_DEFAULT |
"" | WiFi password (empty = open network for ESP32-S3 compatibility) |
BLE_DEVICE_NAME_DEFAULT |
"Crivit Rower" | Bluetooth name |