BLE smart trainer
Standard FTMS over Bluetooth LE. Pair your Wahoo Kickr Core or any FTMS-compatible trainer. No vendor SDK, no Google Play Services.
Indoor cycling · Android · GPLv3
Structured ERG workouts for your smart trainer. Build them in your browser, beam them to your phone with a QR code, ride them against your FTP. Everything stays on your device.
The whole idea
The workout editor runs right here on this site - no install, no sign-up. Draw your intervals, point your phone at a QR code, and the app takes over your trainer. That's the entire pipeline.
01 · In your browser
Lay out intervals as coloured blocks in the editor. Durations, %FTP, repeats - tweak until the shape feels right. Works on any laptop or tablet.
02 · One tap in the app
Tap "Send to phone" in the editor, scan the QR straight from the app's home screen. The workout lands in your library and opens, ready to start. No cloud in between.
03 · On the trainer
ERG mode drives the trainer to each interval's wattage. You hold the cadence, it holds the watts. The finished ride saves to your phone as a file - export FIT any time.
What's inside
Every screen is designed for arm's-length legibility on a sweaty phone. Telemetry first, decoration last.
Standard FTMS over Bluetooth LE. Pair your Wahoo Kickr Core or any FTMS-compatible trainer. No vendor SDK, no Google Play Services.
The app drives your trainer to hit each interval's target wattage automatically. You hold the cadence, it holds the watts.
Z1 recovery through Z7 neuromuscular, calibrated as percentages of your FTP. Visualised on every workout, every interval, every chart.
Every workout is a readable JSON file. Use the bundled library, drop in your own, hand-craft them in a text editor, or build them visually with the workout editor.
Pair a Zwift Click v1 controller to nudge target power up or down mid-interval. On-screen +/- buttons work too. Your handlebar, your call.
Rides, FTP, weight, paired devices: all on your phone. No telemetry leaves the device. Optional sync is opt-in and lazily loaded.
Workout library
Sweet Spot, Threshold, VO2. The same intervals you already know from other training apps, structured against your FTP and ridden in the same colour-coded zones.
Threshold
VO2 Max
Community libraries
A workout library is just static files: a small manifest.json
plus the workouts it points at. Host it on any web space - a Codeberg
or GitHub Pages site, your blog, a folder on your NAS. No server code,
no gatekeeper, no permission needed.
You publish
{
"schemaVersion": 2,
"id": "gravel-base",
"name": "Gravel Base Builder",
"author": "Your Name",
"bundles": [
{ "id": "base",
"version": 3,
"url": "base.json" }
]
}
manifest.json · anywhere HTTPS
Riders add it
browse the pack, pick workouts
They own their copy
Every workout a rider adds becomes a local file on their phone - theirs to ride, rename, or edit. If your library changes later, nothing breaks and nothing is silently swapped underneath them.
local-first, always
Publish your winter plan for the whole club. Ship a pack alongside
your coaching. Curate the intervals you swear by. The in-app
Browse community screen lists public libraries from the
registry at indoorbike.app/registry.json - open a pull
request to get yours listed, or share your manifest URL directly
and skip the registry entirely.
Open API
Third-party tools - training planners, AI coaches, analytics apps - can generate a workout and hand it to a rider as a plain URL. The link opens the editor pre-loaded; the rider tweaks it and beams it to their phone. No backend, no OAuth, no callback. No user data ever crosses a wire.
One URL is the whole integration:
https://indoorbike.app/editor/?name=Threshold+3x8&ftp=250
&s=10m@50,3x(8m@100;4m@55),5m@50
Durations, %FTP targets, and repeat groups in a compact text form. Open this example in the editor →
The workout travels inside the URL itself. Your tool never talks to our servers, and we never see your users.
Links open the editor, not the phone. The rider reviews the shape, adjusts if they like, then sends it on - nothing lands on a device unseen.
A compact llms.txt spec makes the format trivial for LLMs and codegen. Free "Open in Indoor Bike" badges if you want an on-brand button.
From install to first sprint
Install the APK, pair your trainer, pick a workout, ride. No sign-up screens, no subscription paywalls, no welcome carousels.
Get the app via Obtainium (or sideload the APK from a release). Open it. The first screen asks for your FTP and weight. That's it.
Tap the trainer chip. Power on your Kickr Core, the app discovers it over BLE, and remembers it. Optionally pair a HRM and a Zwift Click.
The home screen proposes your next ride - resume, ride again, or a starred favourite. Or search the library by how much time you have.
Hit start. ERG holds the target wattage. The HUD shows zone, power, cadence, HR, and time-in-step. The completed ride saves locally as a file.
The deal
No price, no premium tier, no ads, no "free trial". And because it ships through F-Droid, that promise is structural: no proprietary blobs, no Google Play Services, no analytics SDKs, no crash reporters phoning home. Even this page loads zero third-party resources - the fonts are self-hosted.
Questions
Any smart trainer that speaks the standard Bluetooth LE FTMS profile. The first-class target is the Wahoo Kickr Core; other FTMS-compatible trainers should work but aren't extensively tested yet.
No. The app is fully functional offline with no sign-up. Optional Auth0 login and CleverCloud sync exist for users who want cross-device backup, but everything works without them.
Install via Obtainium to get signed APKs and automatic updates straight from the Codeberg releases. An F-Droid listing is in preparation. You can also build from source.
Yes. Workouts are plain JSON files describing a sequence of intervals (duration + power as % of FTP). Build them visually in the browser editor and QR them to the app, import a file, or paste a URL.
Write a small manifest.json pointing at your workout files and host both on any HTTPS web space - static hosting is enough. Riders add your library by URL or QR, and you can open a pull request to appear in the in-app Browse list. See the publishing guide.
Yes - generate an editor URL with the workout encoded in its parameters and show it to your user. No registration, no keys, no server-to-server calls. Start with the API quick start or the machine-readable spec.
Not yet. The shared core is built with Kotlin Multiplatform Mobile so an iOS port is planned, but the current focus is shipping a polished Android release first.
Standard BLE HRMs (including the Garmin HRM) pair the same way as the trainer. HR shows on the ride HUD and is recorded with the ride file.
Install with Obtainium to get the app and automatic updates straight from our Codeberg releases - no store account, no Google services. An F-Droid release is also in preparation.
New to Obtainium? Install it, tap Add App, and paste
https://codeberg.org/juhanilehtimaki/IndoorCyclingApp.