An open-source, high-performance indoor cycling training application built with the Qt framework (C++17). MaximumTrainer delivers structured interval workouts with real-time power, cadence, heart rate, and speed feedback, and controls smart trainers automatically via FTMS ERG mode. Browse and sync workouts from intervals.icu, or import your own .erg / .mrc / .zwo files.
π Want to use the app? See the User Guide β pairing sensors, finding & building workouts, training, Studio mode, and more.
Prebuilt binaries for each release live on the Releases page β you don't need to build from source unless you want to. Grab the asset for your platform from the latest release:
| Platform | Asset | Notes |
|---|---|---|
| Linux | MaximumTrainer-linux-x86_64.AppImage |
chmod +x it and run β self-contained, no install |
| Windows | MaximumTrainer-windows-x64.msi (installer) or -windows-x64.zip (portable) |
Windows 10 1809+ |
| macOS | MaximumTrainer-macos.dmg |
Open and drag to Applications |
| Web (WASM) | MaximumTrainer-wasm.zip |
Runs in a WebAssembly-capable browser |
Prefer to compile it yourself? See Building below.
| Item | Details |
|---|---|
| Language | C++17 (β 95 % C++) |
| Framework | Qt 6 (6.11+) on all platforms |
| Build file | MaximumTrainer.pro (qmake) |
| Qt modules | core Β· gui Β· widgets Β· network Β· bluetooth Β· webenginewidgets Β· printsupport Β· concurrent |
| Trainer protocol | Bluetooth LE Fitness Machine Service (FTMS / 0x1826) for ERG resistance control |
| Sensor profiles | Heart Rate (0x180D) Β· Cycling Speed & Cadence (0x1816) Β· Cycling Power (0x1818) Β· Moxy Muscle Oxygen (0xAAB0) |
| Workout formats | .erg, .mrc, .zwo (imported and converted to the native XML format) Β· Intervals.icu calendar sync Β· export as .fit |
| Workout source | Integrated intervals.icu for online workout plans |
For everything about using the app β pairing sensors, finding / importing / creating workouts, ERG vs Slope, the workout player (video Β· web Β· Retro Race game), Studio mode, activity uploads, and troubleshooting β see the User Guide.
No hardware? A built-in Simulation mode emits realistic drifting power / HR / cadence / speed and responds to ERG commands, so you can exercise the full workout flow β handy for development and CI β without any Bluetooth devices.
| Workout library |
|---|
![]() |
| Workout editor |
|---|
![]() |
| Devices β pairing & trainer config |
|---|
![]() |
| Workout in progress |
|---|
![]() |
| Studio mode β per-rider configuration |
|---|
![]() |
All three platforms are built and tested automatically via GitHub Actions CI (see .github/workflows/build.yml). Use MaximumTrainer.pro with qmake and a standard C++ compiler.
| Dependency | Version | Platform | Notes |
|---|---|---|---|
| Qt | 6.x (6.11+) | all | Core framework (incl. QtMultimedia, QtWebEngine, QtBluetooth) |
| QWT | 6.3.0 | all | Plotting widgets (built from source against Qt 6) |
Audio & media playback: both sound-effect feedback (interval beeps, etc., via
QSoundEffect) and the embedded video + internet-radio player (QMediaPlayer/QVideoWidget/QAudioOutput) use Qt's own QtMultimedia.
- Windows 10 version 1809 or later is required (the minimum for Qt 6.11). Windows 7, 8, and 8.1 are not supported.
- A Bluetooth 4.0+ adapter with a WDM-compatible driver.
- No special permissions or manifest entries are needed.
Required tools: Qt 6.x (msvc2022_64, with the qtwebengine, qtconnectivity,
qtmultimedia, qtwebchannel, qtpositioning modules), Visual Studio 2022+ (MSVC; Qt 6.11 ships msvc2022 builds only).
Environment variables β set before running qmake:
| Variable | Description | Example |
|---|---|---|
QTDIR |
Qt installation root for the target arch | C:\Qt\6.11.3\msvc2022_64 |
QWT_INSTALL |
QWT installation root (built against Qt 6) | C:/qwt |
qmake invocation:
qmake MaximumTrainer.pro `
"QWT_INSTALL=C:/qwt"The Windows Kit libraries (
Gdi32,User32) are resolved automatically by the MSVC linker.
Download links:
- Qt 6.x: https://www.qt.io/download
- QWT 6.3.0: https://sourceforge.net/projects/qwt/files/qwt/6.3.0/
Install Qt 6 + dependencies, build QWT 6 from source (no Qt6 qwt apt package
exists), then build the app:
sudo apt-get install -y \
qt6-base-dev qt6-webengine-dev qt6-connectivity-dev \
qt6-multimedia-dev qt6-webchannel-dev qt6-positioning-dev \
cmake build-essential
# Build QWT 6.3.0 from source against Qt 6 (installs to /tmp/qwt6 here)
cd /tmp && curl -L -o qwt.tar.bz2 \
"https://sourceforge.net/projects/qwt/files/qwt/6.3.0/qwt-6.3.0.tar.bz2/download"
tar xf qwt.tar.bz2 && cd qwt-6.3.0
qmake6 qwt.pro && make -j$(nproc)
make install INSTALL_ROOT=/tmp/qwt6-stage
mkdir -p /tmp/qwt6 && mv /tmp/qwt6-stage/usr/local/qwt-6.3.0/* /tmp/qwt6/
# Build MaximumTrainer
cd /path/to/MaximumTrainer_Redux
qmake6 MaximumTrainer.pro QWT_INSTALL=/tmp/qwt6
make -j$(nproc)
# Run (QWT in a non-standard prefix needs LD_LIBRARY_PATH):
LD_LIBRARY_PATH=/tmp/qwt6/lib ./build/release/MaximumTrainerWayland: QtWebEngine and some embedded native widgets are more stable under XWayland than the native Wayland QPA plugin, so on a Wayland session the app forces the
xcbplatform automatically. SetQT_QPA_PLATFORMyourself to override.
Uses Qt 6.11.3 with Clang. QWT is built from source (non-framework) against Qt 6.
macOS Bluetooth permission: The app's
mac/Info.plistincludes theNSBluetoothAlwaysUsageDescriptionkey, which is required by macOS 10.15+ for any app that accesses BLE devices. The first time the app attempts to connect a sensor, macOS will prompt for Bluetooth permission. This permission can be reviewed or revoked in System Settings β Privacy & Security β Bluetooth.
# Install Qt 6.11.3 via install-qt-action or the Qt Installer, then:
# Build QWT 6.3.0 from source (non-framework, required for Qt 6)
curl -L -o /tmp/qwt.tar.bz2 "https://sourceforge.net/projects/qwt/files/qwt/6.3.0/qwt-6.3.0.tar.bz2/download"
tar -xjf /tmp/qwt.tar.bz2 -C /tmp && cd /tmp/qwt-6.3.0
sed -i.bak 's/QwtFramework//' qwtconfig.pri
qmake qwt.pro && make -j$(sysctl -n hw.logicalcpu) && sudo make install
# Build MaximumTrainer
cd /path/to/MaximumTrainer_Redux
qmake MaximumTrainer.pro \
"QWT_INSTALL=/usr/local/qwt-6.3.0"
makeMaximumTrainer has a multi-tier test suite covering BLE parsing, service APIs, UI flows, and integration scenarios β all run headlessly in CI without real hardware.
| Suite | Location | What is tested |
|---|---|---|
| BLE unit tests | tests/btle/ |
BLE characteristic parsing (HR, CSC, Power, FTMS), SimulatorHub signal emission, trainer simulations (Elite, Wahoo KICKR, Garmin Tacx), battery level, interval summary |
| Service unit tests | tests/strava/, tests/intervals_icu/ |
HTTP request construction, auth headers, URL patterns, null-manager guards for the Strava and Intervals.icu upload services |
| ZWO importer tests | tests/intervals_icu/ |
Parsing of SteadyState, Ramp, IntervalsT, FreeRide, mixed, and malformed ZWO workout files |
| Credential store tests | tests/credential_store/ |
Round-trip read/write, overwrite, remove, missing key, multi-service, WASM no-op |
| Config bootstrap tests | tests/config_bootstrap/ |
First-run default configuration, legacy settings migration, unreadable settings store, credential load failures logged without values |
| Plan adherence tests | tests/plan_adherence/ |
Completed/skipped/substituted entries, adherence %, encode-decode, change signals |
| Logger tests | tests/logger/ |
Log level filtering, output format, file write enable/disable |
| Studio tests | tests/studio/ |
Multi-hub simultaneous signals, user-ID propagation, FTP scaling |
| Login screen tests | tests/integration/ |
OAuth dialog state, offline login path, Intervals.icu OAuth URL generation |
| Workout UI tests | tests/integration/ |
Full ERG session driven by SimulatorHub through WorkoutDialog; interval advancement; data accumulation |
| Offline / BLE integration | tests/integration/ |
BLE adapter smoke test, offline mode screenshot, runtime validation |
| Playwright (E2E) | tests/playwright/ |
WASM asset loading, Web Bluetooth API mock, login screen, landing page |
cd tests/btle
qmake btle_tests.pro
make -j$(nproc)
../../build/tests/btle_tests -v2The BLE suite has 51 test cases across HR parsing, CSC, Power, FTMS, trainer simulations, SimulatorHub, battery, and interval summary. See the CI run status for pass/fail counts on Linux, Windows, and macOS.
MaximumTrainer writes diagnostic messages (network errors, BLE events, OAuth login steps) to a log file. File logging is enabled by default on first launch; you can adjust the level or disable it in Preferences β Logging.
| Platform | Default log file path | Fallback if the default folder is not writable |
|---|---|---|
| Windows | %APPDATA%\MaximumTrainer\MaximumTrainer_Redux\MaximumTrainer.log(e.g. C:\Users\YourName\AppData\Roaming\MaximumTrainer\MaximumTrainer_Redux\MaximumTrainer.log) |
%TEMP%\MaximumTrainer.log(e.g. C:\Users\YourName\AppData\Local\Temp\MaximumTrainer.log) |
| macOS | ~/Library/Application Support/MaximumTrainer/MaximumTrainer_Redux/MaximumTrainer.log |
$TMPDIR/MaximumTrainer.log(a per-user folder under /var/folders/β¦/T/; run echo $TMPDIR in Terminal to see it) |
| Linux | ~/.local/share/MaximumTrainer/MaximumTrainer_Redux/MaximumTrainer.log(or $XDG_DATA_HOME/MaximumTrainer/MaximumTrainer_Redux/MaximumTrainer.log when $XDG_DATA_HOME is set) |
$TMPDIR/MaximumTrainer.log, which is /tmp/MaximumTrainer.log when $TMPDIR is not set |
| Web (WASM) | No log file; messages go to the browser's developer console | n/a |
The log file is opened before the splash screen appears, and every startup step is logged as it begins and ends, so if the app hangs or crashes while starting, the last line of the log names the step that failed. If the default folder cannot be written, the log goes to the fallback file above and its first lines record the fallback. If neither can be written, messages go to stdout only.
If you chose a custom path in Preferences β Logging, the app switches to it once the configuration has loaded. Anything logged before that point (the startup banner and the configuration steps) is still in the default file, so check both when the app fails early.
On Windows, the log starts with each graphics adapter and its driver version. A crash also logs:
- the DLL and offset that faulted
- the call stack, with function names and source lines for the app's own code
(
MaximumTrainer.pdbships next to the exe) - the graphics-driver and third-party DLLs loaded in the process, such as overlays and antivirus hooks
It also writes MaximumTrainer-crash.dmp next to the log file. Attach both to the bug report.
If the app crashes while the embedded web engine starts (the last log line reads
during GlobalVars: QWebEngineProfile init), the next launch starts it with GPU acceleration
off ("safe graphics") and keeps doing so until the app is updated; the first launch of a new
version tries the GPU again. That launch also saves the web engine's own log in
MaximumTrainer-webengine.log next to the app log; attach it too. Set MT_WEBENGINE_SAFE_MODE=0
to turn safe graphics off again, MT_WEBENGINE_SAFE_MODE=1 to force it on, or launch with
--safe-graphics to try it once. MT_WEBENGINE_LOG=1 saves the web engine's log on any launch.
The Windows build ships its own Visual C++ runtime (msvcp140.dll, vcruntime140*.dll) next to
MaximumTrainer.exe, because Qt 6.11 needs version 14.40 or newer and an older copy in
System32 makes the app crash at startup with a fault in MSVCP140.dll. The log's
C++ runtime: line shows which copy was loaded.
Set the log level to Debug before reproducing an issue (or launch with --debug, or /debug on
Windows), then attach the log file to your bug report.
The Open log file button in the Logging settings page opens the file directly in your default text editor.
For full troubleshooting guidance see the User Guide β Log Files & Troubleshooting section.
The bundled Polarized 3x training plan is adapted from Robin Ingelbrecht's zwift-workouts (MIT license).
Project now going through new revisions, with plans to enhance as an open-source interval trainer for indoor cycling & indoor rowing. backlog and work items can be found here




