gymnasticon icon indicating copy to clipboard operation
gymnasticon copied to clipboard

Peloton Bike+ compatibility?

Open skoregon opened this issue 3 years ago • 7 comments

I installed and used Gymnasticon on a friend's Peloton Bike. It was great. My wife is now in possession of a Peloton Bike+. Any chance of that being added to the project? I'm a useless dev, but happy to poke around with anything else if helpful.

skoregon avatar Mar 08 '21 22:03 skoregon

https://fccid.io/2AA3N-TTR01 The FCC filing has some photos of the insides of Bike+ "Internal Photos 2" appears to show the board in the resistance control unit. "Internal Photos 1" is mostly the tablet.

skoregon avatar Mar 08 '21 22:03 skoregon

It appears that this is the processor for the sensor/resistance board (based on the FCC photos of internals): https://www.st.com/resource/en/datasheet/stm32l432kc.pdf Controller for the resistance unit: https://www.st.com/resource/en/datasheet/l9942.pdf

skoregon avatar Mar 08 '21 23:03 skoregon

Hi @skoregon, nice investigating!

I don't have access to a Bike+ so take everything I say with a grain of salt. Also just a warning to anyone reading: there's always a chance of damaging gear, so please don't try anything below unless you are comfortable doing so.

I have heard that the connection between sensor board and tablet is USB with a Type-C connector on the tablet side? If so, I would guess based on that microcontroller datasheet you linked (STM32L432KC) that it is USB 2.0. And that it probably shows up as a CDC (Virtual Com Port) when plugged into a computer/tablet. If so, it should be possible to communicate with it the same way as the Peloton Bike (non-plus). It is even possible that parts of the protocol are the same and some things Just Work already. It is of course possible that they made an entirely new protocol too.

An easy first step would be to plug the USB cable into a computer, see if a USB CDC device shows up, try running gymnasticon with --peloton-path pointing at the USB CDC device path. You could also use a terminal program like screen or a simple pyserial / node-serialport script to play around with it.

If that actually works then I think the next things to figure out would be:

  • Resistance control messages
  • Simple wiring options for passive/active use-cases

If not, a next step could be to try capture some data and figure out the protocol.

ptx2 avatar Mar 10 '21 17:03 ptx2

I plugged in the USBC cable from the resistance control unit into a Pi4revB running gymnasticon. I add the line dtoverlay=dwc2,dr_mode=host to the config.txt file as I assume it is needed to put the USB C port into host mode so it can gather data from the resistance control unit.

The Pi4 boots when connected to the USB-C from the resistance control unit, so that's a good start. I got on the bike and pedaled a bit and it is in a no resistance state.

libusb-t yields pi@gymnasticon:/ $ lsusb -t

/:  Bus 03.Port 1: Dev 1, Class=root_hub, Driver=dwc2/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Communications, Driver=cdc_acm, 12M
    |__ Port 1: Dev 2, If 1, Class=CDC Data, Driver=cdc_acm, 12M
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M

dmesg output attached.

dmesgoutput.txt

I don't see anything that looks like a device, or the peloton resistance control unit attached. I'm a novice, so I'm not sure how to more thoroughly interrogate the Pi4's USB attachments, or even if I've configured it correctly. See link below for some discussion. It is unclear to me if the Pi4 is a suitable device for this project or not. https://www.raspberrypi.org/forums/viewtopic.php?t=246348

Any further guidance would be welcome on how to investigate.

I'm also having a difficult time figuring out how gymnasticon would work from a cabling perspective. Somehow the data needs to pass through the pi and be manipulated, then passed back both to the resistance control unit (if resistance control can be characterized) as well as downstream to the tablet.

At the same time, power over the USBC cable has to be provided to the tablet and the RCU and presumably the Pi, although the Pi could possible do its data interface over the USB A ports with a A->C converter and be powered separately? If that strategy were used though, somehow the data needs to get back into the USB C cable w/ the power to for both the resistance control unit and the tablet.

skoregon avatar Mar 19 '21 06:03 skoregon

Hey, nice progress!

I think this confirms some of our assumptions about the communications:

(from your dmesg)

[    4.892304] usb 3-1: New USB device found, idVendor=317e, idProduct=a005, bcdDevice= 2.00
[    4.892324] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[    4.892340] usb 3-1: Product: Peloton Titan
[    4.892356] usb 3-1: Manufacturer: Peloton
[    4.892370] usb 3-1: SerialNumber: 00000000001A
[    4.981078] cdc_acm 3-1:1.0: ttyACM0: USB ACM device
[    4.981703] usbcore: registered new interface driver cdc_acm
[    4.981716] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

