BikeCon maps indoor bike data to a virtual game controller. It lets you use a Linux device (Raspberry Pi Zero 2W recommended) as a bridge so your bike can act as an input device on a PC.
BikeCon currently supports two input protocol types:
- Private protocol of some Keep bikes
- Standard FTMS-compatible devices
In addition, BikeCon keeps an optional FTMS compatibility layer service that rebroadcasts Keep bike data as FTMS for third-party apps (for example, GTBIKEV / Zwift).
- Cycling device (one of the following):
- Keep bike (currently tested with Keep C2 Lite, firmware 1.0.1)
- Generic BLE indoor bike with FTMS support
- Bridge device: A small Linux computer with Bluetooth and USB Gadget support (e.g. Raspberry Pi Zero 2W). In this document, this type of device is referred to as “Raspberry Pi”.
- Joy-Con (optional): For combined button input. If unavailable, use the built-in web virtual controller.
# Clone repo
git clone https://github.com/shinkisan/BikeCon.git
# Enter project directory
cd BikeConThe flow depends on your bike type:
- Keep mode: You need to extract auth info from Keep App communication.
- FTMS mode: No Keep packet capture required; just scan and select the target FTMS device.
Before installation, you must extract authentication information from official app traffic.
Important: your bike must stay offline (including future usage with this project), otherwise data may go through Wi-Fi instead of BLE.
- Enable Developer Options: In “About phone”, tap “Build number” / “Software version” repeatedly until Developer Mode is enabled.
- Enable HCI logging: In Developer Options, enable Bluetooth HCI snoop log.
- Generate traffic:
- Restart Bluetooth on the phone.
- Open Keep App, connect your bike, and ride for a few minutes.
- End workout and close Keep App.
- Export logs:
- Locate the log file (usually
/data/misc/bluetooth/logs/btsnoop_hci.log; or export viaadb bugreport bugreport.zip, then it is typically underFS/data/misc/bluetooth/logs/btsnoop_hci.log). - Copy the file to your Raspberry Pi.
- Locate the log file (usually
BikeCon provides identity_gen.py, which generates identity.json and automatically updates bike_type in config.json.
Keep mode (with log file argument):
# Install packet parsing dependencies
sudo apt install tshark -y
pip install pyshark
python3 identity_gen.py btsnoop_hci.logGeneric FTMS mode (no arguments):
python3 identity_gen.pyAfter launch, press Enter to start BLE scan (Esc to exit), then choose a device to generate identity.json.
chmod +x install.sh
sudo ./install.shsudo ./start.shsudo ./stop.shsudo ./uninstall.shBikeCon includes these 6 systemd services (startup order):
- BikeCon-hardware.service - Configure USB Gadget and emulate HID gamepad
- BikeCon-mixer.service - Mix bike data and controller button input
- BikeCon-bike.service - BLE bike connection
- BikeCon-joycon.service - Joy-Con input handling
- BikeCon-web.service - Web UI (port 8000)
- BikeCon-ftms.service - FTMS compatibility layer (exposes FTMS BLE service externally)
BikeCon includes an FTMS compatibility layer that rebroadcasts Keep bike data through standard FTMS service for some third-party apps (e.g. GTBIKEV).
- Default:
ftms_layer_enabledinconfig.jsonisfalse(disabled) - Enable method 1 (recommended): turn on “FTMS Service” in Web settings
- Enable method 2: edit
/etc/BikeCon/config.jsonand setftms_layer_enabledtotrue - Forced-off rule: when
bike_typeinconfig.jsonisftms, FTMS compatibility layer is forcibly disabled (Web button will appear unavailable)
After startup, open: http://<raspberry-pi-ip>:8000
View bike packet log (runtime):
tail -f /dev/shm/BikeCon/bike_raw_data.logView bike packet log (persistent):
tail -f /var/log/BikeCon/bike_raw_data.logView all service logs:
journalctl -u BikeCon-*.service -fView specific service log:
journalctl -u BikeCon-bike.service -fconfig.json- App configurationidentity.json- Authentication data
This project is not fully tested yet. If you hit issues or request support for other models, please open an issue with related logs.
Bike (BLE) -> bike_driver_xxxx.py -> bike_service.py -> mixer.py -> USB gamepad
↓ ↑
webapp.py webapp.py (virtual gamepad) / joycon_service.py
↓
ftms_server.py (FTMS compatibility layer) -> Third-party apps (e.g. GTBIKEV)
If you want to test locally, these two scripts may help:
Purpose: simulate an FTMS indoor bike that BikeCon can connect to when you do not have real FTMS hardware.
Common commands:
python3 dev/fake_ftms_server.py
python3 dev/fake_ftms_server.py --name BikeCon-Fake-FTMS --hz 5 --web-port 8080
python3 dev/fake_ftms_server.py --start-activeAfter startup:
- Default web management URL:
http://<device-ip>:8080 - In BikeCon FTMS mode, point
identity.jsonbike_macto this simulated device (can be discovered by scan)
Purpose: connect as an FTMS client to an FTMS service (BikeCon FTMS layer or fake_ftms_server).
Common commands:
# Scan and connect by name (default target: BikeCon-FTMS)
python3 dev/fake_ftms_client.py
# Connect by device name or MAC
python3 dev/fake_ftms_client.py BikeCon-Fake-FTMS
python3 dev/fake_ftms_client.py AA:BB:CC:DD:EE:FF
# Interactive mode
python3 dev/fake_ftms_client.py -i
# Console-only mode (no web UI)
python3 dev/fake_ftms_client.py --no-webNotes:
fake_ftms_client.pydefault web port is8080(change with--port)fake_ftms_server.pyweb UI default is also8080- If both run on the same machine, change at least one port to avoid conflicts
This project is licensed under GNU GPL v3.
This project is for technical research and personal learning only. Compatibility across all hardware and firmware versions is not guaranteed. The author is not responsible for device issues or Keep account issues caused by using this project.
This project heavily uses AI; code style is still mixed and being improved over time.
FTMS compatibility layer implementation references code and ideas from: