[TOC]
FrameType: if payload size > 250
then 1
else 0
ExtendedFrameType: if payload size > 250
then 2
else 1
(byte)
Name | Value (hex) | Size |
---|---|---|
Preamble | FE | 1 byte |
Type (Req = 0 or Rsp = 1)[1] | 0x | 1 byte |
Payload size | xx | 1 byte |
Message ID | xx | 1 byte |
Message payload | ... | dynamic[2] |
Checksum[3] (CRC16-CCITT)[4] | xx xx | 2 byte |
Postamble | EE | 1 byte |
[1] maybe more types -> used by isFragment()
[2] calculation:
x = payload size - 1 byte (msg_id) - 2 bytes (crc)
[3] checksum of the Message ID and content
[4] more details: https://gist.github.com/ThePBone/435b625418945592d7a0a3f04adc67b0
Skipped for now; only used for large data transmissions (e.g. Firmware OTA, Core Dumps)
Index | Name | Description | Size |
---|---|---|---|
0 | Timestamp | Milliseconds since 1970 as long byte buffer | 8 byte |
8 | Timezone | Timezone offset (amount of ms to add) as int byte buffer | 4 byte |
Index | Name | Description | Size |
---|---|---|---|
0 | Hardcoded | Always contains 1 | 1 byte |
1 | IsSamsungDevice | Samsung device = 1, Other = 2 | 1 byte |
2 | AndroidSdk | Android Version as SDK Integer | 1 byte |
Might be used to enable phone-exclusive functions
Index | Name | Description | Size |
---|---|---|---|
0 | Status | 0 or 1 | 1 byte |
Used to prepare for client-based TTS voice notifications. Does not seem to have any effect
Index | Name | Description | Size |
---|---|---|---|
0 | ? | 0 or 1; Probably used to play an internal beep sound | 1 byte |
Used for notifications. Does not seem to have any effect
Index | Name | Description | Size |
---|---|---|---|
0 | Mode? | Set to 2 if phone supports Game Mode, otherwise unknown | 1 byte |
Unknown effect. Only used on Samsung devices with Game Mode available.
Index | Name | Description | Size |
---|---|---|---|
0 | Enabled | 0 or 1; Enable EQ | 1 byte |
1 | Preset | 0-10; Preset | 1 byte |
Preset ID | Description |
---|---|
0 | Bass boost (optimized for Dolby) |
1 | Soft (optimized for Dolby) |
2 | Dynamic (optimized for Dolby) |
3 | Clear (optimized for Dolby) |
4 | Treble boost (optimized for Dolby) |
5 | Bass boost |
6 | Soft |
7 | Dynamic |
8 | Clear |
9 | Treble boost |
Index | Name | Description | Size |
---|---|---|---|
0 | Enabled | 0 or 1; Touchpad lock | 1 byte |
Index | Name | Description | Size |
---|---|---|---|
0 | Enabled | 0 or 1; Enable ambient sound | 1 byte |
Index | Name | Description | Size |
---|---|---|---|
0 | Type | 0 or 1; Default or VoiceFocus | 1 byte |
Index | Name | Description | Size |
---|---|---|---|
0 | Volume | 1-5; Ambient volume | 1 byte |
Index | Name | Description | Size |
---|---|---|---|
0 | LeftMuteStatus | 0 or 1; Mute | 1 byte |
1 | RightMuteStatus | 0 or 1; Mute | 1 byte |
Only available in FindMyGear mode
Index | Name | Description | Size |
---|---|---|---|
0 | LeftOption | ID of Action to be set on the left device | 1 byte |
1 | RightOption | ID of Action to be set on the left device | 1 byte |
ID | Description |
---|---|
0 | Voice assistant |
1 | Quick ambient sound |
2 | Volume (if set on left: volume down, else volume up) |
3 | Ambient sound |
4 | Spotify SpotOn (requires client compatibility) |
5 | Other... (left only) |
6 | Other... (right only) |
Index | Name | Description | Size |
---|---|---|---|
0 | Device | Hand-over main connection (1 = left, 0 = right) | 1 byte |
Message ID | Description |
---|---|
MSG_ID_FIND_MY_EARBUDS_START | Starts playing loud beeping sounds |
MSG_ID_FIND_MY_EARBUDS_STOP | Stops playing and return to the default BT audio |
MSG_ID_DEBUG_SERIAL_NUMBER | Returns serial number for both devices |
MSG_ID_RESET | Resets device to factory defaults and responds with a result code |
MSG_ID_DEBUG_GET_ALL_DATA | Returns version data and sensor measurements |
MSG_ID_DEBUG_BUILD_INFO | Returns device string for both devices |
MSG_ID_LOG_SESSION_OPEN | Open logging session and disconnect the music stream |
MSG_ID_LOG_SESSION_CLOSE | Close logging session and resume the music stream |
MSG_ID_BATTERY_TYPE | Returns battery type strings for both devices |
Index | Name | Description | Size |
---|---|---|---|
0 | VersionOfMR | 1 or 2; Also used to check for the new ambient module | 1 byte |
1 | EarType | 0 or 1; Unused[1] | 1 byte |
2 | DeviceBatGageL | 0-100%; Battery (L) | 1 byte |
3 | DeviceBatGageR | 0-100%; Battery (R) | 1 byte |
4 | TwsStatus | 0 or 1; Coupled device status[1] | 1 byte |
5 | MainConnection | 0 (R) or 1 (L); Current main connection[1] | 1 byte |
6 | WearingStatus | 0 (none), 1 (L), 16 (R), 17 (both)[1] | 1 byte |
7 | AmbientSoundEnable | 0 or 1; Is ambient sound enabled[1] | 1 byte |
8 | AmbientSoundType | 0 or 1; Is voice focus mode enabled[1] | 1 byte |
9 | AmbientSoundVolume | Ambient sound volume[2] | 1 byte |
10 | EqualizerEnable | 0 or 1; Is EQ enabled[1] | 1 byte |
11 | EqualizerType | 0-10[3]; EQ type | 1 byte |
12 | <dynamic> | Touch lock state (+ one option slot, if byte 13 not used) | 1 byte |
13 | <dynamic> (optional) | Touch option (L and R)[4] | 1 byte |
[1] additional code: -1 (test code)
[2] post-calculation:
int saved = vol + -1;
-> expected range [1;5][3] range: [0;10] ->
if (type >= 5) type -= 5;
-> actual range: [0;5][4]byte 13 is only required for touch actions that can set two different actions for L and R long press at the same time. If only one option slot is sufficent, byte 13 is not transmitted and the single action is stored in byte 12 alongside the touch lock state which can be either 0 or 1.
The device probably expects a response. The same SPPMessage should be sent back (response type) and after that, you should transmit basic (android) manager app information. (Refer to MSG_ID_MANAGER_INFO
)
Single touch option
- Byte 13 is not transmitted, instead the option is stored inside byte 12
- Used by 'volume button preset'
byte lock_touchpad_status = (sppMessage.getParameters()[12] & 240) >> 4;
byte touchpad_option = sppMessage.getParameters()[12] & 15;
storeLeftTouchpadOption(touchpad_option);
Double touch option
- Byte 13 is transmitted, both options are stored inside
- The touch lock state remains in byte 12
byte lock_touchpad_status = sppMessage.getParameters()[12] & 255;
byte touchpad_option_l = (sppMessage.getParameters()[13] & 240) >> 4;
byte touchpad_option_r = sppMessage.getParameters()[13] & 15;
Index | Name | Description | Size |
---|---|---|---|
0 | EarType | 0 or 1; Unused[1] | 1 byte |
1 | DeviceBatGageL | 0-100%; Battery (L) | 1 byte |
2 | DeviceBatGageR | 0-100%; Battery (R) | 1 byte |
3 | TwsStatus | 0 or 1; Coupled device status[1] | 1 byte |
4 | MainConnection | 0 (R) or 1 (L); Current main connection[1] | 1 byte |
5 | WearingStatus | 0 (none), 1 (L), 16 (R), 17 (both)[1] | 1 byte |
[1] additional code: -1 (test code)
The device probably expects a response. The same SPPMessage should be sent back (response type) and after that, you should transmit basic (android) manager app information. (Refer to MSG_ID_MANAGER_INFO
)
Index | Name | Description | Size |
---|---|---|---|
0 | Action | Original ID of message sent to the earbuds | 1 byte |
1 | ResultCode | Result/error code (0 = Success) | 1 byte |
2 | ExtraData (opt.) | Only used if the MainConnection has been manually changed | 2 byte |
Generic response. Android app always sends a time update if received.
[UNCHECKED] If byte 0 corresponds to 112, then we're looking at the response message of a manual MainConnection change. In this case, byte 2 describes which earpiece is declared as the main connection 0 (R) or 1 (L).
- (Extended) Status Update requires (?) RESP with result code