Skip to content

Add Freebeat Fit bike support via internal UART - #4825

Merged
cagnulein merged 9 commits into
masterfrom
worktree-freebeat-bike-support
Jul 25, 2026
Merged

Add Freebeat Fit bike support via internal UART#4825
cagnulein merged 9 commits into
masterfrom
worktree-freebeat-bike-support

Conversation

@cagnulein

Copy link
Copy Markdown
Owner

Summary

  • Adds full support for the Freebeat Fit exercise bike (issue [REQ] - Freebeat Fit Connection #4822) running QZ directly on the bike's internal Android tablet
  • Communication uses the internal UART at /dev/ttyS4 (9600 baud 8N1), routed through QZ's existing Usbserial.java USB serial abstraction on Android and native termios on Linux/Mac
  • Protocol was fully reverse-engineered from the Freebeat APK (/dev/ttyS4, baud 9600, binary framing with sync bytes and checksum)

Changes

  • src/devices/freebeatbike/FreebeatUSB.h/.cpp — new QThread subclass implementing the binary protocol:
    • 5-byte commands: [startCode, action, data, (action+data)&0xFF, 0xA0]
    • 13-byte telemetry packets: speed (bytes 4-5 LE), resistance (byte 7, 1-100), RPM (bytes 8-9 LE), checksum (byte 11)
    • LED heartbeat packet every cycle to keep the bike alive
    • Resistance (1–100), query, stop, and reset commands
  • src/devices/freebeatbike/freebeatbike.h/.cpp — new device class extending bike, with virtualbike, QTimer at 200ms, ERG mode via resistanceFromPowerRequest()
  • src/qzsettings.h/.cpp — new freebeat_serialport setting; allSettingsCount incremented to 1000
  • src/settings.qml — new property string freebeat_serialport (added last per CLAUDE.md) and "Freebeat Bike Options" accordion with serial port field
  • src/devices/bluetooth.h/.cpp — reads freebeatSerialPort setting, instantiates freebeatbike when non-empty, destructor cleanup, device getter
  • src/qdomyos-zwift.pri — added 4 new source/header files
  • src/settings-catalog.json — added freebeat_serialport entry, settingCount incremented to 964

Test plan

  • Set "Freebeat Bike Options → Serial Port" in QZ settings to the device's serial port (e.g. /dev/ttyS4 on the bike's Android tablet, or a USB serial path on other platforms)
  • Launch QZ — it should instantiate freebeatbike and start FreebeatUSB thread
  • Verify telemetry (speed, cadence, power) is received and displayed
  • Verify resistance changes are sent correctly (Zwift ERG, manual slider)
  • Verify virtual bike BLE service advertises correctly

Closes #4822

🤖 Generated with Claude Code

cagnulein and others added 9 commits July 17, 2026 17:52
- New FreebeatUSB QThread: opens /dev/ttyS4 at 9600 baud 8N1 (internal UART),
  handles binary protocol decoded from APK reverse engineering:
  - 5-byte commands [startCode, action, data, checksum, 0xA0]
  - 13-byte telemetry packets with speed, RPM and resistance
  - LED heartbeat to keep bike awake
- New freebeatbike device class extending bike, with virtual bike support
- New QZSettings::freebeat_serialport setting
- QML accordion "Freebeat Bike Options" with serial port field
- Wired into bluetooth.cpp instantiation, destructor and device getter
- Added to qdomyos-zwift.pri and settings-catalog.json

Fixes #4822

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…OTOCOL.md

The Freebeat bike uses an internal UART (/dev/ttyS4), not a USB-CDC/FTDI
adapter, so Usbserial.java was wrong. Now FreebeatUSB opens the port with
POSIX open() + termios on all platforms (Android included), preceded by
chmod 777 on Android (same technique as DCUARTDriver in the original APK).
Removed QAndroidJniObject / rxBuffer since they are no longer needed.

Also add PROTOCOL.md documenting the full binary protocol reverse-engineered
from the APK (packet layout, command table, checksum, power formula).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Try open() first; if EACCES, escalate via 'su -c chmod 666' — same logic
as DCUARTDriver in the original APK. Use QProcess instead of system() for
proper logging and Qt integration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ike-support

# Conflicts:
#	src/homeform.h
#	src/settings-catalog.json
#	src/settings.qml
The array bound was hardcoded at 1000 entries; merging master's new
settings (e.g. waterrower_usb) pushed the total past that cap, causing
a "too many initializers" build error.
Keeps this branch's own setting addition last, after master's
waterrower_usb, matching the convention of appending new settings.
@cagnulein
cagnulein merged commit 7cb0721 into master Jul 25, 2026
5 of 17 checks passed
@cagnulein
cagnulein deleted the worktree-freebeat-bike-support branch July 25, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REQ] - Freebeat Fit Connection

1 participant