Skip to content

feat(lib): add support for the soundcore P42i (D1205) - #436

Open
R-Schulmann wants to merge 2 commits into
Oppzippy:masterfrom
R-Schulmann:feat/d1205
Open

R-Schulmann wants to merge 2 commits into
Oppzippy:masterfrom
R-Schulmann:feat/d1205

Conversation

@R-Schulmann

Copy link
Copy Markdown

AI disclosure

Protocol analysis and implementation were done with Claude (Anthropic). I captured the
Bluetooth traffic from the official app, ran all hardware tests on my own P42i and
reviewed the changes.

Summary

Adds support for the soundcore P42i (D1205). Closes #336.

The serial number starts with 1205 and the vendor RFCOMM UUID ends in d1205. The
05 01 info packet identifies the device as D1202 internally, and apart from the
equalizer block the state update packet follows the D1202 layout byte for byte, so the
module is modeled on d1202. Differences:

  • Adaptive noise canceling is ambient sound mode 3 rather than a noise canceling mode.
  • The equalizer is parametric and written with 03 8a: preset id, 6 zero bytes, filter
    count, then per filter type, gain (dB), frequency (Hz) and Q as little endian f32.
    Presets send the exact filter lists captured from the app, custom profiles send 8
    peaking filters at 100–12800 Hz with Q 0.7. Unlike the D1203 in Add support for Soundcore Liberty 5 Pro (D1203) #414, the length
    field includes the checksum, so the normal packet serializer works.
  • The state update packet has a 145 byte equalizer block: preset id, saved custom gains
    (8 × f32, kept while a preset is active), 6 unknown bytes, filter count and 8 filter
    slots of 13 bytes padded with 0xff.
  • The app disables spatial audio by sending 10 81 with 0, not via the equalizer
    packet like on the D1202.

Features

Feature Status
Ambient sound mode (NC / transparency / normal / adaptive) ✅
Manual noise canceling level (1–5) ✅
Wind noise suppression ✅
Equalizer presets + custom profiles ✅
Button configuration (single/double/triple/long, both sides) ✅
Touch tone, wearing tone, low battery prompt ✅
Wearing detection, gaming mode, spatial audio ✅
LDAC ✅
Auto power off (10/20/30/60 min, never) ✅
Dual connections + device list ✅
Battery, charging, case battery, serial number, firmware ✅
Hearguard (transparency option, sound mode byte 2) ❌ parsed and preserved, no matching setting id yet
Limit high volume ❌ parsed and preserved, not offered by the app
Find device, button reset, hearing health stats (20 05), HearID ❌ not implemented

Note: on this device LDAC and dual connections are mutually exclusive. Enabling LDAC
restarts the earbuds and disables dual connections (observed in the official app).

Preset volume adjustments are only used for display and are calculated from the
preset's filters (RBJ biquads, assuming type 0/1/2 = low shelf/peaking/high shelf).
The device always receives the exact captured filter lists.

The preset names are translated from the German version of the Soundcore app
(Anker-Klangsignatur, Klarer Gesang, Kraftvoller Bass, Ruhig und beruhigend), so the
English app may use slightly different names. The preset ids are 0x0000–0x0003.

Testing

  • 9 new unit tests using a captured state update packet (serial number zeroed) and the
    packet from Add support for Soundcore P42i #336. Preset, custom EQ, sound mode, spatial audio and button packets are
    checked byte for byte against captures from the official app.
  • cargo test -p openscq30-lib passes, including test_set_all_settings. cargo fmt
    and cargo clippy are clean for the new files.
  • Hardware: soundcore P42i, firmware 06.72, Linux (SteamOS) via the CLI. Every exposed
    setting was set, read back over a new connection (fresh state update packet) and
    restored: all ambient sound modes, NC level, wind noise, all presets, custom EQ,
    buttons, all toggles and auto power off including disabled. Battery levels match
    the official app.
Hardware test log (soundcore P42i, firmware 06.72, openscq30 CLI on SteamOS)

Each setting was set, then read back with a new connection (fresh state update packet),
then restored and read back again.

--- read only ---
dualConnections: true
ldac: false
batteryLevelLeft: 10/10
batteryLevelRight: 10/10
caseBatteryLevel: 7/10
serialNumber: 1205XXXXXXXXXXXX (redacted)

--- sound modes ---
ambientSoundMode: NoiseCanceling -> set Transparency -> read Transparency -> restore NoiseCanceling -> read NoiseCanceling
ambientSoundMode: NoiseCanceling -> set AdaptiveNoiseCanceling -> read AdaptiveNoiseCanceling -> restore NoiseCanceling -> read NoiseCanceling
ambientSoundMode: NoiseCanceling -> set Normal -> read Normal -> restore NoiseCanceling -> read NoiseCanceling
manualNoiseCanceling: 5 -> set 3 -> read 3 -> restore 5 -> read 5
windNoiseSuppression: false -> set true -> read true -> restore false -> read false

--- equalizer ---
presetEqualizerProfile: SoundcoreSignature -> set ClearVocals -> read ClearVocals -> restore SoundcoreSignature -> read SoundcoreSignature
presetEqualizerProfile: SoundcoreSignature -> set PowerfulBass -> read PowerfulBass -> restore SoundcoreSignature -> read SoundcoreSignature
presetEqualizerProfile: SoundcoreSignature -> set CalmAndSoothing -> read CalmAndSoothing -> restore SoundcoreSignature -> read SoundcoreSignature
volumeAdjustments: [19, 7, 2, 1, 0, 0, 5, 14] -> set 1,1,13,9,9,35,29,29 -> read [1, 1, 13, 9, 9, 35, 29, 29]
presetEqualizerProfile (final): SoundcoreSignature

--- buttons ---
rightSinglePress: PlayPause -> set VolumeUp -> read VolumeUp -> restore PlayPause -> read PlayPause
leftLongPress: AmbientSoundMode -> set PlayPause -> read PlayPause -> restore AmbientSoundMode -> read AmbientSoundMode

--- toggles ---
touchTone: true -> set false -> read false -> restore true -> read true
wearingTone: true -> set false -> read false -> restore true -> read true
lowBatteryPrompt: true -> set false -> read false -> restore true -> read true
wearingDetection: true -> set false -> read false -> restore true -> read true
gamingMode: false -> set true -> read true -> restore false -> read false
spatialAudio: false -> set true -> read true -> restore false -> read false
autoPowerOff: 60m -> set disabled -> read disabled -> restore 60m -> read 60m

The device list for dual connections was also read correctly (4 devices with names),
omitted here for privacy.

If you'd like a Hearguard setting, I'm happy to add it in a follow-up.

@R-Schulmann

Copy link
Copy Markdown
Author

Follow-up from additional testing on my P42i:

  • Filter types confirmed by listening tests: type 0 = low shelf, type 2 = high shelf
    (type 1 = peaking was already confirmed by the custom EQ).
  • The custom slot (fe fe) accepts arbitrary filter lists, not only the 8 fixed bands
    the app uses: single shelves and a -9 dB peak at 1 kHz were all audible.
  • Sending more than 8 filters crashes the earbuds (tested with 10). The state update
    packet has exactly 8 filter slots, so 8 seems to be the firmware limit. This PR never
    sends more than 8.

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.

Add support for Soundcore P42i

1 participant