Improve sorting and heuristics.

This commit is contained in:
darken
2022-01-29 20:00:04 +01:00
parent 0f924ecb82
commit 3fc18bc40b
24 changed files with 203 additions and 128 deletions
@@ -18,41 +18,74 @@ class FakeBleData @Inject constructor(
fun getFakeData(): Collection<BleScanResult> {
val fakeDevices = mutableListOf<BleScanResult>()
// AirPods Pro
BleScanResult(
address = "78:73:AF:B4:85:5E",
rssi = Random.nextInt(100) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
manufacturerSpecificData = mapOf(76 to "07 19 01 0E 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
).run { fakeDevices.add(this) }
// AirPods Gen1
BleScanResult(
address = "4E:9E:D1:49:D2:6D",
address = "78:73:AF:B4:85:22",
rssi = Random.nextInt(100) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
).run {
if (Random.nextBoolean()) {
fakeDevices.add(this)
}
}
// AirPods Gen2
BleScanResult(
address = "78:73:FF:B4:85:5E",
rssi = Random.nextInt(100) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
manufacturerSpecificData = mapOf(76 to "07 19 01 0F 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
).run {
if (Random.nextBoolean()) {
fakeDevices.add(this)
}
}
// AirPods Gen3
BleScanResult(
address = "4E:9E:D1:49:D2:6D",
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 200,
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".hexToByteArray())
).run { fakeDevices.add(this) }
manufacturerSpecificData = mapOf(76 to "07 19 01 13 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".hexToByteArray())
).run {
if (Random.nextBoolean()) {
fakeDevices.add(this)
}
}
// AirPods Max
BleScanResult(
address = "7E:E5:C7:65:D2:B5",
rssi = Random.nextInt(100) * -1,
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".hexToByteArray())
).run { fakeDevices.add(this) }
).run {
if (Random.nextBoolean()) {
fakeDevices.add(this)
}
}
// BeatsFlex
BleScanResult(
address = "5E:9E:D1:49:D2:6D",
rssi = Random.nextInt(100) * -1,
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
).run { fakeDevices.add(this) }
).run {
if (Random.nextBoolean()) {
fakeDevices.add(this)
}
}
// Unknown Device
BleScanResult(
address = "6E:9E:D1:49:D2:6D",
rssi = Random.nextInt(100) * -1,
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 500,
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
).run { fakeDevices.add(this) }
).run {
if (Random.nextBoolean()) {
fakeDevices.add(this)
}
}
return fakeDevices
}
@@ -70,7 +70,9 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
}
}
vm.listItems.observe2(ui) { adapter.update(it) }
vm.listItems.observe2(ui) {
adapter.update(it)
}
vm.workerAutolaunch.observe2 {
// While UI is active, subscribe to the autolaunch routine
@@ -4,7 +4,6 @@ import android.app.Activity
import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.darken.capod.common.WebpageTool
import eu.darken.capod.common.bluetooth.BluetoothManager2
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.autoreport.DebugSettings
@@ -46,7 +45,6 @@ class OverviewFragmentVM @Inject constructor(
debugSettings: DebugSettings,
private val upgradeRepo: UpgradeRepo,
private val bluetoothManager: BluetoothManager2,
private val webpageTool: WebpageTool,
) : ViewModel3(dispatcherProvider = dispatcherProvider) {
val upgradeState = upgradeRepo.upgradeInfo.asLiveData2()
@@ -55,7 +53,7 @@ class OverviewFragmentVM @Inject constructor(
private val updateTicker = channelFlow<Unit> {
while (isActive) {
trySend(Unit)
delay(1000)
delay(3000)
}
}
@@ -9,7 +9,6 @@ import eu.darken.capod.databinding.OverviewPodsAppleSingleBasicItemBinding
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
import eu.darken.capod.pods.core.getBatteryDrawable
import eu.darken.capod.pods.core.getBatteryLevelHeadset
import eu.darken.capod.pods.core.getSignalQuality
import eu.darken.capod.pods.core.lastSeenFormatted
import java.time.Instant
@@ -34,10 +33,7 @@ class BasicSingleApplePodsCardVH(parent: ViewGroup) :
lastSeen.text = device.lastSeenFormatted(item.now)
reception.text = device.getSignalQuality(context)
if (item.isMainPod) {
reception.append("\n(${getString(R.string.pods_yours)})")
}
reception.text = item.getReceptionText()
batteryLabel.text = device.getBatteryLevelHeadset(context)
batteryIcon.setImageResource(getBatteryDrawable(device.batteryHeadsetPercent))
@@ -40,11 +40,9 @@ class DualApplePodsCardVH(parent: ViewGroup) :
lastSeen.text = context.getString(R.string.last_seen_x, device.lastSeenFormatted(item.now))
firstSeen.text = context.getString(R.string.first_seen_x, device.firstSeenFormatted(item.now))
firstSeen.isGone = Duration.between(device.firstSeenAt, device.lastSeenAt).toMinutes() < 1
firstSeen.isGone = Duration.between(device.seenFirstAt, device.seenLastAt).toMinutes() < 1
reception.text = device.getSignalQuality(context).let {
if (item.isMainPod) "$it\n(${getString(R.string.pods_yours)})" else it
}
reception.text = item.getReceptionText()
// Left Pod
device.apply {
@@ -3,11 +3,13 @@ package eu.darken.capod.main.ui.overview.cards.pods
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.viewbinding.ViewBinding
import eu.darken.capod.R
import eu.darken.capod.common.lists.BindableVH
import eu.darken.capod.common.lists.differ.DifferItem
import eu.darken.capod.common.lists.modular.ModularAdapter
import eu.darken.capod.main.ui.overview.OverviewAdapter
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.getSignalQuality
import java.time.Instant
abstract class PodDeviceVH<D : PodDeviceVH.Item, B : ViewBinding>(
@@ -15,6 +17,10 @@ abstract class PodDeviceVH<D : PodDeviceVH.Item, B : ViewBinding>(
parent: ViewGroup
) : ModularAdapter.VH(layoutId, parent), BindableVH<D, B> {
fun Item.getReceptionText(): String = device.getSignalQuality(context)
.let { if (showDebug) "$it ${device.seenCounter}" else it }
.let { if (isMainPod) "$it\n(${getString(R.string.pods_yours)})" else it }
interface Item : OverviewAdapter.Item {
val now: Instant
@@ -10,7 +10,6 @@ import eu.darken.capod.databinding.OverviewPodsAppleSingleItemBinding
import eu.darken.capod.pods.core.apple.SingleApplePods
import eu.darken.capod.pods.core.getBatteryDrawable
import eu.darken.capod.pods.core.getBatteryLevelHeadset
import eu.darken.capod.pods.core.getSignalQuality
import eu.darken.capod.pods.core.lastSeenFormatted
import java.time.Instant
@@ -35,10 +34,7 @@ class SingleApplePodsCardVH(parent: ViewGroup) :
lastSeen.text = device.lastSeenFormatted(item.now)
reception.text = device.getSignalQuality(context)
if (item.isMainPod) {
reception.append("\n(${getString(R.string.pods_yours)})")
}
reception.text = item.getReceptionText()
batteryLabel.text = device.getBatteryLevelHeadset(context)
batteryIcon.setImageResource(getBatteryDrawable(device.batteryHeadsetPercent))
@@ -6,7 +6,6 @@ import eu.darken.capod.R
import eu.darken.capod.common.lists.binding
import eu.darken.capod.databinding.OverviewPodsUnknownItemBinding
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.getSignalQuality
import eu.darken.capod.pods.core.lastSeenFormatted
import java.time.Instant
@@ -30,10 +29,7 @@ class UnknownPodDeviceCardVH(parent: ViewGroup) :
lastSeen.text = device.lastSeenFormatted(item.now)
reception.text = device.getSignalQuality(context)
if (item.isMainPod) {
reception.append("\n(${getString(R.string.pods_yours)})")
}
reception.text = item.getReceptionText()
rawdata.text = device.rawDataHex
}
@@ -75,7 +75,7 @@ class PodMonitor @Inject constructor(
val now = Instant.now()
deviceCache.toList().forEach { (key, value) ->
if (Duration.between(value.lastSeenAt, now) > Duration.ofSeconds(20)) {
if (Duration.between(value.seenLastAt, now) > Duration.ofSeconds(20)) {
log(TAG, VERBOSE) { "Removing stale device from cache: $value" }
deviceCache.remove(key)
}
@@ -89,28 +89,9 @@ class PodMonitor @Inject constructor(
}
}
val now = Instant.now()
val minimumSignalQuality = generalSettings.minimumSignalQuality.value
val mainDeviceModel = generalSettings.mainDeviceModel.value
val aboveTresholdSorted = pods.values
.filter { it.signalQuality > minimumSignalQuality }
.sortedWith(
compareByDescending<PodDevice> { it.model == mainDeviceModel && it.model != PodDevice.Model.UNKNOWN }
.thenBy {
val age = Duration.between(it.lastSeenAt, now).seconds
if (age < 3) 0L else age
}
.thenByDescending { it.signalQuality }
)
val belowThresholdSorted = pods.values
.filter { it.signalQuality <= minimumSignalQuality }
.sortedWith(
compareBy<PodDevice> { Duration.between(it.lastSeenAt, now) }.thenByDescending { it.signalQuality }
)
val presorted = (aboveTresholdSorted + belowThresholdSorted)
val presorted = pods.values.sortPodsToInterest()
val main = presorted.determineMainDevice()
presorted.sortedByDescending { it == main }
}
.onStart { emit(emptyList()) }
@@ -126,16 +107,40 @@ class PodMonitor @Inject constructor(
.map { it.determineMainDevice() }
.replayingShare(appScope)
private fun List<PodDevice>.determineMainDevice(): PodDevice? = this.firstOrNull()?.let {
val mainDeviceModel = generalSettings.mainDeviceModel.value
when {
it.model == PodDevice.Model.UNKNOWN -> null
mainDeviceModel != PodDevice.Model.UNKNOWN && it.model != mainDeviceModel -> null
it.signalQuality <= generalSettings.minimumSignalQuality.value -> null
else -> it
}
private fun Collection<PodDevice>.sortPodsToInterest(): List<PodDevice> = this.let { devices ->
val now = Instant.now()
return@let devices.sortedWith(
compareByDescending<PodDevice> { true }
.thenBy {
val age = Duration.between(it.seenLastAt, now).seconds
if (age < 5) 0L else (age / 3L).toLong()
}
.thenByDescending { it.signalQuality }
.thenByDescending { (it.seenCounter / 10) }
)
}
private fun List<PodDevice>.determineMainDevice(): PodDevice? = this
.sortPodsToInterest()
.let { devices ->
val mainDeviceModel = generalSettings.mainDeviceModel.value
val presorted = devices.sortedByDescending {
it.model == mainDeviceModel && it.model != PodDevice.Model.UNKNOWN
}
return@let presorted.firstOrNull()?.let { candidate ->
when {
candidate.model == PodDevice.Model.UNKNOWN -> null
mainDeviceModel != PodDevice.Model.UNKNOWN && candidate.model != mainDeviceModel -> null
candidate.signalQuality <= generalSettings.minimumSignalQuality.value -> null
else -> candidate
}
}
}
companion object {
private val TAG = logTag("Monitor", "PodMonitor")
}
@@ -20,9 +20,11 @@ interface PodDevice {
val address: String
get() = scanResult.address
val lastSeenAt: Instant
val seenLastAt: Instant
val firstSeenAt: Instant
val seenFirstAt: Instant
val seenCounter: Int
val scanResult: BleScanResult
@@ -45,7 +45,7 @@ fun getBatteryDrawable(percent: Float?): Int = when {
private val lastSeenFormatter = RelativeDateTimeFormatter.getInstance()
fun PodDevice.lastSeenFormatted(now: Instant): String {
val duration = Duration.between(lastSeenAt, now)
val duration = Duration.between(seenLastAt, now)
return lastSeenFormatter.format(
duration.seconds.toDouble(),
RelativeDateTimeFormatter.Direction.LAST,
@@ -54,7 +54,7 @@ fun PodDevice.lastSeenFormatted(now: Instant): String {
}
fun PodDevice.firstSeenFormatted(now: Instant): String {
val duration = Duration.between(firstSeenAt, now)
val duration = Duration.between(seenFirstAt, now)
return lastSeenFormatter.format(
duration.toMinutes().toDouble(),
RelativeDateTimeFormatter.Direction.LAST,
@@ -1,6 +1,5 @@
package eu.darken.capod.pods.core.apple
import eu.darken.capod.common.SystemClockWrap
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.log
@@ -34,7 +33,8 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
data class KnownDevice(
val id: PodDevice.Id,
val firstSeenAt: Instant,
val seenFirstAt: Instant,
val seenCounter: Int,
val history: List<ApplePods>
) {
val lastMessage: ProximityPairing.Message
@@ -47,14 +47,18 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
get() = 0.75f + (history.size / (MAX_HISTORY * 4f))
fun averageRssi(latest: Int): Int =
history.map { it.rssi }.plus(latest).takeLast(10).average().toInt()
val timestampNanos: Duration
get() = Duration.ofNanos(history.last().scanResult.generatedAtNanos)
history.map { it.rssi }.plus(latest).takeLast(10).median()
fun isOlderThan(age: Duration): Boolean {
val now = Duration.ofNanos(SystemClockWrap.elapsedRealtimeNanos)
return now - timestampNanos > age
val now = Instant.now()
return Duration.between(history.last().seenLastAt, now) > age
}
private fun List<Int>.median(): Int = this.sorted().let {
if (it.size % 2 == 0)
(it[it.size / 2] + it[(it.size - 1) / 2]) / 2
else
it[it.size / 2]
}
override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})"
@@ -107,13 +111,17 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
knownDevices[device.identifier] = when {
existing != null -> {
existing.copy(history = existing.history.plus(device))
existing.copy(
seenCounter = existing.seenCounter + 1,
history = existing.history.plus(device)
)
}
else -> {
log(tag) { "searchHistory1: Creating new history for $device" }
KnownDevice(
id = device.identifier,
firstSeenAt = device.firstSeenAt,
seenFirstAt = device.seenFirstAt,
seenCounter = 1,
history = listOf(device)
)
}
@@ -11,8 +11,9 @@ import javax.inject.Inject
data class UnknownAppleDevice(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = 0f,
@@ -33,15 +34,18 @@ data class UnknownAppleDevice(
scanResult: BleScanResult,
proximityMessage: ProximityPairing.Message,
): ApplePods {
val basic = UnknownAppleDevice(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = UnknownAppleDevice(scanResult = scanResult, proximityMessage = proximityMessage)
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,
seenCounter = result.seenCounter,
confidence = result.confidence,
rssiAverage = result.averageRssi(basic.rssi),
)
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class AirPodsGen1 constructor(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -39,16 +40,18 @@ data class AirPodsGen1 constructor(
proximityMessage.getModelInfo().full == DEVICE_CODE
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = AirPodsGen1(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = AirPodsGen1(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
cachedBatteryPercentage = result.getLatestCaseBattery(),
rssiAverage = result.averageRssi(basic.rssi),
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class AirPodsGen2 constructor(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -39,16 +40,18 @@ data class AirPodsGen2 constructor(
proximityMessage.getModelInfo().full == DEVICE_CODE
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = AirPodsGen2(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = AirPodsGen2(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
cachedBatteryPercentage = result.getLatestCaseBattery(),
rssiAverage = result.averageRssi(basic.rssi),
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class AirPodsGen3 constructor(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -38,16 +39,18 @@ data class AirPodsGen3 constructor(
proximityMessage.getModelInfo().full == DEVICE_CODE
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = AirPodsGen3(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = AirPodsGen3(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
cachedBatteryPercentage = result.getLatestCaseBattery(),
rssiAverage = result.averageRssi(basic.rssi),
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class AirPodsMax(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -31,16 +32,18 @@ data class AirPodsMax(
proximityMessage.getModelInfo().dirty == DEVICE_CODE_DIRTY
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = AirPodsMax(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = AirPodsMax(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
rssiAverage = result.averageRssi(basic.rssi),
)
@@ -13,8 +13,9 @@ import javax.inject.Inject
data class AirPodsPro(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -50,7 +51,8 @@ data class AirPodsPro(
return basic.copy(
identifier = result.id,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
cachedBatteryPercentage = result.getLatestCaseBattery(),
rssiAverage = result.averageRssi(basic.rssi),
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class BeatsFlex(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -31,16 +32,18 @@ data class BeatsFlex(
proximityMessage.getModelInfo().full == DEVICE_CODE
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = BeatsFlex(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = BeatsFlex(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
rssiAverage = result.averageRssi(basic.rssi),
)
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class BeatsSolo3(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -30,16 +31,18 @@ data class BeatsSolo3(
proximityMessage.getModelInfo().full == DEVICE_CODE
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = BeatsSolo3(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = BeatsSolo3(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
rssiAverage = result.averageRssi(basic.rssi),
)
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class BeatsStudio3(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -28,16 +29,18 @@ data class BeatsStudio3(
proximityMessage.getModelInfo().dirty == DEVICE_CODE_DIRTY
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = BeatsStudio3(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = BeatsStudio3(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
rssiAverage = result.averageRssi(basic.rssi),
)
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class BeatsX(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -31,16 +32,18 @@ data class BeatsX(
proximityMessage.getModelInfo().full == DEVICE_CODE
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = BeatsX(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = BeatsX(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
rssiAverage = result.averageRssi(basic.rssi),
)
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class PowerBeats3(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -30,16 +31,18 @@ data class PowerBeats3(
proximityMessage.getModelInfo().full == DEVICE_CODE
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
val basic = PowerBeats3(scanResult = scanResult, proximityMessage = proximityMessage)
var basic = PowerBeats3(scanResult = scanResult, proximityMessage = proximityMessage)
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,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
rssiAverage = result.averageRssi(basic.rssi),
)
@@ -12,8 +12,9 @@ import javax.inject.Inject
data class PowerBeatsPro(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val lastSeenAt: Instant = Instant.now(),
override val firstSeenAt: Instant = Instant.now(),
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 confidence: Float = PodDevice.BASE_CONFIDENCE,
@@ -49,7 +50,8 @@ data class PowerBeatsPro(
return basic.copy(
identifier = result.id,
firstSeenAt = result.firstSeenAt,
seenFirstAt = result.seenFirstAt,
seenCounter = result.seenCounter,
confidence = result.confidence,
cachedBatteryPercentage = result.getLatestCaseBattery(),
rssiAverage = result.averageRssi(basic.rssi),