π Reverse-Engineering a SupeRun Walking Pad
Sometimes the most satisfying software projects start with a very dumb emotion: I own this thing, why the hell can't I control it properly?
That was the vibe with my SupeRun walking pad. It worked, technically. But it lived behind a vendor app, an opaque BLE protocol, and the usual pile of consumer-IoT mystery meat. So Joe and I did what any reasonable pair of nerds would do: we started peeling it apart.
The end result was better than expected: a Python controller, an Android MVP app, protocol notes, and a public repo for the clean parts of the work. Not bad for what started as treadmill spite.
The Goal
We wanted direct control over the walking pad without depending on the original app.
- Connect over BLE
- Start, pause, stop, and set speed
- Understand the packet format instead of blindly replaying bytes
- Figure out whether startup sounds / voice prompts could be disabled
- Package the clean, reusable parts into a public repo
Step 1: Treat the App as Evidence
We started by decompiling the Android app package and looking for anything related to Bluetooth, sound settings, control packets, and treadmill state.
That immediately paid off. Instead of fishing blind, we found explicit code paths for things like:
controlBuzzerSwitchcontrolSpeakergetSpeakerStatus
That's the good kind of reverse engineering: not cinematic hacker nonsense, just carefully reading somebody else's Java/Kotlin and noticing they already told you where the bodies are buried.
Step 2: Find the BLE Shape of the Device
Once we had clues from the app, we matched them against BLE traffic and device behavior. The useful part of the protocol sat behind a simple custom service/characteristic layout:
Service: 0000ffff-0000-1000-8000-00805f9b34fb
Write: 0000ff01-0000-1000-8000-00805f9b34fb
Notify: 0000ff02-0000-1000-8000-00805f9b34fb
We recovered a keepalive packet, control packet family, and enough status structure to start building real tools instead of throwing random hex at the treadmill and hoping it didn't launch me into a wall.
Lesson: consumer BLE devices often aren't cryptographically fancy. They're just undocumented. Once you find the packet family and timing model, the whole thing stops looking magical.
Step 3: Build the Python Controller First
Python was the fastest way to iterate, so we built a small controller around bleak.
It handles scan/connect, notifications, keepalive packets, and the obvious treadmill commands.
def make_keepalive(seq: int) -> bytes:
return bytes([0x4D, 0x00, seq & 0xFF, 0x05, 0x6A, 0x05, 0xFD, 0xF8, 0x43])
def mph_to_raw(speed_mph: float) -> int:
return max(0, min(int(round(speed_mph * 1600.0)), 0xFFFF))
The interesting detail here is that speed appeared to use a raw scale of mph * 1600.
That's one of those protocol facts that looks arbitrary until you test it a few times and realize,
no, this weird little number is actually the map.
Once the Python side was stable enough, we had commands for start, set speed, pause, and stop. More importantly, we had a place to encode what we believed the protocol meant.
Step 4: Chase the Beep
Naturally, we also wanted to see whether the walking pad's startup sound and voice prompts could be disabled. The vendor app strongly suggested yes.
From the reversed app logic, we recovered packet forms like these:
Query speaker status: 6B 05 9F 9A 43
Set speaker config: 6B 07 9E <powerOn> <voicePrompts> <xor> 43
That's where the project shifted from "we can definitely drive the treadmill" to "we probably can control the audio settings too, but let's not pretend we've fully nailed that path yet." Some of the reverse engineering was clean. Some of it was still experimental.
And that's fine. A good engineering write-up should separate confirmed behavior from strongly supported hypotheses. Bullshitting your future self is a great way to waste a weekend.
Step 5: Build the Android MVP
After the Python controller proved the basics, we built a minimal Android app that talks directly to the walking pad over BLE. No pretty UX. No account system. No cloud nonsense. Just:
- connect / disconnect
- start
- set speed
- pause
- stop
- status text from incoming notifications
It reuses the same packet model as the Python controller, which is the main point. One clean protocol understanding, two clients.
Step 6: Publish the Clean Version
The raw project folder had a lot of garbage in it: APKs, bugreports, BLE snoop logs, local SDK config, decompiler output, build junk, and a real device MAC address that had no business being shoved into a public repo.
So we cleaned it properly before publishing:
| Kept | Excluded |
|---|---|
| Python controller | Vendor APK |
| Android MVP source | Bugreports / BLE snoop logs |
| Protocol notes | Decompiled proprietary sources |
| Gradle wrapper / project files | Local SDK config / build artifacts |
We also scrubbed the real hardcoded BLE MAC from the public code and replaced it with placeholders. Public repo means public repo, not accidental dox-yourself-as-a-service.
The result lives here: github.com/hdxsfbr/walkingpad
What I Like About This Project
This wasn't just "make a treadmill go brrr." It was a nice little stack of practical engineering skills:
- reverse engineering for interoperability
- BLE protocol inference
- quick Python tooling for fast iteration
- an Android control surface once the protocol was understood
- good repo hygiene before publishing
That's my favorite kind of project: the thing itself is kind of funny, but the engineering muscles are real.
Whatβs Next
- replace hardcoded device targeting in the Android app with scan/select UX
- keep refining speaker / buzzer control behavior
- validate behavior across more PitPat / SupeRun variants
- make the Android app less MVP-looking and more "normal human can use this"
Anyway, that's how a walking pad turned into a reverse-engineering project. Not because it needed to. Because it annoyed me. Which, honestly, is often how the good ones start.