A cross-platform indoor cycling application built in Rust with real-time sensor integration, structured workouts, and comprehensive ride analytics.
- Bluetooth Smart Trainer Support - Connect to FTMS-compatible smart trainers and power meters via Bluetooth LE
- Real-time Metrics - Live power, cadence, heart rate, and speed display with configurable smoothing
- Training Zones - Power and heart rate zones with visual indicators and time-in-zone tracking
- Structured Workouts - Import and execute workouts from ZWO (Zwift) and MRC/ERG formats
- ERG Mode - Automatic resistance control to match target power during workouts
- Voice Alerts - Text-to-speech announcements for interval changes, countdowns, and workout events
- Voice Control - Hands-free workout control via offline Vosk speech recognition (optional feature)
- Ride Recording - Automatic recording with pause detection and lap markers
- Strava Sync - Automatic upload to Strava with OAuth authentication, token refresh, and retry support
- Export Formats - Export rides to FIT, TCX, and CSV for upload to TrainingPeaks, Garmin Connect, etc.
- Ride History - Browse past rides with filtering, sorting, and detailed analytics
- Offline-First - All data stored locally in SQLite, no account required
| Platform | Architecture | Status |
|---|---|---|
| Windows | x64 | Supported |
| macOS | Intel (x64) | Supported |
| macOS | Apple Silicon (ARM64) | Supported |
| Linux | x64 | Supported |
- Windows: Windows 10 or later, Bluetooth LE adapter
- macOS: macOS 11 (Big Sur) or later, built-in Bluetooth
- Linux: X11 or Wayland, BlueZ 5.x, Bluetooth LE adapter
Download the latest release for your platform from the Releases page.
Requires Rust 1.75 or later.
# Clone the repository
git clone https://github.com/ProvidenceIT/rust-ride.git
cd rust-ride
# Build release binary
cargo build --release
# Run
./target/release/rustride# Ubuntu/Debian
sudo apt-get install libdbus-1-dev pkg-config libxcb-render0-dev \
libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev \
libgtk-3-dev libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev
# For voice alerts (text-to-speech)
sudo apt-get install speech-dispatcher libspeechd-devRustRide includes voice announcements for hands-free workout guidance. Voice alerts announce:
- Interval changes with target power and duration
- Countdown warnings before interval transitions (10, 5, 3, 2, 1 seconds)
- Workout start, pause, resume, and completion
- Optional motivational cues during high-intensity and recovery intervals
Configure voice alerts in Settings > Voice Alerts:
- Voice Selection - Choose from available system voices
- Speech Rate - Adjust how fast announcements are spoken (0.5x - 2.0x)
- Volume - Control voice alert volume independently from sound effects
- Per-Alert Configuration - Enable/disable voice vs. sound for specific alert types
Text-to-speech uses the built-in Windows SAPI (Speech API). This is available by default on Windows 10 and later.
Troubleshooting:
- If voices are not available, ensure text-to-speech is enabled in Windows Settings > Time & Language > Speech
- Additional voices can be installed through Windows Settings > Time & Language > Speech > Manage voices
- For PowerShell installation:
Add-WindowsCapability -Online -Name Language.Speech~~~en-US~0.0.1.0
Text-to-speech uses AVSpeechSynthesizer, which is built into macOS 11 (Big Sur) and later.
Troubleshooting:
- Voices are managed in System Preferences > Accessibility > Spoken Content
- Additional voices can be downloaded through System Preferences > Accessibility > Spoken Content > System Voice > Manage Voices
Text-to-speech uses speech-dispatcher, which must be installed separately.
Installation:
# Ubuntu/Debian
sudo apt-get install speech-dispatcher speech-dispatcher-espeak-ng
# Fedora
sudo dnf install speech-dispatcher speech-dispatcher-espeak-ng
# Arch Linux
sudo pacman -S speech-dispatcher espeak-ngTroubleshooting:
- Verify speech-dispatcher is running:
speech-dispatcher --version - Test speech from command line:
spd-say "Hello world" - If no audio, check that pulseaudio or pipewire is running
- Ensure your user is in the
audiogroup:sudo usermod -aG audio $USER
- Thread Safety: TTS operations run on a dedicated worker thread to handle platform-specific constraints (especially on macOS where the TTS engine is not thread-safe)
- Voice Availability: Available voices depend on the system language packs installed
- Linux Virtual Environments: TTS may not work in Docker containers or VMs without proper audio passthrough configuration
- Simultaneous Speech: Only one voice announcement plays at a time; high-priority alerts (interval changes) interrupt lower-priority ones
RustRide supports hands-free workout control using offline voice recognition powered by Vosk. This is an optional feature that must be enabled at compile time.
Build RustRide with the voice-control feature:
cargo build --release --features voice-controlOn first use, a Vosk speech model (~50MB) will be downloaded automatically.
| Command | Phrases | Action |
|---|---|---|
| Pause | "pause", "stop", "hold" | Pause the current ride/workout |
| Resume | "resume", "continue", "unpause" | Resume paused activity |
| Skip | "skip", "next interval", "skip interval" | Skip to next workout interval |
| End | "end workout", "finish", "done" | End the current workout |
| Take Lap | "take lap", "lap", "mark lap" | Mark a lap |
| Status | "status", "how am I doing" | Announce current metrics |
Commands also work with common misrecognitions (e.g., "paws" → pause, "resoom" → resume).
Voice control can be activated in three ways:
- Wake Word - Say "Hey Rust Ride" or "OK Ride" to begin listening for 5 seconds
- Push-to-Talk - Hold the F4 key (configurable) while speaking
- Always Listening - Continuously listen for commands (higher battery usage)
Configure the activation mode in Settings > Voice Control.
When a command is recognized:
- An audio tone confirms recognition
- TTS speaks the action (e.g., "Pausing")
- A visual indicator shows the recognized text
- Microphone access (Settings > Privacy > Microphone)
- Audio input device
- Microphone permission (System Preferences > Security & Privacy > Microphone)
- Audio input device
# Install audio capture dependencies
sudo apt install libasound2-dev # ALSA
# or
sudo apt install libpulse-dev # PulseAudio
# Ensure user has audio group membership
sudo usermod -aG audio $USER| Issue | Solution |
|---|---|
| "No microphone found" | Check microphone is connected and permissions are granted |
| Model download fails | Verify internet connection; check disk space (~100MB needed) |
| Commands not recognized | Speak clearly; check microphone volume; try push-to-talk mode |
| Recognition timeout | Speak within 5 seconds of wake word; try push-to-talk mode |
All voice recognition happens locally on your device using the Vosk engine. No audio is ever sent to the cloud. The downloaded model runs entirely offline.
- Launch the application - Run the
rustrideexecutable - Connect sensors - Go to Sensor Setup and scan for Bluetooth devices
- Configure FTP - Set your Functional Threshold Power in Settings for accurate zone calculations
- Start riding - Return to Home and click "Start Ride"
RustRide can automatically upload your rides to Strava after each session. To enable this feature, you need to set up Strava API credentials.
- Log in to Strava - Visit www.strava.com and sign in to your account
- Go to API Settings - Navigate to www.strava.com/settings/api
- Create Your Application:
- Application Name: Enter a name (e.g., "RustRide")
- Category: Select "Training"
- Club: Leave blank (optional)
- Website: Enter any valid URL (e.g., "https://github.com/ProvidenceIT/rust-ride")
- Application Description: Brief description of your use
- Authorization Callback Domain: Enter
localhost
- Accept the API Agreement and click "Create"
After creating your application, you'll see:
- Client ID: A numeric identifier (e.g.,
12345) - Client Secret: A long alphanumeric string (keep this secure!)
⚠️ Security Note: Never share your Client Secret publicly. Treat it like a password.
Set the following environment variables before launching RustRide:
Windows (PowerShell):
$env:STRAVA_CLIENT_ID = "your_client_id"
$env:STRAVA_CLIENT_SECRET = "your_client_secret"
.\rustride.exeWindows (Command Prompt):
set STRAVA_CLIENT_ID=your_client_id
set STRAVA_CLIENT_SECRET=your_client_secret
rustride.exemacOS / Linux:
export STRAVA_CLIENT_ID="your_client_id"
export STRAVA_CLIENT_SECRET="your_client_secret"
./rustrideFor persistent configuration, add these to your shell profile (.bashrc, .zshrc, or Windows environment variables).
- Open RustRide and go to Settings > Strava
- Click "Connect to Strava"
- Your browser will open for Strava authorization
- Log in and click "Authorize" to grant RustRide access
- Return to RustRide - you should see your Strava profile
Once connected, RustRide provides:
- Auto-sync: Automatically upload rides after each session (configurable)
- Manual retry: Retry failed uploads from Ride History
- Token refresh: Automatic token refresh when expired
- Upload status: View sync status in Ride History and Ride Detail screens
| Issue | Solution |
|---|---|
| "Authorization required" error | Ensure STRAVA_CLIENT_ID and STRAVA_CLIENT_SECRET are set correctly |
| Browser doesn't open | Check if the authorization URL is displayed in the console |
| Upload fails repeatedly | Verify your internet connection and Strava service status |
| "Token expired" after reconnect | Disconnect and reconnect your Strava account |
Strava imposes rate limits on API usage:
- 100 requests per 15 minutes
- 1,000 requests per day
RustRide is designed to stay well within these limits for normal usage. If you encounter rate limiting, wait 15 minutes before retrying.
Place .zwo files in the workouts directory. These are XML-based files with support for:
- Steady-state intervals
- Ramps (gradual power changes)
- Free ride sections
- Text instructions
Place .mrc or .erg files in the workouts directory. These are text-based files with:
- Time and power percentage pairs
- Course header information
All data is stored locally:
- Database:
~/.rustride/rustride.db(SQLite) - Config:
~/.rustride/config.toml - Workouts:
~/.rustride/workouts/
# Run tests
cargo test
# Run with debug logging
RUST_LOG=debug cargo run
# Check formatting
cargo fmt --check
# Run clippy lints
cargo clippy --all-targets --all-features -- -D warnings
# Build documentation
cargo doc --no-deps --opensrc/
├── app.rs # Main application state and event loop
├── main.rs # Entry point
├── lib.rs # Library exports
├── audio/ # Audio alerts and text-to-speech
│ ├── tts.rs # Cross-platform TTS provider (SAPI/AVSpeech/speech-dispatcher)
│ ├── engine.rs # Audio engine with priority queue
│ ├── cues.rs # Message templates for workout announcements
│ └── workout_bridge.rs # Bridges workout events to audio alerts
├── voice/ # Voice control (optional: voice-control feature)
│ ├── model_manager.rs # Vosk model download and initialization
│ ├── audio_input.rs # Microphone capture via cpal
│ ├── recognizer.rs # Vosk speech recognition wrapper
│ ├── engine.rs # Voice recognition pipeline
│ ├── command_parser.rs # Fuzzy command matching
│ ├── wake_word.rs # Wake word detection
│ ├── push_to_talk.rs # Push-to-talk activation
│ ├── feedback.rs # Audio feedback for voice events
│ └── error.rs # User-friendly error handling
├── sensors/ # Bluetooth sensor management and FTMS parsing
├── metrics/ # Real-time metrics calculation and zones
├── recording/ # Ride recording and export (TCX, CSV)
├── workouts/ # Workout parsing (ZWO, MRC) and execution engine
├── storage/ # SQLite database and configuration
└── ui/ # egui-based user interface
├── screens/ # Application screens (home, ride, settings, etc.)
├── widgets/ # Reusable UI components
└── theme.rs # Visual styling
MIT License - see LICENSE for details.