mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-15 10:46:12 -04:00
Handle ear detection and microphone detection for dual pods better.
This commit is contained in:
@@ -5,10 +5,16 @@ import java.util.*
|
||||
fun Byte.toHex(): String = String.format("%02X", this)
|
||||
fun UByte.toHex(): String = this.toByte().toHex()
|
||||
|
||||
val Byte.upperNibble get() = (this.toInt() shr 4 and 0b1111).toByte()
|
||||
val Byte.lowerNibble get() = (this.toInt() and 0b1111).toByte()
|
||||
val UByte.upperNibble get() = (this.toInt() shr 4 and 0b1111).toUByte()
|
||||
val UByte.lowerNibble get() = (this.toInt() and 0b1111).toUByte()
|
||||
val Byte.upperNibble get() = (this.toInt() shr 4 and 0b1111).toShort()
|
||||
val Byte.lowerNibble get() = (this.toInt() and 0b1111).toShort()
|
||||
val UByte.upperNibble get() = (this.toInt() shr 4 and 0b1111).toUShort()
|
||||
val UByte.lowerNibble get() = (this.toInt() and 0b1111).toUShort()
|
||||
|
||||
fun Byte.isBitSet(pos: Int): Boolean = BitSet.valueOf(arrayOf(this).toByteArray()).get(pos)
|
||||
fun UByte.isBitSet(pos: Int): Boolean = this.toByte().isBitSet(pos)
|
||||
fun UByte.isBitSet(pos: Int): Boolean = this.toByte().isBitSet(pos)
|
||||
|
||||
fun Short.isBitSet(pos: Int): Boolean = this.toByte().isBitSet(pos)
|
||||
fun UShort.isBitSet(pos: Int): Boolean = this.toShort().isBitSet(pos)
|
||||
|
||||
fun UShort.toBinaryString(): String = Integer.toBinaryString(this.toInt()).padStart(4, '0')
|
||||
fun UByte.toBinaryString(): String = Integer.toBinaryString(this.toInt()).padStart(8, '0')
|
||||
|
||||
+7
-5
@@ -7,7 +7,6 @@ import eu.darken.capod.R
|
||||
import eu.darken.capod.common.lists.binding
|
||||
import eu.darken.capod.databinding.OverviewPodsAppleDualItemBinding
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.HasDualPods.Pod
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods.DeviceColor
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
||||
@@ -31,6 +30,9 @@ class DualApplePodsCardVH(parent: ViewGroup) :
|
||||
text = sb
|
||||
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
||||
else setTypeface(typeface, Typeface.NORMAL)
|
||||
if (item.showDebug) {
|
||||
append(" [${device.primaryPod.name}]")
|
||||
}
|
||||
}
|
||||
deviceIcon.setImageResource(device.iconRes)
|
||||
|
||||
@@ -49,8 +51,8 @@ class DualApplePodsCardVH(parent: ViewGroup) :
|
||||
podLeftChargingIcon.isInvisible = !isLeftPodCharging
|
||||
podLeftChargingLabel.isInvisible = !isLeftPodCharging
|
||||
|
||||
podLeftMicrophoneIcon.isInvisible = microPhonePod != Pod.LEFT
|
||||
podLeftMicrophoneLabel.isInvisible = microPhonePod != Pod.LEFT
|
||||
podLeftMicrophoneIcon.isInvisible = !isLeftPodMicrophone
|
||||
podLeftMicrophoneLabel.isInvisible = !isLeftPodMicrophone
|
||||
|
||||
podLeftWearIcon.isInvisible = !isLeftPodInEar
|
||||
podLeftWearLabel.isInvisible = !isLeftPodInEar
|
||||
@@ -64,8 +66,8 @@ class DualApplePodsCardVH(parent: ViewGroup) :
|
||||
podRightChargingIcon.isInvisible = !isRightPodCharging
|
||||
podRightChargingLabel.isInvisible = !isRightPodCharging
|
||||
|
||||
podRightMicrophoneIcon.isInvisible = microPhonePod != Pod.RIGHT
|
||||
podRightMicrophoneLabel.isInvisible = microPhonePod != Pod.RIGHT
|
||||
podRightMicrophoneIcon.isInvisible = !isRightPodMicrophone
|
||||
podRightMicrophoneLabel.isInvisible = !isRightPodMicrophone
|
||||
|
||||
podRightWearIcon.isInvisible = !isRightPodInEar
|
||||
podRightWearLabel.isInvisible = !isRightPodInEar
|
||||
|
||||
@@ -6,6 +6,9 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.toBinaryString
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.airpods.*
|
||||
import eu.darken.capod.pods.core.apple.beats.*
|
||||
@@ -63,6 +66,14 @@ class AppleFactory @Inject constructor(
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm,
|
||||
)
|
||||
if (device is DualApplePods) {
|
||||
device.apply {
|
||||
log(
|
||||
TAG,
|
||||
WARN
|
||||
) { "${rssi} ${scanResult.address} ${rawStatus.upperNibble.toBinaryString()} ${rawStatus.lowerNibble.toBinaryString()} primary=$primaryPod isCasePod=$isThisPodInThecase" }
|
||||
}
|
||||
}
|
||||
|
||||
if (factory == null && !SILENCED_PMS.contains(device.rawDeviceModel) && scanResult.address != "6E:9E:D1:49:D2:6D") {
|
||||
SILENCED_PMS.add(device.rawDeviceModel)
|
||||
|
||||
@@ -5,6 +5,9 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.toHex
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
@@ -27,11 +30,20 @@ interface ApplePods : PodDevice {
|
||||
val rawStatus: UByte
|
||||
get() = proximityMessage.data[3]
|
||||
|
||||
val rawStatusHex: String
|
||||
get() = rawStatus.toHex()
|
||||
|
||||
val rawPodsBattery: UByte
|
||||
get() = proximityMessage.data[4]
|
||||
|
||||
val rawCaseBattery: UByte
|
||||
get() = proximityMessage.data[5]
|
||||
val rawPodsBatteryHex: String
|
||||
get() = rawPodsBattery.toHex()
|
||||
|
||||
val rawFlags: UShort
|
||||
get() = proximityMessage.data[5].upperNibble
|
||||
|
||||
val rawCaseBattery: UShort
|
||||
get() = proximityMessage.data[5].lowerNibble
|
||||
|
||||
val rawCaseLidState: UByte
|
||||
get() = proximityMessage.data[6]
|
||||
|
||||
@@ -14,17 +14,24 @@ import eu.darken.capod.pods.core.HasDualPods.Pod
|
||||
|
||||
interface DualApplePods : ApplePods, HasDualPods, HasDualEarDetection, HasCase {
|
||||
|
||||
val microPhonePod: Pod
|
||||
val primaryPod: Pod
|
||||
get() = when (rawStatus.isBitSet(5)) {
|
||||
true -> Pod.LEFT
|
||||
false -> Pod.RIGHT
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone)
|
||||
* If the right pod is the primary, the values are flipped.
|
||||
*/
|
||||
val areValuesFlipped: Boolean
|
||||
get() = !rawStatus.isBitSet(5)
|
||||
|
||||
override val batteryLeftPodPercent: Float?
|
||||
get() {
|
||||
val value = when (microPhonePod) {
|
||||
Pod.LEFT -> rawPodsBattery.lowerNibble.toInt()
|
||||
Pod.RIGHT -> rawPodsBattery.upperNibble.toInt()
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.upperNibble.toInt()
|
||||
false -> rawPodsBattery.lowerNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
@@ -39,9 +46,9 @@ interface DualApplePods : ApplePods, HasDualPods, HasDualEarDetection, HasCase {
|
||||
|
||||
override val batteryRightPodPercent: Float?
|
||||
get() {
|
||||
val value = when (microPhonePod) {
|
||||
Pod.LEFT -> rawPodsBattery.upperNibble.toInt()
|
||||
Pod.RIGHT -> rawPodsBattery.lowerNibble.toInt()
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.lowerNibble.toInt()
|
||||
false -> rawPodsBattery.upperNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
@@ -53,32 +60,56 @@ interface DualApplePods : ApplePods, HasDualPods, HasDualEarDetection, HasCase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isThisPodInThecase: Boolean
|
||||
get() = rawStatus.isBitSet(6)
|
||||
|
||||
val isOnePodInCase: Boolean
|
||||
get() = rawStatus.isBitSet(4)
|
||||
|
||||
val areBothPodsInCase: Boolean
|
||||
get() = rawStatus.isBitSet(2)
|
||||
|
||||
override val isLeftPodInEar: Boolean
|
||||
get() = when (microPhonePod) {
|
||||
Pod.LEFT -> rawStatus.isBitSet(1)
|
||||
Pod.RIGHT -> rawStatus.isBitSet(3)
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(3)
|
||||
false -> rawStatus.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodInEar: Boolean
|
||||
get() = when (microPhonePod) {
|
||||
Pod.LEFT -> rawStatus.isBitSet(3)
|
||||
Pod.RIGHT -> rawStatus.isBitSet(1)
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(1)
|
||||
false -> rawStatus.isBitSet(3)
|
||||
}
|
||||
|
||||
/**
|
||||
* The data flip bit is set if the left pod is primary.
|
||||
* For the pod that is in the case, this is flipped again though.
|
||||
*/
|
||||
val isLeftPodMicrophone: Boolean
|
||||
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||
|
||||
/**
|
||||
* The data flip bit is UNset if the right pod is primary.
|
||||
* For the pod that is in the case, this is flipped again though.
|
||||
*/
|
||||
val isRightPodMicrophone: Boolean
|
||||
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||
|
||||
val isLeftPodCharging: Boolean
|
||||
get() = when (microPhonePod) {
|
||||
Pod.LEFT -> rawCaseBattery.upperNibble.isBitSet(0)
|
||||
Pod.RIGHT -> rawCaseBattery.upperNibble.isBitSet(1)
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(0)
|
||||
true -> rawFlags.isBitSet(1)
|
||||
}
|
||||
|
||||
val isRightPodCharging: Boolean
|
||||
get() = when (microPhonePod) {
|
||||
Pod.LEFT -> rawCaseBattery.upperNibble.isBitSet(1)
|
||||
Pod.RIGHT -> rawCaseBattery.upperNibble.isBitSet(0)
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(1)
|
||||
true -> rawFlags.isBitSet(0)
|
||||
}
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = when (val value = rawCaseBattery.lowerNibble.toInt()) {
|
||||
get() = when (val value = rawCaseBattery.toInt()) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
@@ -89,7 +120,7 @@ interface DualApplePods : ApplePods, HasDualPods, HasDualEarDetection, HasCase {
|
||||
}
|
||||
|
||||
override val isCaseCharging: Boolean
|
||||
get() = rawCaseBattery.upperNibble.isBitSet(2)
|
||||
get() = rawFlags.isBitSet(2)
|
||||
|
||||
val caseLidState: LidState
|
||||
get() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.HasEarDetection
|
||||
import eu.darken.capod.pods.core.HasSinglePod
|
||||
|
||||
@@ -11,7 +10,7 @@ interface SingleApplePods : BasicSingleApplePods, HasEarDetection, HasSinglePod
|
||||
get() = rawStatus.isBitSet(1)
|
||||
|
||||
val isHeadsetBeingCharged: Boolean
|
||||
get() = rawCaseBattery.upperNibble.isBitSet(0)
|
||||
get() = rawFlags.isBitSet(0)
|
||||
|
||||
override val isBeingWorn: Boolean
|
||||
get() = isHeadphonesBeingWorn
|
||||
|
||||
@@ -19,7 +19,8 @@ object ProximityPairing {
|
||||
val vendor: UByte,
|
||||
val length: UByte,
|
||||
val device: UShort,
|
||||
val batteryData: Set<UByte>,
|
||||
val podBatteryData: Set<UShort>,
|
||||
val caseBatteryData: UShort,
|
||||
val deviceColor: UByte,
|
||||
)
|
||||
|
||||
@@ -28,7 +29,8 @@ object ProximityPairing {
|
||||
length = PROXIMITY_PAIRING_MESSAGE_LENGTH.toUByte(),
|
||||
device = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(),
|
||||
// Make comparison order independent
|
||||
batteryData = setOf(data[4].upperNibble, data[4].lowerNibble),
|
||||
podBatteryData = setOf(data[4].upperNibble, data[4].lowerNibble),
|
||||
caseBatteryData = data[5].lowerNibble,
|
||||
deviceColor = data[7]
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user