(and lsusb -t)

/:  Bus 03.Port 1: Dev 1, Class=root_hub, Driver=dwc2/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Communications, Driver=cdc_acm, 12M
        |__ Port 1: Dev 2, If 1, Class=CDC Data, Driver=cdc_acm, 12M

Do you see a /dev/ttyACM0 device when the bike is connected? If so, an easy thing to try is running gymnasticon with --bike peloton and --peloton-path /dev/ttyACM0. If no luck, then I suppose it's time to figure out the protocol :-)

Great points on the wiring...

For the non-plus bike there are two wiring options with Gymnasticon: active and passive. Active mode is when the tablet remains disconnected and Gymnasticon polls the bike for power/cadence/resistance data. Passive mode is when the tablet does the polling and Gymnasticon just listens to the responses on the wire. The user chooses the mode with their RS232 cable wiring.

I feel both active and passive modes would be useful on the Bike+ too. The wiring for active mode is hopefully just plugging the bike directly into the Pi as you've already done. The wiring for passive mode, as you point out, presents some challenges.

For development purposes maybe something like this could work:

separate power adapter -> tablet's "P" USB-C port (this is just to power the tablet)
bike -> computer (acting as both usb host and device/gadget) -> tablet's second USB-C port

The idea is that hopefully the tablet doesn't care which USB port the bike's serial device appears on.

One issue with this setup is I'm pretty sure you can't use a stock Raspberry Pi for the "computer". Don't quote me but I think none of them have the hardware to act as host and gadget at the same time. You may be able to add a second USB controller via SPI but I'm not sure of an easy off-the-shelf option. I think the BeagleBone Black might be able to do it without modification though.

A similar option using two computers:

separate power adapter -> tablet's "P" USB-C port (this is just to power the tablet)
bike -> computerA (usb host)
computerB (usb device/gadget) -> tablet's second USB-C port

(computerA and computerB talk over the network)

You could use the Pi for computerB and any computer with a USB port for computerA.

Once setup, and assuming it works, on Linux I think you should get a /dev/ttyGS0 when the gadget serial port is set up. And hopefully you already have /dev/ttyACM0 for the bike. From there a small glue program could be written to just forward data between /dev/ttyACM0 and /dev/ttyGS0 in both directions and log it for analysis. socat(1) could probably do this without writing any code. PySerial comes with some forwarding examples that work over the network too.

Some links that might help:

https://www.isticktoit.net/?p=1383 https://www.kernel.org/doc/html/latest/usb/gadget_configfs.html https://www.kernel.org/doc/html/latest/usb/gadget_serial.html https://www.kernel.org/doc/html/latest/usb/gadget_multi.html https://github.com/usb-tools/Facedancer https://beagleboard.org/black

Also I found this one useful for pictures and background since I don't have the bike:

https://www.dcrainmaker.com/2020/09/peloton-bike-plus-details-features.html

Let me know if I can help, good luck!

EDIT: BTW, when setting up the CDC gadget on Linux, it'd be a good idea to use the same device info as the bike, i.e. idVendor, idProduct, Manufacturer, etc. so it looks right to the tablet.

ptx2 avatar Mar 20 '21 01:03 ptx2

I was looking at this as well -- just in case anyone finds this useful, here is a windows USB dump. I think it will be better to try to do actual investigation via Linux.

   ---===>Device Information<===---

English product name: "Peloton Titan"

ConnectionStatus:
Current Config Value: 0x01 -> Device Bus Speed: Full (is not SuperSpeed or higher capable) Device Address: 0x05 Open Pipes: 3

      ===>Device Descriptor<===

bLength: 0x12 bDescriptorType: 0x01 bcdUSB: 0x0201 bDeviceClass: 0x02 -> This is a Communication Device bDeviceSubClass: 0x02 *!*ERROR: bDeviceSubClass of 2 is invalid bDeviceProtocol: 0x00 bMaxPacketSize0: 0x40 = (64) Bytes idVendor: 0x317E = Vendor ID not listed with USB.org idProduct: 0xA005 bcdDevice: 0x0200 iManufacturer: 0x01 English (United States) "Peloton" iProduct: 0x02 English (United States) "Peloton Titan" iSerialNumber: 0x03 English (United States) "00000000001A" bNumConfigurations: 0x01

      ---===>Open Pipes<===---

      ===>Endpoint Descriptor<===

