feat: Peloton Tread support (FTMS Treadmill Data 0x2ACD) - #70
Open
aaearon wants to merge 1 commit into
Open
Conversation
Reads live speed/incline from the tablet's ITreadInterface and broadcasts
FTMS Treadmill Data (0x2ACD); the overlay HUD switches to speed/incline. The
Bike path is unchanged.
Detection reads Settings.Global["peloton_platform"] ("prism" = Tread), not
Build.MODEL: PLTN-TTR01 is the tablet, shared by Bike+/Tread/Row (Peloton's
own isTopaz() check). Unknown/unreadable platform falls back to the Bike path.
The Tread is motorized, so the binder is read-only: allowlisted read/lifecycle
transactions through one guarded wrapper (no motion/write codes), registered
under grupetto's own package and unregistered exactly once including on error
and cancellation, to avoid Peloton's pin-lock watchdog. Bind is cancellation-aware.
Based on reverse-engineering affernetservice on real hardware, the same way
BikeData.java reads the Bike. 49 unit tests; verified on a real Tread
(0x2ACD confirmed via nRF Connect) and a Bike Gen 1 regression pass. Bike+/Row
detection is unverified (no hardware); platform check + Bike-path fallback fail safe.
doudar
approved these changes
Aug 20, 2026
Owner
|
Amazing! Thank you! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds Tread support alongside the existing Bike support. On a Tread, grupetto reads live speed and incline from the tablet's
ITreadInterfaceand broadcasts them as FTMS Treadmill Data (0x2ACD); the overlay HUD switches to speed/incline. Nothing about the Bike path changes.Detecting a Tread turned out to be the tricky part.
Build.MODELdoesn't work —PLTN-TTR01is the tablet, and the Bike+, Tread and Row all use the same one (it's literally Peloton's ownisTopaz()check). So detection readsSettings.Global["peloton_platform"]instead, which is"prism"on a Tread. Anything unknown or unreadable falls back to the Bike path, and the detected values get logged at startup.Since the Tread is motorized, I kept the binder read-only: it only sends allowlisted read/lifecycle transactions through one guarded wrapper — no motion or write codes anywhere — registers under grupetto's own package and unregisters exactly once, including on error and cancellation, so it can't trip Peloton's pin-lock watchdog. The bind is cancellation-aware too.
This is based on reverse-engineering Peloton's
affernetserviceon real hardware, the same way the existingBikeData.javareads the Bike.49 unit tests cover the parsing, scaling, sensor selection, the
0x2ACDlayout and bind cancellation. I tested it on my own Tread (confirmed0x2ACDin nRF Connect) and my Bike v1 to check I didn't regress it. I don't have a Bike+ or Row to test, so their detection is unverified — but the platform check plus the Bike-path fallback are meant to fail safe there.No dependency, Gradle, or CI changes.