Skip to content

Commit

Permalink
Fix AirPods Max and AirPods Max (USB-C) support
Browse files Browse the repository at this point in the history
Closes #236
  • Loading branch information
d4rken committed Nov 8, 2024
1 parent 663e2d3 commit 6b922e8
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ interface PodDevice {
"AirPods Max",
R.drawable.devic_headphones_generic
),
@Json(name = "airpods.max2") AIRPODS_MAX2(
"AirPods Max 2",
@Json(name = "airpods.max.usbc") AIRPODS_MAX_USBC(
"AirPods Max USB-C",
R.drawable.devic_headphones_generic
),
@Json(name = "beats.flex") BEATS_FLEX(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import eu.darken.capod.pods.core.apple.airpods.AirPodsGen3
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen4
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen4Anc
import eu.darken.capod.pods.core.apple.airpods.AirPodsMax
import eu.darken.capod.pods.core.apple.airpods.AirPodsMax2
import eu.darken.capod.pods.core.apple.airpods.AirPodsMaxUsbc
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro2
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro2Usbc
Expand Down Expand Up @@ -43,7 +43,7 @@ abstract class AppleFactoryModule {
@Binds @IntoSet abstract fun airPodsPro2(factory: AirPodsPro2.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun airPodsPro2Usbc(factory: AirPodsPro2Usbc.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun airPodsMax2(factory: AirPodsMax2.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun airPodsMax2(factory: AirPodsMaxUsbc.Factory): ApplePodsFactory<out ApplePods>

@Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun beatsSolo3(factory: BeatsSolo3.Factory): ApplePodsFactory<out ApplePods>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ data class AirPodsMax(
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {

override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
}

override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
Expand All @@ -63,7 +63,7 @@ data class AirPodsMax(
}

companion object {
private val DEVICE_CODE_DIRTY = 0x200A.toUByte()
private val DEVICE_CODE = 0x0A20.toUShort()
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import javax.inject.Inject

data class AirPodsMax2(
data class AirPodsMaxUsbc(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
Expand All @@ -25,7 +25,7 @@ data class AirPodsMax2(
private val rssiAverage: Int? = null,
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {

override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX2
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX_USBC

override val rssi: Int
get() = rssiAverage ?: super<SingleApplePods>.rssi
Expand All @@ -39,11 +39,11 @@ data class AirPodsMax2(
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {

override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
}

override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
var basic = AirPodsMax2(scanResult = scanResult, proximityMessage = message)
var basic = AirPodsMaxUsbc(scanResult = scanResult, proximityMessage = message)
val result = searchHistory(basic)

if (result != null) basic = basic.copy(identifier = result.id)
Expand All @@ -63,7 +63,7 @@ data class AirPodsMax2(
}

companion object {
private val DEVICE_CODE_DIRTY = 0x201F.toUByte()
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max2")
private val DEVICE_CODE = 0x1F20.toUShort()
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max", "USBC")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package eu.darken.capod.pods.core.apple.airpods

import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test

class AirPodsMaxUsbcTest : BaseAirPodsTest() {

// Test data from https://github.com/d4rken-org/capod/issues/236
@Test
fun `default AirPods Max`() = runTest {
create<AirPodsMaxUsbc>("07 19 01 1F 20 2B 05 80 03 12 C5 2E 8B F9 9A 7E 19 7B 63 0F 30 6E D7 3B E2 EC 32") {
rawPrefix shouldBe 0x01.toUByte()
rawDeviceModel shouldBe 0x1F20.toUShort()
rawStatus shouldBe 0x2B.toUByte()
rawPodsBattery shouldBe 0x05.toUByte()
rawFlags shouldBe 0x8.toUShort()
rawCaseBattery shouldBe 0x0.toUShort()
rawCaseLidState shouldBe 0x03.toUByte()
rawDeviceColor shouldBe 0x12.toUByte()
rawSuffix shouldBe 0xC5.toUByte()

batteryHeadsetPercent shouldBe 0.5f

isHeadsetBeingCharged shouldBe false

model shouldBe PodDevice.Model.AIRPODS_MAX_USBC
}
}
}

0 comments on commit 6b922e8

Please sign in to comment.