diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt index f47ce53a..aeb2c1d2 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt @@ -27,7 +27,8 @@ class AppleFactory @Inject constructor( data class KnownDevice( val identifier: UUID, - val scanResult: ScanResult + val scanResult: ScanResult, + val message: ProximityPairing.Message, ) { val address: String get() = scanResult.device.address @@ -72,25 +73,47 @@ class AppleFactory @Inject constructor( return proximityMessage } - private suspend fun recognizeDevice(scanResult: ScanResult): UUID { + private suspend fun recognizeDevice(scanResult: ScanResult, message: ProximityPairing.Message): UUID { val address = scanResult.device.address var identifier: UUID? = null knownDevs.values.firstOrNull { it.address == address }?.let { log(TAG, VERBOSE) { "recognizeDevice: Recovered previous ID via address: $it" } - knownDevs[it.identifier] = it.copy(scanResult = scanResult) + knownDevs[it.identifier] = it.copy( + scanResult = scanResult, + message = message, + ) identifier = it.identifier } + if (identifier == null) { + val currentMarkers = message.getRecogMarkers() + knownDevs.values + .firstOrNull { it.message.getRecogMarkers() == currentMarkers } + ?.let { + log(TAG, DEBUG) { "recognizeDevice: Close match based similarity markers." } + log(TAG, DEBUG) { "recognizeDevice: Old marker: ${it.message.getRecogMarkers()}" } + log(TAG, DEBUG) { "recognizeDevice: New marker: $currentMarkers" } + knownDevs[it.identifier] = it.copy( + scanResult = scanResult, + message = message, + ) + identifier = it.identifier + } + } + if (identifier == null) { knownDevs.values .firstOrNull { it.rssi > -60 && !it.isOlderThan(Duration.ofSeconds(10)) } ?.let { - log(TAG, VERBOSE) { "recognizeDevice: Close match based on RSSI and timestamp." } - knownDevs[it.identifier] = it.copy(scanResult = scanResult) + log(TAG, DEBUG) { "recognizeDevice: Close match based on RSSI and timestamp." } + knownDevs[it.identifier] = it.copy( + scanResult = scanResult, + message = message, + ) identifier = it.identifier } } @@ -103,7 +126,8 @@ class AppleFactory @Inject constructor( knownDevs[identifier!!] = KnownDevice( identifier = identifier!!, - scanResult = scanResult + scanResult = scanResult, + message = message ) knownDevs.values.toList().forEach { knownDevice -> @@ -119,7 +143,7 @@ class AppleFactory @Inject constructor( suspend fun create(scanResult: ScanResult): PodDevice? = mutex.withLock { val pm = getMessage(scanResult) ?: return@withLock null - val identifier = recognizeDevice(scanResult) + val identifier = recognizeDevice(scanResult, pm) log(TAG, INFO) { val data = scanResult.scanRecord!!.getManufacturerSpecificData( @@ -131,6 +155,14 @@ class AppleFactory @Inject constructor( "Decoding (MAC=${scanResult.device.address}, nanos=${scanResult.timestampNanos}, rssi=${scanResult.rssi}): $dataHex" } + return createPodDevice(scanResult, pm, identifier) + } + + private fun createPodDevice( + scanResult: ScanResult, + pm: ProximityPairing.Message, + identifier: UUID + ): ApplePods { val dm = ( ((pm.data[1].toInt() and 255) shl 8) or (pm.data[2].toInt() and 255) ).toUShort() diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt index 8f1df3dd..d81626e3 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt @@ -3,6 +3,8 @@ package eu.darken.capod.pods.core.apple.protocol import android.bluetooth.le.ScanFilter import dagger.Reusable import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.lowerNibble +import eu.darken.capod.common.upperNibble import eu.darken.capod.pods.core.apple.ApplePods import javax.inject.Inject @@ -13,7 +15,24 @@ object ProximityPairing { val type: UByte, val length: Int, val data: UByteArray - ) + ) { + data class Markers( + val vendor: UByte, + val length: UByte, + val device: UShort, + val batteryData: Set, + val deviceColor: UByte, + ) + + fun getRecogMarkers(): Markers = Markers( + vendor = CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING, + 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), + deviceColor = data[7] + ) + } @Reusable class Decoder @Inject constructor() {