# ANT+ to Bluetooth LE Bridge

Bridge an ANT+ cycling power trainer over Bluetooth Low Energy, allowing modern devices that don't support 
ANT+ to receive power and cadence data.

## Quick Start

1. **Set up the USB dongle** — Follow the [Setup instructions](#setup) to prevent the `usbserial` driver from claiming it
2. **Build:**
   ```bash
   make build
   ```
3. **Run** (requires root):
   ```bash
   sudo ./bin/bridge
   ```
4. **Connect** — On your computer or phone, scan for BLE devices and connect to `LinuxAntBtBridge`.

That's it. Your trainer's power and cadence data should now be available over BLE.

## Why this exists

Modern training apps and devices (phone apps, many power meter displays) support Bluetooth LE but lack ANT+ radios. 
Previously, hardware-based Bluetooth-to-ANT+ bridges were available for this purpose, but these have now been 
discontinued. This project provides a simple, software-based alternative using a standard USB ANT+ dongle.
This is useful if the hardware device breaks and cannot be replaced when the trainer is still functioning 
perfectly. 

This bridge listens to ANT+ data from a trainer and re-broadcasts it as a standard Cycling Power Profile 
service over BLE.

## How it works

1. Opens a USB ANT+ dongle and listens for broadcast messages from a specific trainer device number.
2. Parses power (Watts) and cadence (RPM) from the trainer's ANT+ data.
3. Exposes a standard Cycling Power Profile BLE service via the system's Bluetooth adapter, updating values as new 
   ANT+ messages arrive.

## Requirements

### Hardware

- A Bluetooth adapter (built-in or USB)
- An ANT+ USB dongle (tested: ANT USBStick2, vendor `0x0fcf`, product `0x1008`)
- An ANT+ cycling power trainer

### Software

- Linux: tested on Debian 13. 
- BlueZ 5.50+ (for Bluetooth management)
- D-Bus (for BlueZ communication)
- `hciconfig` (usually part of the `bluez-utils` or `bluez-tools` package)

**Note:** This program must be run as root to access the USB dongle.

## Tested hardware

- USB dongle: ANT USBStick2 (Dynastream Innovations, vendor `0x0fcf`, product `0x1008`)

## Setup

The Linux `usbserial` driver must be prevented from claiming the ANT+ dongle. 

### Find your dongle's USB IDs

If you're not using the tested dongle, find your dongle's vendor and product IDs:

```bash
lsusb
```

Look for a line with "ANT" in the description. Note the vendor ID (e.g., `0fcf`) and product ID (e.g., `1008`).

### Create the udev rule

Create the following udev rule to prevent `usbserial` from claiming the dongle:

```bash
sudo tee /etc/udev/rules.d/99-ant-usb.rules > /dev/null <<EOF
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fcf", ATTRS{idProduct}=="1008", ENV{MODALIAS}="ignore"
EOF
```

Replace `0fcf` and `1008` with your dongle's actual vendor and product IDs if different.

### Reload the rules

```bash
sudo modprobe -r usb_serial_simple
sudo udevadm control --reload-rules
sudo udevadm trigger
```

### Verify

Reconnect the dongle and check `dmesg`:

```bash
dmesg | tail -5
```

Expected output (no `usbserial` messages):
```
usb 3-1.2.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 3-1.2.1: Product: ANT USBStick2
usb 3-1.2.1: Manufacturer: Dynastream Innovations
```

If you see a line like `usb_serial converter now attached to ttyUSB0`, the udev rule did not apply.

## Installation

```bash
make build
```

The binary is built to `bin/bridge`.

## Usage

```bash
# Listen to all trainers (default)
sudo ./bin/bridge

# Filter for a specific trainer
sudo ./bin/bridge --device 0xbb9

# Custom vendor/product IDs with specific device
sudo ./bin/bridge --vendor 0x0fcf --product 0x1008 --device 3001
```

### CLI Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--vendor` | `0x0fcf` | USB vendor ID (hex or decimal, e.g. `0x0fcf` or `4047`) |
| `--product` | `0x1008` | USB product ID (hex or decimal) |
| `--device` | `0 (any)` | ANT+ device number (hex or decimal). Use 0 or omit to listen to all devices |
| `--help` | | Show help |
| `--version` | | Show version info |

## Finding your trainer's ANT+ device number

The bridge listens to all broadcasting devices by default. When multiple ANT+ trainers are nearby, each will appear in the 
log output as `ant: <device_id>: power: ...`. Use `--device <id>` to filter for a specific trainer.

If you're unsure of your trainer's device number, you can discover it using another ANT+ device (e.g., a Garmin Edge 
head unit). Connect the Garmin to your trainer and look for the device number in the ANT+ sensor list.

You can also try common device numbers for your trainer brand — most manufacturers use predictable numbers for their 
devices.

## Connecting

The bridge exposes a standard [Cycling Power Profile](https://bluetooth.com/specifications/specs/cycling-power-service-1-0/) 
service over BLE. Any app that supports this profile will work — no custom app required.

Tested with:
- Zwift

May also work with other Cycling Power Profile apps:
- TrainingPeaks
- Wahoo SYSTM
- TrainerRoad
- Rouvy
- Kinomap
- GoldenCheetah

### Steps to connect

1. Run the bridge: `sudo ./bin/bridge`
2. On your computer or phone, scan for BLE devices
3. Connect to the device named `LinuxAntBtBridge`
4. Start training in your app

Your trainer's power and cadence data should now be available in the app.

## Output

```
2026-05-26 11:00:34 ANT Bridge abc1234 built 2026-05-26T11:00:00
2026-05-26 11:00:34 usb: vendor: 0x0fcf/4047
2026-05-26 11:00:34 usb: product: 0x1008/4104
2026-05-26 11:00:34 ant: device: any
2026-05-26 11:00:34 ant: device opened
2026-05-26 11:00:34 bt: BLE advertising...
2026-05-26 11:00:34 ant: scan mode started
2026-05-26 11:00:34 ant: listening...
2026-05-26 11:00:35 ant: 3001: power: 150W  cadence: 80 rpm
```

The bridge logs a lot of output during normal operation — this is expected and helpful for debugging during this early 
stage of development.

Key log lines:
- `ANT Bridge ...` — Startup message with version and build time
- `usb: vendor: ...` / `usb: product: ...` — USB dongle configuration
- `ant: device: ...` — ANT+ device filter (or `any` if listening to all devices)
- `ant: device opened` — USB dongle opened successfully
- `bt: BLE advertising...` — BLE service is ready and visible to other devices
- `ant: scan mode started` / `ant: listening...` — ANT+ receiver is active
- `ant: <id>: power: ...` — Power/cadence data with transmitting device ID
- `bt: watts: ...` — Power/cadence data being broadcast over BLE
- `ant: received msg from ignored device ...` — Message from non-matching device (when `--device` is specified)
- `hciconfig ... failed` — Bluetooth adapter reset warnings (usually harmless)

## Development

```bash
make build    # Build with ldflags (version, build time)
make clean    # Remove build artifacts
```

## Known issues

- **Verbose logging:** The bridge produces a lot of log output during normal operation. This is expected and helpful 
  for debugging during this early stage of development.
- **Wattage drops:** The wattage sometimes drops briefly to 0 but then quickly recovers. If this happens frequently, 
  please report it with your log output.

## Troubleshooting

### No BLE devices found

- Check that the Bluetooth adapter is enabled: `rfkill list bluetooth`
- Check that the bridge is advertising: look for `BLE advertising...` in the log output
- Make sure no other app is using the Bluetooth adapter

### ANT+ dongle not found

- Check that the dongle is plugged in: `lsusb | grep 0fcf`
- Check that the `usbserial` driver is not claiming the dongle: `lsmod | grep usb_serial_simple`
- Verify the udev rule was applied (see [Setup](#setup))
- Make sure you're running with `sudo`

### No data from trainer

- Make sure the trainer is turned on and broadcasting
- Use another device (e.g. Garmin Edge) to verify it is broadcasting. This also provides a way
  to get the device id.
- The bridge listens to all devices by default. If multiple trainers are broadcasting, use
  `--device <id>` to filter for a specific one
- Make sure the dongle is close enough to the trainer (within 1-2 meters)
- Check the log output for the `listening...` message

### BLE connection drops

- Make sure the bridge is within range of the receiving device (within 5-10 meters)
- Check for Bluetooth interference (other BLE devices, Wi-Fi, etc.)
- Check the log output for `failed to write power characteristic` messages
