diff --git a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt index 5cd875ce..39839f41 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt @@ -403,10 +403,11 @@ fun DeviceSettingsScreen( } // PME Config visualization (debug only, opcode 0x53). - // The Wireshark dissector calls this "PME Config"; captures so far are - // all-zero on in-production firmware, so the bar chart stays hidden in - // that case — showing empty bars would imply the device has a flat EQ, - // which we don't actually know. + // PME = Personal Medical Equipment (hearing-aid band gains for iOS + // 18.1+ AirPods Pro 2 hearing-aid mode). The bar chart reuses the + // EQ visualization because the data shape (per-ear × per-profile × + // band gains) renders identically; hidden on all-zero payloads, which + // just mean the user hasn't configured a hearing-aid profile yet. val pmeConfig = device.pmeConfig if (eu.darken.capod.BuildConfig.DEBUG && pmeConfig != null && diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/engine/AapSessionEngine.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/engine/AapSessionEngine.kt index 9c67a2f2..fee3ca50 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/engine/AapSessionEngine.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/engine/AapSessionEngine.kt @@ -590,7 +590,7 @@ internal class AapSessionEngine( AapMessageType.UARP_DATA.value, // 0x004F AapMessageType.UNKNOWN_0X50.value, // 0x0050 AapMessageType.SOURCE_CONTEXT.value, // 0x0052 - AapMessageType.SET_BAND_EDGES.value, // 0x0054 (real EQ; distinct from 0x53 PmeConfig which CAPod decodes) + AapMessageType.SET_BAND_EDGES.value, // 0x0054 (neighbouring opcode; distinct from 0x53 PmeConfig/hearing-aid which CAPod decodes) AapMessageType.UNKNOWN_0X55.value, // 0x0055 AapMessageType.SLEEP_DETECTION_UPDATE.value, // 0x0057 AapMessageType.UNKNOWN_0X58.value, // 0x0058 diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapMessageType.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapMessageType.kt index ef09d761..00f76f90 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapMessageType.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapMessageType.kt @@ -90,9 +90,11 @@ enum class AapMessageType(val value: Int, val wiresharkName: String) { SOURCE_CONTEXT(0x0052, "Source Context"), /** - * PME (Personal Mixing Engine?) config per Wireshark. CAPod decodes this as - * 4 × 8 Float32 values (see [AapSetting.PmeConfig]). Real EQ likely lives - * at [SET_BAND_EDGES]. + * PME = Personal Medical Equipment (cf. PPE = Personal Protective Equipment) — + * hearing-aid configuration for the iOS 18.1+ hearing-aid feature on AirPods + * Pro 2. Decoded as 4 × 8 Float32 values (see [AapSetting.PmeConfig]); see + * that data class for the layout rationale. "PME Config" is the label the + * Wireshark AAP dissector uses for this opcode. */ PME_CONFIG(0x0053, "PME Config"), diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapSetting.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapSetting.kt index 6050c7be..de30514e 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapSetting.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/AapSetting.kt @@ -164,13 +164,18 @@ sealed class AapSetting { } /** - * Payload of message type 0x0053. The Wireshark AAP dissector calls this - * "PME Config" (Personal Mixing Engine). CAPod used to label it as EQ - * bands, but captures so far show this data is all zeros on in-production - * firmware — the real equalizer path is likely 0x0054 "Set Band Edges". + * Payload of message type 0x0053 — "PME Config" in the Wireshark AAP dissector. + * PME = Personal Medical Equipment (cf. PPE = Personal Protective Equipment): + * the hearing-aid configuration for Apple's iOS 18.1+ hearing-aid feature on + * AirPods Pro 2. * - * The payload is decoded as 4 sets × 8 Float32 values until the true schema - * is confirmed. Callers should treat all-zero [sets] as "no config reported". + * Decoded as 4 × 8 Float32 values — consistent with per-ear × per-profile + * audiogram band gains (e.g. L/R × two environment profiles, 8 frequency + * bands). CAPod previously called this "EQ bands". + * + * Callers should treat all-zero [sets] as "no hearing-aid profile configured" + * — stock firmware reports zeros until the user runs Apple's Hearing Test / + * hearing-aid setup. */ data class PmeConfig( val sets: List>, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/DefaultAapDeviceProfile.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/DefaultAapDeviceProfile.kt index 254c7fd4..caaeede8 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/DefaultAapDeviceProfile.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/protocol/DefaultAapDeviceProfile.kt @@ -158,10 +158,12 @@ class DefaultAapDeviceProfile( return AapSetting.AudioSource::class to AapSetting.AudioSource(mac, type) } - // 0x53 is "PME Config" per the Wireshark AAP dissector. CAPod decodes the - // 4-set × 8-band Float32 payload verbatim; captures so far have all-zero - // payloads so the schema is unconfirmed. Real EQ likely lives on 0x54 - // "Set Band Edges" with a different byte layout. + // 0x53 is "PME Config" per the Wireshark AAP dissector — Personal Medical + // Equipment (cf. PPE), i.e. the iOS 18.1+ hearing-aid profile on AirPods + // Pro 2. Decoded verbatim as 4 × 8 Float32 (per-ear × per-profile band + // gains); stock firmware reports all-zero until the user runs Apple's + // Hearing Test. 0x54 "Set Band Edges" is a neighbouring opcode with a + // different payload — not decoded here. if (message.commandType == AapMessageType.PME_CONFIG.value) { if (message.payload.size < 6 + 128) return null val sets = mutableListOf>()