Skip to content

Repository files navigation

Hyperborea

Hyperborea is an Android app that bridges ICON Fitness exercise equipment to Zwift and other fitness platforms. It reads live exercise data from the equipment's console over USB serial using the FitPro protocol and re-broadcasts it as standard fitness protocols — Bluetooth Low Energy FTMS and a WiFi TCP stream — so apps like Zwift see a normal smart trainer. It also forwards resistance/incline targets back to the equipment for ERG-style control.

It runs on the equipment's own Android console (the primary target is the NordicTrack S22i, a 22" landscape console) and works with any ICON Fitness device that speaks the FitPro protocol over USB (USB vendor ID 0x213C) — bikes, treadmills, and ellipticals. These consoles ship a range of Android versions in the field — anything from Android 5.1 to Android 9+ — and Hyperborea supports the whole range.

Important

Disclaimer. This is an independent project. It is not affiliated with, authorized by, or endorsed by ICON Health & Fitness, iFit, NordicTrack, ProForm, FreeMotion, Schwinn, Zwift, or any other company. "iFit", "NordicTrack", "ProForm", "FitPro", "Zwift", and other names are trademarks of their respective owners and are used here only for identification. The software is provided "as is" with no warranty (see LICENSE). You are responsible for complying with the terms of service and warranties of your equipment and the platforms you connect to, and for any modifications you make to your device. Use at your own risk.

Screenshots

Dashboard Profile picker Ride detail Settings
Dashboard Profiles Ride Settings

More screenshots are in screenshots/.

Building

Requires the Android SDK (and JDK 17+). Create a local.properties in the repo root pointing at your SDK (see local.properties.example):

sdk.dir=/path/to/Android/Sdk

Then:

./gradlew :app:assembleStandardDebug   # debug APK -> app/build/outputs/apk/standard/debug/
./gradlew test                         # run all unit tests
./gradlew lint                         # static analysis

Always build the standard product flavor. Release builds (./gradlew :app:assembleStandardRelease) are signed with release.jks if present, otherwise with the debug key — see local.properties.example for the optional release-signing and self-update/diagnostics settings. The ./gradlew prepareRelease task runs the full clean → lint → test → build → package pipeline and requires a release keystore.

Installing

Hyperborea installs as a regular Android APK over ADB — no root required. The release/Hyperborea/ directory contains deploy scripts (deploy.sh, deploy.ps1, deploy.cmd) that:

  1. adb install -r -g Hyperborea onto the console.
  2. adb shell pm disable-user --user 0 … the iFit apps that compete for the FitPro USB device (standalone, ERU, GlassOS service, and friends — com.ifit.launcher is left enabled so the home button still works).
  3. adb reboot to make sure no iFit processes survive.

All of step 2 works as the unprivileged shell user; the only prerequisite is an ADB connection to the console. On consoles where ADB isn't enabled out of the box, community tools like atvTools can enable it without rooting the device.

BLE caveat. On consoles whose BSP ships with config_bluetooth_le_peripheral_mode_supported=false in framework-res.apk, getBluetoothLeAdvertiser() returns null and FTMS broadcast over BLE won't work without a /vendor/overlay/ fix — and that overlay push does require root. WiFi broadcast (TCP, port 36866) works on every device. The previous deployment flow installed Hyperborea as a privileged system app at /system/priv-app/Hyperborea/ and pushed the BLE-peripheral overlay along with it; that path lives on the archive/priv-app-deployment branch if you need to revive it for a rootable console.

Architecture

:app  →  :core  ←  :hardware:fitpro
  ↓                 :broadcast:ftms
  ↓                 :broadcast:wifi
  ↓                 :ecosystem:ifit
  ↓                 :sensor:hrm
  └── wires everything together via Hilt
  • :core — pure Kotlin (no Android deps): domain interfaces, data types, orchestration logic.
  • :hardware:fitpro — the FitPro USB-serial hardware adapter (115200 baud).
  • :broadcast:ftms — BLE GATT server advertising FTMS (service 0x1826).
  • :broadcast:wifi — WiFi TCP server for fitness apps.
  • :ecosystem:ifit — coexistence with the stock iFit apps (stop the standalone app, disable the crash-looping update receiver) while Hyperborea owns the USB device.
  • :sensor:hrm — optional external BLE heart-rate monitor support.
  • :app — Compose UI (Material 3, landscape), the foreground service, the orchestrator wiring, and the Android platform implementations of the :core interfaces.

Data flows hardware → orchestrator → each broadcast sink; control targets flow back the other way. See AGENTS.md for the design conventions and module details.

Optional server features

Two features are disabled unless you configure them at build time (see local.properties.example):

  • In-app self-update — checks a manifest URL (server.url) and downloads APK updates from an allowed prefix (r2.base.url). With no server.url configured, auto-update is off.
  • Support-diagnostics upload — POSTs a logs+system bundle (no personal data) to ${server.url}/api/support/upload from the in-app "Get help" action. Off when server.url is empty.

Neither feature sends authentication; a fork hosting its own infrastructure can point these at its own endpoints (and add certificate pinning in app/src/main/res/xml/network_security_config.xml).

Contributing

See CONTRIBUTING.md.

License

MIT © 2026 Jonathan Ball.

About

No description, website, or topics provided.

Resources

Contributing

Stars

6 stars

Watchers

4 watching

Forks

Releases

Packages

Contributors

Languages