Skip to main content

specialized-turbo

Python library for talking to Specialized Turbo e-bikes (Vado, Levo, Creo) over Bluetooth Low Energy. Reads speed, power, cadence, battery, motor temp, odometer, assist level, range. Can also write settings like assist level and acceleration.

Async, built on bleak. Includes a CLI. Protocol docs in docs/protocol.md.

Installation

pip install specialized-turbo

For automatic key retrieval on newer encrypted bikes:

pip install "specialized-turbo[cloud]"

Quick start

Stream telemetry

import asyncio
from specialized_turbo import SpecializedConnection, TelemetryMonitor


async def main():
    async with SpecializedConnection("DC:DD:BB:4A:D6:55") as conn:
        monitor = TelemetryMonitor(conn)
        await monitor.start()

        async for msg in monitor.stream():
            print(f"{msg.field_name} = {msg.converted_value} {msg.unit}")


asyncio.run(main())

Newer bikes advertise an HMI serial/hardware pair and require a wrapped key from Specialized's keystore API. Supply either a provider or a wrapped key:

from specialized_turbo import SpecializedConnection
from specialized_turbo.cloud import SpecializedCloudClient

async with SpecializedCloudClient() as cloud:
    await cloud.login("rider@example.com", password)
    async with SpecializedConnection(
        "DC:DD:BB:4A:D6:55",
        key_provider=cloud,
    ) as conn:
        ...

# Or avoid account authentication by supplying the 64-character wrapped key.
async with SpecializedConnection(address, wrapped_key=wrapped_key) as conn:
    ...

The password is not persisted by the library. Provider users control token storage; manual-key users can operate without cloud access after obtaining the per-bike wrapped key.

Read the snapshot

async with SpecializedConnection("DC:DD:BB:4A:D6:55") as conn:
    monitor = TelemetryMonitor(conn)
    await monitor.start()
    await asyncio.sleep(5)

    snap = monitor.snapshot
    print(f"Speed: {snap.motor.speed_kmh} km/h")
    print(f"Battery: {snap.battery.charge_pct}%")
    print(
        f"Power: {snap.motor.rider_power_w} W (rider) + {snap.motor.motor_power_w} W (motor)"
    )
    print(f"Cadence: {snap.motor.cadence_rpm} RPM")
    print(f"Assist: {snap.motor.assist_level}")

Query a single value

from specialized_turbo import SpecializedConnection, Sender, BatteryChannel

async with SpecializedConnection("DC:DD:BB:4A:D6:55") as conn:
    msg = await conn.request_value(Sender.BATTERY, BatteryChannel.CHARGE_PERCENT)
    print(f"Battery: {msg.converted_value}%")

Write commands

async with SpecializedConnection("DC:DD:BB:4A:D6:55") as conn:
    await conn.set_assist_level(2)  # TRAIL
    await conn.set_acceleration(50.0)  # 50%
    await conn.set_shuttle(25)
    await conn.set_assist_percentage(0, 35)  # ECO = 35%

CLI

Scan for bikes:

specialized-turbo scan
specialized-turbo scan --timeout 15

Retrieve the wrapped key required by a newer encrypted bike:

specialized-turbo fetch-key DC:DD:BB:4A:D6:55 \
  --email rider@example.com

The password is prompted without echo. By default, stdout contains only the 64-character wrapped key, ready for --wrapped-key or the Home Assistant manual-key flow. Use explicit HMI identifiers when the bike is not nearby:

specialized-turbo fetch-key \
  --hmi-hardware 3.2.1 \
  --hmi-serial 123456789 \
  --email rider@example.com \
  --json

fetch-key requires specialized-turbo[cloud]. It does not print the final unwrapped AES key.

Stream telemetry:

specialized-turbo telemetry DC:DD:BB:4A:D6:55 --email rider@example.com
specialized-turbo telemetry DC:DD:BB:4A:D6:55 --wrapped-key "$WRAPPED_KEY"
specialized-turbo telemetry DC:DD:BB:4A:D6:55 --format json
specialized-turbo telemetry DC:DD:BB:4A:D6:55 --duration 30

Read a single value:

specialized-turbo read list                                             # show available fields
specialized-turbo read battery_charge_percent DC:DD:BB:4A:D6:55
specialized-turbo read speed DC:DD:BB:4A:D6:55 --format json

Write a value:

