diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/PodDevice.kt b/app-common/src/main/java/eu/darken/capod/pods/core/PodDevice.kt index bcc09ceb..d3532b49 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/PodDevice.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/PodDevice.kt @@ -133,6 +133,9 @@ interface PodDevice { @Json(name = "beats.powerbeats.pro") POWERBEATS_PRO( "Power Beats Pro" ), + @Json(name = "beats.powerbeats.pro2") POWERBEATS_PRO2( + "Power Beats Pro 2" + ), @Json(name = "beats.fit.pro") BEATS_FIT_PRO( "Beats Fit Pro" ), diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt index 1cd582b7..64af3c58 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt @@ -23,6 +23,7 @@ import eu.darken.capod.pods.core.apple.beats.BeatsX import eu.darken.capod.pods.core.apple.beats.PowerBeats3 import eu.darken.capod.pods.core.apple.beats.PowerBeats4 import eu.darken.capod.pods.core.apple.beats.PowerBeatsPro +import eu.darken.capod.pods.core.apple.beats.PowerBeatsPro2 import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen1 import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen2 import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen3 @@ -52,6 +53,7 @@ abstract class AppleFactoryModule { @Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory @Binds @IntoSet abstract fun powerBeats4(factory: PowerBeats4.Factory): ApplePodsFactory @Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun powerBeatsPro2(factory: PowerBeatsPro2.Factory): ApplePodsFactory @Binds @IntoSet abstract fun beatsFitPro(factory: BeatsFitPro.Factory): ApplePodsFactory @Binds @IntoSet abstract fun fakeAirPodsGen1(factory: FakeAirPodsGen1.Factory): ApplePodsFactory diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt index e6844cc0..029eb2ce 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt @@ -43,7 +43,7 @@ data class PowerBeatsPro( ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): 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( @@ -74,7 +74,7 @@ data class PowerBeatsPro( } companion object { - private val DEVICE_CODE_DIRTY = 11.toUByte() + private val DEVICE_CODE = 0x0B20.toUShort() private val TAG = logTag("PodDevice", "Beats", "PowerBeats", "Pro") } } \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2.kt new file mode 100644 index 00000000..c5ef4340 --- /dev/null +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2.kt @@ -0,0 +1,80 @@ +package eu.darken.capod.pods.core.apple.beats + +import eu.darken.capod.common.bluetooth.BleScanResult +import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.apple.ApplePods +import eu.darken.capod.pods.core.apple.ApplePodsFactory +import eu.darken.capod.pods.core.apple.DualApplePods +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage +import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload +import java.time.Instant +import javax.inject.Inject + +data class PowerBeatsPro2( + override val identifier: PodDevice.Id = PodDevice.Id(), + override val seenLastAt: Instant = Instant.now(), + override val seenFirstAt: Instant = Instant.now(), + override val seenCounter: Int = 1, + override val scanResult: BleScanResult, + override val payload: ProximityPayload, + override val flags: ApplePods.Flags, + override val reliability: Float = PodDevice.BASE_CONFIDENCE, + private val rssiAverage: Int? = null, + private val cachedBatteryPercentage: Float? = null, + private val cachedCaseState: DualApplePods.LidState? = null +) : DualApplePods { + + override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO2 + + override val batteryCasePercent: Float? + get() = super.batteryCasePercent ?: cachedBatteryPercentage + + override val caseLidState: DualApplePods.LidState + get() = cachedCaseState ?: super.caseLidState + + override val rssi: Int + get() = rssiAverage ?: super.rssi + + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { + + override fun isResponsible(message: ProximityMessage): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } + + override fun create( + scanResult: BleScanResult, + payload: ProximityPayload, + flags: ApplePods.Flags + ): ApplePods { + var basic = PowerBeatsPro2(scanResult = scanResult, payload = payload, flags = flags) + val result = repo.search(basic) + + if (result != null) basic = basic.copy(identifier = result.id) + repo.updateHistory(basic) + + if (result == null) return basic + + return basic.copy( + identifier = result.id, + seenFirstAt = result.seenFirstAt, + seenLastAt = scanResult.receivedAt, + seenCounter = result.seenCounter, + reliability = result.reliability, + cachedBatteryPercentage = result.getLatestCaseBattery(), + rssiAverage = result.rssiSmoothed(basic.rssi), + cachedCaseState = result.getLatestCaseLidState(basic) + ) + } + + } + + companion object { + private val DEVICE_CODE = 0x1D20.toUShort() + private val TAG = logTag("PodDevice", "Beats", "PowerBeats", "Pro2") + } +} \ No newline at end of file diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2Test.kt new file mode 100644 index 00000000..46ce4394 --- /dev/null +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2Test.kt @@ -0,0 +1,61 @@ +package eu.darken.capod.pods.core.apple.beats + +import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.apple.BaseAirPodsTest +import eu.darken.capod.pods.core.apple.HasAppleColor +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test + +class PowerBeatsPro2Test : BaseAirPodsTest() { + + @Test + fun `test PowerBeatsPro2`() = runTest { + create("07 19 01 1D 20 2B AA 8F 01 02 04 30 42 6F CE 74 EE ED 56 AF 4E 31 16 9D 00 D7 DE") { + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1D20.toUShort() + pubStatus shouldBe 0x2B.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x02.toUByte() + pubSuffix shouldBe 0x04.toUByte() + + isLeftPodMicrophone shouldBe true + isRightPodMicrophone shouldBe false + + batteryLeftPodPercent shouldBe 1.0f + batteryRightPodPercent shouldBe 1.0f + + isCaseCharging shouldBe false + isRightPodCharging shouldBe false + isLeftPodCharging shouldBe false + batteryCasePercent shouldBe null + + podStyle.identifier shouldBe HasAppleColor.DeviceColor.RED.name + + model shouldBe PodDevice.Model.POWERBEATS_PRO2 + } + } + + @Test + fun `test PowerBeatsPro2 - variant 2`() = runTest { + create("07 19 01 1D 20 73 AA 98 32 02 04 7D 47 B0 15 DE 5F B6 6B CB 46 F9 16 5C 88 4F 4E") { + isLeftPodMicrophone shouldBe false + isRightPodMicrophone shouldBe true + + batteryLeftPodPercent shouldBe 1.0f + batteryRightPodPercent shouldBe 1.0f + + isCaseCharging shouldBe false + isRightPodCharging shouldBe false + isLeftPodCharging shouldBe true + batteryCasePercent shouldBe 0.8f + + podStyle.identifier shouldBe HasAppleColor.DeviceColor.RED.name + + model shouldBe PodDevice.Model.POWERBEATS_PRO2 + } + } +} \ No newline at end of file