mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 19:26:12 -04:00
Add support for PowerBeats4
Thanks to @docgalaxyblock
This commit is contained in:
@@ -114,6 +114,9 @@ interface PodDevice {
|
|||||||
@Json(name = "beats.powerbeats.3") POWERBEATS_3(
|
@Json(name = "beats.powerbeats.3") POWERBEATS_3(
|
||||||
"Power Beats 3"
|
"Power Beats 3"
|
||||||
),
|
),
|
||||||
|
@Json(name = "beats.powerbeats.4") POWERBEATS_4(
|
||||||
|
"Power Beats 4"
|
||||||
|
),
|
||||||
@Json(name = "beats.powerbeats.pro") POWERBEATS_PRO(
|
@Json(name = "beats.powerbeats.pro") POWERBEATS_PRO(
|
||||||
"Power Beats Pro"
|
"Power Beats Pro"
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ abstract class AppleFactoryModule {
|
|||||||
@Binds @IntoSet abstract fun beatsStudio3(factory: BeatsStudio3.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun beatsStudio3(factory: BeatsStudio3.Factory): ApplePodsFactory<out ApplePods>
|
||||||
@Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePodsFactory<out ApplePods>
|
||||||
@Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory<out ApplePods>
|
||||||
|
@Binds @IntoSet abstract fun powerBeats4(factory: PowerBeats4.Factory): ApplePodsFactory<out ApplePods>
|
||||||
@Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory<out ApplePods>
|
||||||
@Binds @IntoSet abstract fun beatsFitPro(factory: BeatsFitPro.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun beatsFitPro(factory: BeatsFitPro.Factory): ApplePodsFactory<out ApplePods>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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.SingleApplePods
|
||||||
|
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||||
|
import eu.darken.capod.pods.core.apple.airpods.HasStateDetectionAirPods
|
||||||
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
|
import java.time.Instant
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
data class PowerBeats4(
|
||||||
|
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 proximityMessage: ProximityPairing.Message,
|
||||||
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
|
private val rssiAverage: Int? = null,
|
||||||
|
) : SingleApplePods, HasStateDetectionAirPods {
|
||||||
|
|
||||||
|
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_4
|
||||||
|
|
||||||
|
override val rssi: Int
|
||||||
|
get() = rssiAverage ?: super<SingleApplePods>.rssi
|
||||||
|
|
||||||
|
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||||
|
|
||||||
|
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||||
|
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||||
|
var basic = PowerBeats4(scanResult = scanResult, proximityMessage = message)
|
||||||
|
val result = searchHistory(basic)
|
||||||
|
|
||||||
|
if (result != null) basic = basic.copy(identifier = result.id)
|
||||||
|
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 = 0x0D20.toUShort()
|
||||||
|
private val TAG = logTag("PodDevice", "Beats", "PowerBeats", "4")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,4 +40,33 @@ class BeatsFitProTest : BaseAirPodsTest() {
|
|||||||
model shouldBe PodDevice.Model.BEATS_FIT_PRO
|
model shouldBe PodDevice.Model.BEATS_FIT_PRO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `extra rl test case`() = runTest {
|
||||||
|
create<BeatsFitPro>("07 19 01 12 20 04 FA 92 54 11 24 CE B1 DF 9D 8D F5 E3 37 60 B1 23 8B 90 3B 63 3F") {
|
||||||
|
rawPrefix shouldBe 0x01.toUByte()
|
||||||
|
rawDeviceModel shouldBe 0x1220.toUShort()
|
||||||
|
rawStatus shouldBe 0x04.toUByte()
|
||||||
|
rawPodsBattery shouldBe 0xFA.toUByte()
|
||||||
|
rawFlags shouldBe 0x9.toUShort()
|
||||||
|
rawCaseBattery shouldBe 0x2.toUShort()
|
||||||
|
rawCaseLidState shouldBe 0x54.toUByte()
|
||||||
|
rawDeviceColor shouldBe 0x11.toUByte()
|
||||||
|
rawSuffix shouldBe 0x24.toUByte()
|
||||||
|
|
||||||
|
isLeftPodMicrophone shouldBe false
|
||||||
|
isRightPodMicrophone shouldBe true
|
||||||
|
|
||||||
|
batteryLeftPodPercent shouldBe null
|
||||||
|
batteryRightPodPercent shouldBe 1.0f
|
||||||
|
|
||||||
|
isCaseCharging shouldBe false
|
||||||
|
isRightPodCharging shouldBe true
|
||||||
|
isLeftPodCharging shouldBe false
|
||||||
|
batteryCasePercent shouldBe 0.2f
|
||||||
|
podStyle.identifier shouldBe HasAppleColor.DeviceColor.UNKNOWN.name
|
||||||
|
|
||||||
|
model shouldBe PodDevice.Model.BEATS_FIT_PRO
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
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.airpods.HasStateDetectionAirPods
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class PowerBeats4Test : BaseAirPodsTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `playing music`() = runTest {
|
||||||
|
create<PowerBeats4>("07 19 01 0D 20 00 02 80 01 00 05 07 C5 E1 9C BD 9A 05 0E AE 9E 56 53 2F F9 75 4A") {
|
||||||
|
rawPrefix shouldBe 0x01.toUByte()
|
||||||
|
rawDeviceModel shouldBe 0x0D20.toUShort()
|
||||||
|
rawStatus shouldBe 0x0.toUByte()
|
||||||
|
rawPodsBattery shouldBe 0x02.toUByte()
|
||||||
|
rawFlags shouldBe 0x8.toUShort()
|
||||||
|
rawCaseBattery shouldBe 0x0.toUShort()
|
||||||
|
rawCaseLidState shouldBe 0x01.toUByte()
|
||||||
|
rawDeviceColor shouldBe 0x00.toUByte()
|
||||||
|
rawSuffix shouldBe 0x05.toUByte()
|
||||||
|
|
||||||
|
batteryHeadsetPercent shouldBe 0.2f
|
||||||
|
|
||||||
|
model shouldBe PodDevice.Model.POWERBEATS_4
|
||||||
|
|
||||||
|
state shouldBe HasStateDetectionAirPods.ConnectionState.MUSIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `extra test case`() = runTest {
|
||||||
|
create<PowerBeats4>("07 19 01 0D 20 00 02 80 02 00 04 80 90 60 77 32 C2 0C 75 7A 3D 7E D7 1C 0E B8 43") {
|
||||||
|
rawPrefix shouldBe 0x01.toUByte()
|
||||||
|
rawDeviceModel shouldBe 0x0D20.toUShort()
|
||||||
|
rawStatus shouldBe 0x0.toUByte()
|
||||||
|
rawPodsBattery shouldBe 0x02.toUByte()
|
||||||
|
rawFlags shouldBe 0x8.toUShort()
|
||||||
|
rawCaseBattery shouldBe 0x0.toUShort()
|
||||||
|
rawCaseLidState shouldBe 0x02.toUByte()
|
||||||
|
rawDeviceColor shouldBe 0x00.toUByte()
|
||||||
|
rawSuffix shouldBe 0x04.toUByte()
|
||||||
|
|
||||||
|
batteryHeadsetPercent shouldBe 0.2f
|
||||||
|
|
||||||
|
model shouldBe PodDevice.Model.POWERBEATS_4
|
||||||
|
|
||||||
|
state shouldBe HasStateDetectionAirPods.ConnectionState.IDLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `disconnected state`() = runTest {
|
||||||
|
create<PowerBeats4>("07 19 01 0D 20 00 02 80 01 00 00 25 DF 66 40 AF 44 7B 77 95 8F D1 92 50 26 11 74") {
|
||||||
|
rawPrefix shouldBe 0x01.toUByte()
|
||||||
|
rawDeviceModel shouldBe 0x0D20.toUShort()
|
||||||
|
rawStatus shouldBe 0x0.toUByte()
|
||||||
|
rawPodsBattery shouldBe 0x02.toUByte()
|
||||||
|
rawFlags shouldBe 0x8.toUShort()
|
||||||
|
rawCaseBattery shouldBe 0x0.toUShort()
|
||||||
|
rawCaseLidState shouldBe 0x01.toUByte()
|
||||||
|
rawDeviceColor shouldBe 0x00.toUByte()
|
||||||
|
rawSuffix shouldBe 0x00.toUByte()
|
||||||
|
|
||||||
|
batteryHeadsetPercent shouldBe 0.2f
|
||||||
|
|
||||||
|
model shouldBe PodDevice.Model.POWERBEATS_4
|
||||||
|
|
||||||
|
state shouldBe HasStateDetectionAirPods.ConnectionState.DISCONNECTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user