Skip to main content

Theragun Protocol

Introduction

Theragun Generation 4 devices use a common BLE service for speed control. The protocol uses a checksum byte and RPM-based speed control for Theragun devices, or discrete speed levels for Wave devices.

Device Table

IDNameTypeMin FirmwareSpeed Range
proProTheragun2.1.60–3616 RPM
eliteEliteTheragun2.1.60–3616 RPM
primePrimeTheragun2.0.40–3584 RPM
fr01Wave RollerRoller1.0.5150, 275, 350, 415, 470 RPM
WaveSoloWave SoloRoller???1920, 2520, 3120 RPM
WaveDuoWave DuoRoller???30, 45, 65, 80, 100 RPM

BLE Profile

ble_names:
- "Pro"
- "Elite"
- "Prime"
- "fr01"
- "WaveSolo"
- "WaveDuo"
services:
- uuid: "0xffe0"
characteristics:
- uuid: "0xffe1"
properties: [notify]
role: rx
description: "Response / status"
- uuid: "0xffe2"
properties: [write]
role: tx
description: "Command"

Commands

Checksum

byte checksum(byte[] a, int offset, int length) {
int sum = 0;
for (int i = 0; i < length; i++) {
sum += a[i + offset];
}
return (byte)(sum & 255);
}

Set Speed

Written with response to 0xffe2:

// 7-byte packet (mode == 1, i.e. Theragun devices)
byte[] arr = new byte[7];
arr[0] = (byte)0xAA;
arr[2] = 0x01;
arr[3] = 0x03;
arr[4] = (byte)((65280 & rpm) >> 8); // RPM high byte
arr[5] = (byte)(rpm & 255); // RPM low byte
arr[6] = 0x03;
arr[1] = checksum(arr, 2, 5); // checksum of bytes 2..6

// 4-byte packet (mode != 1, i.e. Wave devices)
byte[] arr = new byte[4];
arr[0] = (byte)0xAA;
arr[2] = 0x01;
arr[3] = 0x03;
arr[1] = checksum(arr, 2, 2);

Request Firmware Version

Written without response:

AA 20 20 00

Firmware Version Response

From the Rx characteristic notification (write-with-response also triggers this):

[...] arr[12] << 8 | arr[13]  -> version integer
major = version / 100
minor = (version % 100) / 10
patch = version % 10

Response byte 2 (arr[2]) value 0x20 (32 decimal) indicates a version response packet.

Notes

  • 0xffe0/0xffe1/0xffe2 are 16-bit short-form UUIDs.
  • RPM values for Wave devices are discrete fixed levels, not continuous.
  • The prime device was previously identified as liv2 in older firmware.

Sources