bLength: 0x07 bDescriptorType: 0x05 bEndpointAddress: 0x82 -> Direction: IN - EndpointID: 2 bmAttributes: 0x03 -> Interrupt Transfer Type wMaxPacketSize: 0x0008 = 0x08 bytes bInterval: 0x10

      ===>Endpoint Descriptor<===

bLength: 0x07 bDescriptorType: 0x05 bEndpointAddress: 0x01 -> Direction: OUT - EndpointID: 1 bmAttributes: 0x02 -> Bulk Transfer Type wMaxPacketSize: 0x0040 = 0x40 bytes bInterval: 0x00

      ===>Endpoint Descriptor<===

bLength: 0x07 bDescriptorType: 0x05 bEndpointAddress: 0x81 -> Direction: IN - EndpointID: 1 bmAttributes: 0x02 -> Bulk Transfer Type wMaxPacketSize: 0x0040 = 0x40 bytes bInterval: 0x00

   ---===>Full Configuration Descriptor<===---

      ===>Configuration Descriptor<===

bLength: 0x09 bDescriptorType: 0x02 wTotalLength: 0x0043 -> Validated bNumInterfaces: 0x02 bConfigurationValue: 0x01 iConfiguration: 0x00 bmAttributes: 0xC0 -> Self Powered MaxPower: 0x32 = 100 mA

      ===>Interface Descriptor<===

bLength: 0x09 bDescriptorType: 0x04 bInterfaceNumber: 0x00 bAlternateSetting: 0x00 bNumEndpoints: 0x01 bInterfaceClass: 0x02 -> This is Communications (CDC Control) USB Device Interface Class bInterfaceSubClass: 0x02 bInterfaceProtocol: 0x01 iInterface: 0x00 -> This is a Communications (CDC Control) USB Device Interface Class

      ===>Descriptor Hex Dump<===

bLength: 0x05 bDescriptorType: 0x24 05 24 00 10 01 -> This is a Communications (CDC Control) USB Device Interface Class

      ===>Descriptor Hex Dump<===

bLength: 0x05 bDescriptorType: 0x24 05 24 01 00 01 -> This is a Communications (CDC Control) USB Device Interface Class

      ===>Descriptor Hex Dump<===

bLength: 0x04 bDescriptorType: 0x24 04 24 02 02 -> This is a Communications (CDC Control) USB Device Interface Class

      ===>Descriptor Hex Dump<===

bLength: 0x05 bDescriptorType: 0x24c 05 24 06 00 01

      ===>Endpoint Descriptor<===

bLength: 0x07 bDescriptorType: 0x05 bEndpointAddress: 0x82 -> Direction: IN - EndpointID: 2 bmAttributes: 0x03 -> Interrupt Transfer Type wMaxPacketSize: 0x0008 = 0x08 bytes bInterval: 0x10

      ===>Interface Descriptor<===

bLength: 0x09 bDescriptorType: 0x04 bInterfaceNumber: 0x01 bAlternateSetting: 0x00 bNumEndpoints: 0x02 bInterfaceClass: 0x0A -> This is a CDC Data USB Device Interface Class bInterfaceSubClass: 0x00 bInterfaceProtocol: 0x00 iInterface: 0x00

      ===>Endpoint Descriptor<===

bLength: 0x07 bDescriptorType: 0x05 bEndpointAddress: 0x01 -> Direction: OUT - EndpointID: 1 bmAttributes: 0x02 -> Bulk Transfer Type wMaxPacketSize: 0x0040 = 0x40 bytes bInterval: 0x00

      ===>Endpoint Descriptor<===

bLength: 0x07 bDescriptorType: 0x05 bEndpointAddress: 0x81 -> Direction: IN - EndpointID: 1 bmAttributes: 0x02 -> Bulk Transfer Type wMaxPacketSize: 0x0040 = 0x40 bytes bInterval: 0x00

      ===>BOS Descriptor<===

bLength: 0x05 bDescriptorType: 0x0F wTotalLength: 0x000C bNumDeviceCaps: 0x01

      ===>USB 2.0 Extension Descriptor<===

bLength: 0x07 bDescriptorType: 0x10 bDevCapabilityType: 0x02 bmAttributes: 0x00000002 -> Supports Link Power Management protocol

ChrisJefferson avatar May 09 '21 09:05 ChrisJefferson

Hello, just checking if there has been any progress with this issue (adding support for Peloton Bike+). BTW I have a Peloton Bike+ and I'm waiting for the hardware to start testing and could help with this.

antgonza avatar Jan 05 '23 18:01 antgonza