Skip to content

Add support for Soundcore Liberty 5 Pro (D1203) - #414

Open
iquirino wants to merge 9 commits into
Oppzippy:masterfrom
iquirino:feat/soundcore-liberty-5-pro-d1203
Open

iquirino wants to merge 9 commits into
Oppzippy:masterfrom
iquirino:feat/soundcore-liberty-5-pro-d1203

Conversation

@iquirino

Copy link
Copy Markdown

Summary

Adds support for the Soundcore Liberty 5 Pro (model D1203).

Unlike the other supported devices, the Liberty 5 Pro encodes its state as a TLV (tag-length-value) packet rather than fixed offsets, and its equalizer is an 8-band parametric EQ written with a dedicated command (category 0x03, command 0x8a) using float gains/frequencies/Q. This required a device-specific parser and write path rather than reusing the common equalizer module.

What works (verified on real hardware)

  • Device shows up in list-models and can be paired
  • Reads firmware (left/right), serial number, and per-earbud battery level (0–100 scale)
  • Reads the active equalizer preset (TLV tag 11)
  • Reads and writes an 8-band parametric custom equalizer (100–12800 Hz, −6..+6 dB)

Implementation notes

  • New soundcore::d1203 module with a tolerant TLV state parser (unknown tags are skipped, so firmware revisions with new fields won't break parsing).
  • The D1203 EQ write packet's length field excludes the trailing checksum byte, whereas the generic packet::Outbound serializer includes it. To avoid miscomputing the length, I added PacketIOController::send_raw which writes pre-framed bytes verbatim; the EQ state modifier builds the exact packet itself.
  • The write packet was validated byte-for-byte against real captured traffic in a unit test.

Testing

  • cargo test -p openscq30-lib — 215 passed, 0 failed (8 new D1203 tests, including a byte-exact check of the EQ write packet)
  • test_set_all_settings exercises the new device end-to-end on a demo device
  • Read and write verified against a physical Liberty 5 Pro over RFCOMM

Not included / future work

  • Case battery level (TLV tag 8 was ambiguous in captures)
  • ANC / sound modes
  • A couple of named EQ preset IDs are not yet mapped (handled gracefully as unknown)

The protocol was decoded by reverse engineering the official app's RFCOMM traffic (captured via adb shell dumpsys bluetooth_manager, no root required).

The Liberty 5 Pro uses a TLV-based state packet (distinct from the
offset-based packets of other devices) and a parametric 8-band equalizer
with its own write command (category 0x03, command 0x8a).

- Add SoundcoreD1203 device model with its own TLV state parser
- Read firmware, serial number and per-earbud battery level (0-100 scale)
- Read the active equalizer preset (tag 11)
- Read/write an 8-band parametric custom equalizer (100-12800 Hz, -6..+6 dB)
- Add PacketIOController::send_raw for devices whose on-wire length field
  excludes the checksum byte, which the generic serializer would miscompute

Decoded via reverse engineering of the official app's RFCOMM traffic.
The D1203 sound-mode write command (0x06 0x81, 7-byte payload) is identical
to the Liberty 5 (A3957), so this reuses a3957::structures::SoundModes and the
shared sound-modes module. Exposes ambientSoundMode (NoiseCanceling/Transparency/
Normal), noise canceling mode, manual ANC level and wind noise suppression.

Verified byte-for-byte against real captured traffic and tested on hardware
(switching all modes over RFCOMM from Linux).

Reading the current mode from the TLV state packet is left as a TODO (write path
is fully functional).
Reverse engineering revealed the Liberty 5 Pro uses ambient_sound_mode=3
for Adaptive, which the reused A3957 enum (0/1/2 only) cannot represent.
Replace the A3957 sound-modes reuse with a D1203-specific
D1203AmbientSoundMode enum (NoiseCanceling=0, Transparency=1, Normal=2,
Adaptive=3) whose write handler sends the exact captured 7-byte payloads
over command 0x06 0x81.

Also use the English preset name "Clear Voice" instead of a hardcoded
Portuguese string (UI strings must be English).

All 4 modes verified on real hardware over RFCOMM.
Completes the sound modes screen with noiseCancelingMode (Manual/
Transportation, payload byte 1) and manualNoiseCanceling level 1-5
(payload byte 3), decoded from captured traffic. The 06 81 payload is
now built from the combined state (ambient + nc mode + manual level).

Byte layout verified against real captures; all combinations tested on
hardware over RFCOMM.
Wind noise reduction is a separate command (category 0x41, command 0x88,
payload 0b 00/01), not part of the sound-mode packet. Exposed as the
windNoiseSuppression toggle under Sound Modes. Verified byte-for-byte
against real captures and tested on hardware.
In Transportation NC sub-mode, payload byte 5 selects the vehicle type
(Car=1, Airplane=0). Exposed as the transportationMode selector. Verified
against real captures and tested on hardware.
Remap tap/press actions per earbud. Command 04 8c with payload
[side, gesture_b1, action, gesture_b3]. 4 configurable gestures (single/
double/triple/long; swipes are not remappable), 10 actions (volume,
track, play/pause, ambient sound, voice assistants, AI translation, none).
Single-press only allows the 6 basic actions, matching the app.

Byte layout fully reverse-engineered and verified on hardware.
Touch tone, low battery prompt, wearing tone (command 41 87), LDAC, easy
chat, spatial audio (command 41 88), and auto power off (command 01 86,
10/20/30/60 min or never). All use [sub_id, state] / [enabled, index]
payloads, reverse-engineered and verified byte-for-byte and on hardware.

Also updates the CHANGELOG with the full D1203 feature set.
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.

2 participants