Suzuki Connect RE

Reverse-engineering a motorcycle's Bluetooth cluster protocol, then building a from-scratch Android app to replace the stock one

RoleSolo: RE & app author
TypePersonal / educational
StackKotlin · Jetpack Compose · BLE
AppREDLINE · local-only
01Overview

My 2023 Suzuki Gixxer SF 150 has a Bluetooth-enabled instrument cluster that pairs with Suzuki's stock app for turn-by-turn navigation, routed through Mappls maps, with a UX I didn't love and no way to extend. So I set out to understand the protocol completely, then build my own replacement: Google Maps navigation pushed to the cluster, live telemetry on the phone, and ride analytics the stock app never offered.

There was no documentation. The bike speaks a proprietary binary protocol over a vendor BLE service, and the only reference was the stock app's compiled code. I worked it out the hard way: walking the GATT table on the live bike, decompiling the app with JADX, hooking it at runtime with Frida, and cross-referencing wire captures against the app's own logic until every message type was decoded.

The result is two things: a fully documented protocol spec, and REDLINE, a Kotlin/Jetpack Compose Android app that drives the cluster end-to-end. It's a personal, educational reverse-engineering project; no proprietary Suzuki code is redistributed.

7BLE frame types decoded
14kLOC of Kotlin
205unit tests, green
0cloud · fully local
02Reverse-engineering the protocol

A GATT walk on the live bike revealed a single vendor service exposing two characteristics: one to write phone→bike, one to receive bike→phone notifications. Every message on the wire is exactly 30 bytes with a consistent envelope: a 0xA5 header, an ASCII type byte, a body, a checksum, and a 0x7F terminator.

  byte 0      : 0xA5            header
  byte 1      : ASCII type      e.g. '1','3','6','7'
  bytes 2..27 : body            time / distance / flags / telemetry
  byte 28     : checksum        sum(payload[1:28]) mod 256
  byte 29     : 0x7F            terminator

I confirmed the checksum algorithm directly from the decompiled source rather than guessing it from samples, then mapped each message type to its purpose: display refresh (time + distance + maneuver), phone and bike heartbeats, and an identity push. One non-obvious finding: the cluster is response-driven. It sends nothing until the phone writes first, so a passive listener captures zero notifications.

The discipline that made this work was treating every claim as a hypothesis until the evidence settled it. Several early conclusions were wrong and got walked back in writing: a Wireshark dissector mislabelled the vendor characteristics as a standard digital-key spec, and I'd assumed the bike had an embedded SIM phoning home until physical inspection proved it had none. Each correction is logged with what was assumed, what was actually true, and the evidence that flipped it.

03REDLINE: the replacement app

Once the protocol was understood, I built a full Android app around it. REDLINE encodes Google Maps navigation into the cluster's frame format, reads telemetry the bike emits, and layers on the ride tooling the stock app lacks, all on-device, with no account and no cloud.

Google Maps → cluster nav
Intercepts Maps turn-by-turn notifications, translates each maneuver into the bike's display-refresh frame, and writes it over BLE, replacing the stock Mappls-based navigation.
Live telemetry dashboard
Speed, fuel/range, odometer, trip meters and fuel economy parsed from the bike's notify frames and rendered as a live dashboard on the phone.
Ride analytics & trips
Every ride is recorded with distance, duration, moving vs idle time, speed distribution, and fuel-economy trend, plus lifetime stats, streaks and records.
GPX / CSV export
Trips export to GPX and CSV, with a share-card and an AI-readable text report for any single ride.
Idle cluster display
When navigation is idle, the app renders a clock and now-playing info to the cluster's text fields instead of leaving it blank.
Frame inspector
A built-in inspector decodes live frames on-device: the same RE tooling that built the app, shipped inside it for ongoing protocol work.
04The app in action

A few screens from REDLINE running against the actual bike, built with Jetpack Compose, dark-first, with a visual identity inspired by the cluster itself.

REDLINE live telemetry dashboard: speedometer, fuel/range, odometer, trip meters
Live telemetry dashboard
REDLINE lifetime stats: distance, streak, records, distance trend
Lifetime stats & records
REDLINE home: today's distance, fuel, odometer, bike health, quick actions
Home overview
REDLINE ride detail: 354 km trip with distance, duration, speed, mileage and tags
Ride detail · 354 km trip
REDLINE ride speed graph and speed distribution histogram
Speed graph & distribution
REDLINE trips list with per-ride sparklines, grouped by week
Trips list

All data shown is real, captured from my own bike. Identifiers have been kept out of the public repository.

05Method & tooling

The RE side combined static and dynamic analysis. JADX decompiled the stock app (most class names were unobfuscated, which made the checksum and frame construction readable). Frida hooked the live app to watch BLE writes and the semantic events behind them. Android's HCI snoop log captured the raw bytes on the wire, and a small Python toolkit on the laptop (bleak for BLE, tshark for the captures) let me replay and forge frames against the bike to test each hypothesis.

Everything is documented as it happened: a living protocol spec for the current best understanding, and a chronological discoveries log that keeps the wrong turns rather than silently overwriting them, so the reasoning is auditable, not just the conclusions.

06Tech & tools
KotlinApp language
Jetpack ComposeUI
Android BLEGATT transport
RoomRide storage
Coroutines / FlowAsync + streams
DataStoreSettings / profile
JADXStatic decompilation
FridaRuntime hooking
Wireshark / tsharkWire capture analysis
Python · bleakBLE replay / forging
HCI snoop logRaw BLE capture
JUnit205 unit tests