Case study
Replacing a treadmill app that disappeared
The app for my WalkingPad R1 Pro was removed from Google Play. I recovered its Bluetooth protocol and built a browser app that now runs the treadmill and keeps my workout history.
Why I built it
I walk on this treadmill regularly, and the old app held 519 of my sessions. When it was unpublished from Google Play in November 2025, I needed another way to run the treadmill and keep that history.
My role
All of it: the reverse engineering, product decisions, code, data and deployment. Coding agents wrote much of the code, while the design, review and checks on the real treadmill stayed with me.
Recovering the protocol
The package was obfuscated, and the decompiler could not rebuild some of its constants, so I recovered those by disassembling the bytecode directly.
The protocol notes tag each finding as verified, inferred or unknown, and verified findings point to the source file and line. That way, an inferred value is never treated as verified.
Two frames from the original app confirm the checksum rule, and the frame builder is tested against them byte for byte, because those bytes go to a treadmill motor.




Key decisions
A walk survives a closed tab. The walk in progress is saved to the device every 10 seconds, so a killed tab loses seconds rather than the session.
A walk does not need a connection. Walks are saved on the device first, so the app keeps working without a connection. Anything that didn't upload goes up on a later sync.
A pause does not split a walk. The belt resets its counters when it restarts, so the recorder carries earlier totals forward and the summary covers the whole walk.
The old history was imported. I imported 519 real sessions from the original app and checked that they were consistent before relying on them.
Accounts stay separate. I tested against the real database with two accounts. Neither could see or overwrite the other's data.
What went wrong
The first real walk had the wrong start time. The 73-minute walk was stamped when the app connected, but the belt's counters cover the whole run. I rebuilt the time by hand from the belt's elapsed counter. The lasting fix is to derive the start from that counter, and it is still on my list.
Session uploads returned a 500 error. Fractional calorie and step values were reaching integer database columns, so they are now rounded before they get there.
I had the speed limit wrong. A hardcoded 6 km/h cap did not match my own history, which shows the belt reaching 9.5 km/h. The app now reads the limit from the belt.
Where it is now
I use it to run my treadmill: start, stop and speed control, live telemetry, walk replay and history. It has 302 automated tests.
Next.js, TypeScript, Web Bluetooth, IndexedDB, Neon Postgres