specialized-turbo write list                                            # show writable fields
specialized-turbo write assist_level 2 DC:DD:BB:4A:D6:55  # set to TRAIL
specialized-turbo write acceleration 50 DC:DD:BB:4A:D6:55 # 50% sensitivity

Dump GATT services (debugging):

specialized-turbo services DC:DD:BB:4A:D6:55

Capture complete TCX writes and notifications for protocol debugging:

specialized-turbo capture DC:DD:BB:4A:D6:55 --duration 60 > tcx-capture.tsv

Available fields

Field Unit Writable Description
battery_capacity_wh Wh Total battery capacity
battery_remaining_wh Wh Remaining energy
battery_health % Battery health
battery_temp °C Battery temperature
battery_charge_cycles cycles Number of charge cycles
battery_voltage V Battery voltage
battery_current A Battery current draw
battery_charge_percent % State of charge
rider_power W Rider pedal power
cadence RPM Pedaling cadence
speed km/h Current speed
odometer km Total distance
assist_level -- yes OFF / ECO / TRAIL / TURBO
motor_temp °C Motor temperature
motor_power W Electric motor power
peak_assist % ECO / TRAIL / TURBO percentages
shuttle -- yes Shuttle mode value (0-100)
wheel_circumference mm yes Wheel circumference setting
assist_lev1_pct % yes ECO assist percentage
assist_lev2_pct % yes TRAIL assist percentage
assist_lev3_pct % yes TURBO assist percentage
fake_channel -- Bit-coded internal channel
acceleration % yes Acceleration sensitivity

TCX2+ bikes have additional fields (range, altitude, gradient, calories, system temperature, and more). See docs/protocol.md for the full list.

Protocol support

Five protocol variants exist:

Protocol Message format Encryption
TCU1 [sender][channel][data] None
TCX1 Same as TCU1, using TURBOHMI GATT UUIDs None
TCX2 2-byte parameter ID + payload + clear CRC-16 Optional AES-128-CTR
TCX3 Same as TCX2 Optional AES-128-CTR
TCX4 Same as TCX2 Optional AES-128-CTR

TCX1 shares the TURBOHMI UUID family with TCX2/3/4 but uses legacy write-then-read queries. The connected service structure distinguishes it from TCX2+. The BLEProfile enum (TCU1 / TCX) controls which GATT UUID family to use.

For encrypted bikes, only packet bytes 2–17 are encrypted. The parameter ID and CRC remain clear. The wrapped per-bike key comes from Specialized's cloud, while SYSTEM_GET_NEW_VI supplies a fresh session IV at connection time.

See docs/protocol.md for the full spec.

Pairing

The connection requests authenticated pairing for bikes using the TURBOHMI UUID family. Passkey entry and numeric comparison are handled by the active Bluetooth backend or its pairing agent; Bleak cannot accept a passkey value directly.

If the backend cannot present or confirm the pairing prompt, pair through the operating system first and reconnect. The deprecated --pin and pin= arguments are accepted for compatibility but their values are ignored.

Development

uv sync --extra dev
uv run pytest

License

MIT

Metadata

Release files for specialized-turbo 0.8.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for specialized-turbo 0.8.4
File Size Uploaded
specialized_turbo-0.8.4.tar.gz 157.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for specialized-turbo 0.8.4
File Interpreter ABI Platform
specialized_turbo-0.8.4-py3-none-any.whl Python 3 none any Details

Total release size: 258.5 kB

Release files / specialized_turbo-0.8.4.tar.gz

Download URL specialized_turbo-0.8.4.tar.gz
Size 157.9 kB
Tags Source
SHA-256 checksum
How to use checksums
d535ef9919ea6f71e41e839ca7b6c4a4a83df42cec237ac7e569dbfa378e75e1
BLAKE2b-256 checksum
How to use checksums
1b9fd08ccbe6ae87bde8f3fef31d88f68577e41f4121cf07be3dd510fe9635a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / specialized_turbo-0.8.4-py3-none-any.whl

Download URL specialized_turbo-0.8.4-py3-none-any.whl
Size 100.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e5ee11ac4d5a12ae14d93656cc89d9ff75cf013f0f19b05d0b79014298e82c08
BLAKE2b-256 checksum
How to use checksums
102ae8381c23b1546f933cf18a7bd08bf7b2bc1df7954f226238826ce4b8a4fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.8.4 This release

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.9

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page