Convert CSV files from indoor cycling bikes to TCX format for Strava, Garmin Connect, TrainingPeaks, and Golden Cheetah. All processing runs in the browser via WebAssembly, no server needed.
Try it at upload.bike. If the site is down, grab
the standalone build from the
latest release. Just
unzip and open index.html in any browser.
- Schwinn MPower Echelon (3 firmware variants)
- Stages Indoor Cycles
- The Sufferfest
- Wahoo SYSTM
- NordicTrack Studio Cycles
- PRO-FORM iFit
Don't see your bike? Email your CSV to upload.bike@gmail.com.
cargo run -- --csv input.csv --tcx output.tcx
Options:
--csv <FILE>- input CSV file (required)--tcx <FILE>- output TCX file (required)--time <TIME>- workout start time (defaults to file timestamp)--interpolate- resample to 1-second intervals (recommended)--model <MASS_KG>- use physics model for speed and distance--lint <FILE>- check a TCX file for errors/warnings, exit 1 on errors
--interpolate resamples source data to 1-second intervals using linear
interpolation, which never overshoots the source min/max or invents spikes.
Power/cadence are clamped to >= 0, HR zeros are forward-filled, and distance is
kept monotonic. Any whipsaws or HR dropouts in the output come from the source
data, not interpolation.
--lint <FILE> checks any TCX file (your own, or one produced by
--interpolate) for structural problems and implausible values:
cargo run --release -- --lint output.tcx
Errors (E001-E036) cause exit code 1; warnings (W013-W038) print but exit 0.
See crates/mpowertcx-core/src/linter.rs for the full list of checks.
The static site in web/ runs entirely client-side. To preview locally:
cd web && python3 -m http.server
Then open http://localhost:8000.
wasm-pack build crates/mpowertcx-wasm --target web --out-dir ../../web/pkg
A self-contained HTML file with the full converter built in. All CSS, JS,
and WASM are inlined into a single index.html. It runs from file://,
inside a zip without extracting, or anywhere. No server, no install.
python3 scripts/build-offline.py
Output: web/downloads/upload.bike-portable.zip.
Contains index.html (self-contained) and README.txt.
A GitHub Actions workflow (.github/workflows/build-offline.yml) builds
the zip automatically. Push a tag like v3.1.0 to trigger it:
git tag v3.1.0
git push origin v3.1.0
The workflow creates a GitHub Release with the zip attached.
Download it and copy to web/downloads/. You can also run it manually
from the Actions tab (workflow_dispatch).
Integration tests compare output against the 26 sample CSVs in samples/,
covering plain, physics-model, and interpolated conversions:
cargo test -p mpowertcx-core
The report_parsers test prints which parser handled each sample file:
cargo test -p mpowertcx-core report_parsers -- --nocapture
Shell scripts for byte-exact comparison of plain and model outputs:
bash tests/test_samples.sh
bash tests/test_samples_advanced.sh
crates/
mpowertcx-core/ conversion library (no I/O, WASM-compatible)
mpowertcx-cli/ command-line binary
mpowertcx-wasm/ WebAssembly build for the web frontend
scripts/
build-offline.py builds the portable offline zip
web/
index.html converter page
how-it-works.html about page with interactive physics graph
download.html offline version download page
custom.css shared styles (Pico CSS plus custom rules)
theme.js light/dark theme toggle
preview-chart.js workout preview chart for the converter (ES module)
chart-demo.js physics model chart engine (ES module)
icon.svg site logo
pkg/ WASM package (generated by wasm-pack)
downloads/ built offline zip (gitignored)
samples/ sample CSV embedded in the WASM for the chart demo
samples/ test CSVs and expected TCX outputs
The original Python/PySide2 desktop app is no longer in this repository. Browse the last revision that still included it:
https://github.com/j33433/MPowerTCX/tree/legacy-python/legacy
GPL-3.0