feat: Add BLE device support for 6 new Beats models

Solo Pro (0x0C20), Solo 4 (0x2520), Solo Buds (0x2620), Studio Buds (0x1120), Studio Buds+ (0x1620), Studio Pro (0x1720). Tests use handcrafted data pending real captures.
This commit is contained in:
darken
2026-03-31 19:17:09 +02:00
committed by Matthias Urhahn
parent c860ecc430
commit f65c6e4a29
13 changed files with 618 additions and 0 deletions
@@ -20,7 +20,13 @@ import eu.darken.capod.pods.core.apple.ble.devices.airpods.AirPodsPro3
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsFitPro
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsFlex
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsSolo3
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsSolo4
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsSoloBuds
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsSoloPro
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsStudio3
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsStudioBuds
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsStudioBudsPlus
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsStudioPro
import eu.darken.capod.pods.core.apple.ble.devices.beats.BeatsX
import eu.darken.capod.pods.core.apple.ble.devices.beats.PowerBeats3
import eu.darken.capod.pods.core.apple.ble.devices.beats.PowerBeats4
@@ -58,6 +64,12 @@ abstract class AppleFactoryModule {
@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 beatsSoloPro(factory: BeatsSoloPro.Factory): ApplePodsFactory
@Binds @IntoSet abstract fun beatsSolo4(factory: BeatsSolo4.Factory): ApplePodsFactory
@Binds @IntoSet abstract fun beatsSoloBuds(factory: BeatsSoloBuds.Factory): ApplePodsFactory
@Binds @IntoSet abstract fun beatsStudioBuds(factory: BeatsStudioBuds.Factory): ApplePodsFactory
@Binds @IntoSet abstract fun beatsStudioBudsPlus(factory: BeatsStudioBudsPlus.Factory): ApplePodsFactory
@Binds @IntoSet abstract fun beatsStudioPro(factory: BeatsStudioPro.Factory): ApplePodsFactory
@Binds @IntoSet abstract fun fakeAirPodsGen1(factory: FakeAirPodsGen1.Factory): ApplePodsFactory
@Binds @IntoSet abstract fun fakeAirPodsGen2(factory: FakeAirPodsGen2.Factory): ApplePodsFactory
@@ -0,0 +1,68 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.pods.core.apple.ble.devices.ApplePodsFactory
import eu.darken.capod.pods.core.apple.ble.devices.SingleApplePods
import eu.darken.capod.pods.core.apple.ble.history.PodHistoryRepo
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityMessage
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPayload
import java.time.Instant
import javax.inject.Inject
data class BeatsSolo4(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.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 meta: ApplePods.AppleMeta,
override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
override val model: PodModel = PodModel.BEATS_SOLO_4
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 suspend fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsSolo4(scanResult = scanResult, payload = payload, meta = meta)
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,
rssiAverage = result.rssiSmoothed(basic.rssi),
)
}
}
companion object {
private val DEVICE_CODE = 0x2520.toUShort()
private val TAG = logTag("PodDevice", "Beats", "Solo", "4")
}
}
@@ -0,0 +1,93 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.pods.core.apple.ble.devices.ApplePodsFactory
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.history.PodHistoryRepo
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityMessage
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPayload
import java.time.Instant
import javax.inject.Inject
data class BeatsSoloBuds(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.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 meta: ApplePods.AppleMeta,
override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods {
override val model: PodModel = PodModel.BEATS_SOLO_BUDS
@get:DrawableRes
override val leftPodIcon: Int
get() = R.drawable.device_beats_fitpro_left
@get:DrawableRes
override val rightPodIcon: Int
get() = R.drawable.device_beats_fitpro_right
@get:DrawableRes
override val caseIcon: Int
get() = R.drawable.device_beats_fitpro_case
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 suspend fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsSoloBuds(scanResult = scanResult, payload = payload, meta = meta)
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 = 0x2620.toUShort()
private val TAG = logTag("PodDevice", "Beats", "Solo", "Buds")
}
}
@@ -0,0 +1,68 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.pods.core.apple.ble.devices.ApplePodsFactory
import eu.darken.capod.pods.core.apple.ble.devices.SingleApplePods
import eu.darken.capod.pods.core.apple.ble.history.PodHistoryRepo
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityMessage
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPayload
import java.time.Instant
import javax.inject.Inject
data class BeatsSoloPro(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.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 meta: ApplePods.AppleMeta,
override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
override val model: PodModel = PodModel.BEATS_SOLO_PRO
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 suspend fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsSoloPro(scanResult = scanResult, payload = payload, meta = meta)
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,
rssiAverage = result.rssiSmoothed(basic.rssi),
)
}
}
companion object {
private val DEVICE_CODE = 0x0C20.toUShort()
private val TAG = logTag("PodDevice", "Beats", "Solo", "Pro")
}
}
@@ -0,0 +1,93 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.pods.core.apple.ble.devices.ApplePodsFactory
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.history.PodHistoryRepo
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityMessage
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPayload
import java.time.Instant
import javax.inject.Inject
data class BeatsStudioBuds(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.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 meta: ApplePods.AppleMeta,
override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods {
override val model: PodModel = PodModel.BEATS_STUDIO_BUDS
@get:DrawableRes
override val leftPodIcon: Int
get() = R.drawable.device_beats_fitpro_left
@get:DrawableRes
override val rightPodIcon: Int
get() = R.drawable.device_beats_fitpro_right
@get:DrawableRes
override val caseIcon: Int
get() = R.drawable.device_beats_fitpro_case
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 suspend fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsStudioBuds(scanResult = scanResult, payload = payload, meta = meta)
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 = 0x1120.toUShort()
private val TAG = logTag("PodDevice", "Beats", "Studio", "Buds")
}
}
@@ -0,0 +1,93 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.pods.core.apple.ble.devices.ApplePodsFactory
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.history.PodHistoryRepo
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityMessage
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPayload
import java.time.Instant
import javax.inject.Inject
data class BeatsStudioBudsPlus(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.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 meta: ApplePods.AppleMeta,
override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods {
override val model: PodModel = PodModel.BEATS_STUDIO_BUDS_PLUS
@get:DrawableRes
override val leftPodIcon: Int
get() = R.drawable.device_beats_fitpro_left
@get:DrawableRes
override val rightPodIcon: Int
get() = R.drawable.device_beats_fitpro_right
@get:DrawableRes
override val caseIcon: Int
get() = R.drawable.device_beats_fitpro_case
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 suspend fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsStudioBudsPlus(scanResult = scanResult, payload = payload, meta = meta)
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 = 0x1620.toUShort()
private val TAG = logTag("PodDevice", "Beats", "Studio", "Buds", "Plus")
}
}
@@ -0,0 +1,68 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.pods.core.apple.ble.devices.ApplePodsFactory
import eu.darken.capod.pods.core.apple.ble.devices.SingleApplePods
import eu.darken.capod.pods.core.apple.ble.history.PodHistoryRepo
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityMessage
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPayload
import java.time.Instant
import javax.inject.Inject
data class BeatsStudioPro(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.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 meta: ApplePods.AppleMeta,
override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
override val model: PodModel = PodModel.BEATS_STUDIO_PRO
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 suspend fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsStudioPro(scanResult = scanResult, payload = payload, meta = meta)
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,
rssiAverage = result.rssiSmoothed(basic.rssi),
)
}
}
companion object {
private val DEVICE_CODE = 0x1720.toUShort()
private val TAG = logTag("PodDevice", "Beats", "Studio", "Pro")
}
}
@@ -0,0 +1,20 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class BeatsSolo4Test : BaseBlePodsTest() {
// TODO Replace with real device data
@Test
fun `default BeatsSolo4`() = runTest {
create<BeatsSolo4>("07 19 01 25 20 62 04 80 01 0F 40 0D 70 50 16 F2 40 83 16 BF 10 16 34 9B 74 84 E8") {
pubDeviceModel shouldBe 0x2520.toUShort()
batteryHeadsetPercent shouldBe 0.4f
model shouldBe PodModel.BEATS_SOLO_4
}
}
}
@@ -0,0 +1,21 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class BeatsSoloBudsTest : BaseBlePodsTest() {
// TODO Replace with real device data
@Test
fun `default BeatsSoloBuds`() = runTest {
create<BeatsSoloBuds>("07 19 01 26 20 20 FA 8F 01 00 24 9B 9B 23 52 60 5A 8C 32 1C A5 C2 81 51 82 AF C8") {
pubDeviceModel shouldBe 0x2620.toUShort()
batteryLeftPodPercent shouldBe 1.0f
batteryRightPodPercent shouldBe null
model shouldBe PodModel.BEATS_SOLO_BUDS
}
}
}
@@ -0,0 +1,20 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class BeatsSoloProTest : BaseBlePodsTest() {
// TODO Replace with real device data
@Test
fun `default BeatsSoloPro`() = runTest {
create<BeatsSoloPro>("07 19 01 0C 20 62 04 80 01 0F 40 0D 70 50 16 F2 40 83 16 BF 10 16 34 9B 74 84 E8") {
pubDeviceModel shouldBe 0x0C20.toUShort()
batteryHeadsetPercent shouldBe 0.4f
model shouldBe PodModel.BEATS_SOLO_PRO
}
}
}
@@ -0,0 +1,21 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class BeatsStudioBudsPlusTest : BaseBlePodsTest() {
// TODO Replace with real device data
@Test
fun `default BeatsStudioBudsPlus`() = runTest {
create<BeatsStudioBudsPlus>("07 19 01 16 20 20 FA 8F 01 00 24 9B 9B 23 52 60 5A 8C 32 1C A5 C2 81 51 82 AF C8") {
pubDeviceModel shouldBe 0x1620.toUShort()
batteryLeftPodPercent shouldBe 1.0f
batteryRightPodPercent shouldBe null
model shouldBe PodModel.BEATS_STUDIO_BUDS_PLUS
}
}
}
@@ -0,0 +1,21 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class BeatsStudioBudsTest : BaseBlePodsTest() {
// TODO Replace with real device data
@Test
fun `default BeatsStudioBuds`() = runTest {
create<BeatsStudioBuds>("07 19 01 11 20 20 FA 8F 01 00 24 9B 9B 23 52 60 5A 8C 32 1C A5 C2 81 51 82 AF C8") {
pubDeviceModel shouldBe 0x1120.toUShort()
batteryLeftPodPercent shouldBe 1.0f
batteryRightPodPercent shouldBe null
model shouldBe PodModel.BEATS_STUDIO_BUDS
}
}
}
@@ -0,0 +1,20 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class BeatsStudioProTest : BaseBlePodsTest() {
// TODO Replace with real device data
@Test
fun `default BeatsStudioPro`() = runTest {
create<BeatsStudioPro>("07 19 01 17 20 62 04 80 01 0F 40 0D 70 50 16 F2 40 83 16 BF 10 16 34 9B 74 84 E8") {
pubDeviceModel shouldBe 0x1720.toUShort()
batteryHeadsetPercent shouldBe 0.4f
model shouldBe PodModel.BEATS_STUDIO_PRO
}
}
}