Skip to content

Commit

Permalink
Collect more data from Bluetooth beacons (beacon type, identifiers)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjaakko committed Jul 24, 2024
1 parent 059bb9c commit 9db3d88
Show file tree
Hide file tree
Showing 10 changed files with 621 additions and 64 deletions.
491 changes: 491 additions & 0 deletions app/schemas/xyz.malkki.neostumbler.db.ReportDatabase/6.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions app/src/main/java/xyz/malkki/neostumbler/db/ReportDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import xyz.malkki.neostumbler.db.migrations.RenameTablesToEntities

@Database(
exportSchema = true,
version = 5,
version = 6,
entities = [Report::class, Position::class, WifiAccessPointEntity::class, CellTowerEntity::class, BluetoothBeaconEntity::class,],
autoMigrations = [
AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3, spec = RenameTablesToEntities::class),
AutoMigration(from = 3, to = 4),
AutoMigration(from = 4, to = 5)
AutoMigration(from = 4, to = 5),
AutoMigration(from = 5, to = 6)
]
)
@TypeConverters(InstantConverters::class, LocalDateConverters::class)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package xyz.malkki.neostumbler.db.entities

import android.os.SystemClock
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import org.altbeacon.beacon.Beacon
import xyz.malkki.neostumbler.domain.BluetoothBeacon
import java.time.Instant
import java.time.temporal.ChronoUnit

@Entity(
foreignKeys = [ForeignKey(entity = Report::class, parentColumns = ["id"], childColumns = ["reportId"], onDelete = ForeignKey.CASCADE)]
Expand All @@ -15,18 +17,28 @@ data class BluetoothBeaconEntity(
val macAddress: String,
val age: Long,
val name: String?,
val beaconType: Int?,
val id1: String?,
val id2: String?,
val id3: String?,
val signalStrength: Int?,
@ColumnInfo(index = true) val reportId: Long?
) {
companion object {
fun fromBeacon(reportId: Long, currentTime: Instant, beacon: Beacon): BluetoothBeaconEntity {
fun fromBluetoothBeacon(reportId: Long, reportTimestamp: Instant, beacon: BluetoothBeacon): BluetoothBeaconEntity {
val age = maxOf(0, Instant.now().minusMillis(SystemClock.elapsedRealtime() - beacon.timestamp).until(reportTimestamp, ChronoUnit.MILLIS))

return BluetoothBeaconEntity(
null,
beacon.bluetoothAddress,
currentTime.toEpochMilli() - beacon.lastCycleDetectionTimestamp,
null,
beacon.rssi,
reportId
macAddress = beacon.macAddress,
age = age,
name = null,
signalStrength = beacon.signalStrength,
beaconType = beacon.beaconType,
id1 = beacon.id1,
id2 = beacon.id2,
id3 = beacon.id3,
reportId = reportId
)
}
}
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/java/xyz/malkki/neostumbler/domain/BluetoothBeacon.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package xyz.malkki.neostumbler.domain

import android.os.SystemClock
import org.altbeacon.beacon.Beacon

data class BluetoothBeacon(
val macAddress: String,
val beaconType: Int?,
val id1: String?,
val id2: String?,
val id3: String?,
val signalStrength: Int,
val timestamp: Long
) {
companion object {
fun fromBeacon(beacon: Beacon): BluetoothBeacon {
val timestamp = SystemClock.elapsedRealtime() - (System.currentTimeMillis() - beacon.lastCycleDetectionTimestamp)

return BluetoothBeacon(
macAddress = beacon.bluetoothAddress,
beaconType = beacon.beaconTypeCode,
id1 = beacon.id1?.toString(),
id2 = beacon.id2?.toString(),
id3 = beacon.id3?.toString(),
signalStrength = beacon.rssi,
timestamp = timestamp
)
}
}
}
16 changes: 12 additions & 4 deletions app/src/main/java/xyz/malkki/neostumbler/geosubmit/Report.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,24 @@ data class Report(
data class BluetoothBeacon(
val macAddress: String,
val name: String?,
val beaconType: Int?,
val id1: String?,
val id2: String?,
val id3: String?,
val age: Long,
val signalStrength: Int?
) {
companion object {
fun fromDbEntity(beacon: xyz.malkki.neostumbler.db.entities.BluetoothBeaconEntity): BluetoothBeacon {
return BluetoothBeacon(
beacon.macAddress,
beacon.name,
beacon.age,
beacon.signalStrength
macAddress = beacon.macAddress,
name = beacon.name,
beaconType = beacon.beaconType,
id1 = beacon.id1,
id2 = beacon.id2,
id3 = beacon.id3,
age = beacon.age,
signalStrength = beacon.signalStrength
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import android.content.Context
import android.location.Location
import android.net.wifi.ScanResult
import androidx.room.withTransaction
import org.altbeacon.beacon.Beacon
import timber.log.Timber
import xyz.malkki.neostumbler.StumblerApplication
import xyz.malkki.neostumbler.db.entities.BluetoothBeaconEntity
import xyz.malkki.neostumbler.db.entities.CellTowerEntity
import xyz.malkki.neostumbler.db.entities.Position
import xyz.malkki.neostumbler.db.entities.Report
import xyz.malkki.neostumbler.db.entities.WifiAccessPointEntity
import xyz.malkki.neostumbler.domain.BluetoothBeacon
import xyz.malkki.neostumbler.domain.CellTower
import java.time.Instant

Expand All @@ -23,7 +23,7 @@ class ScanReportCreator(context: Context) {
location: Location,
wifiScanResults: List<ScanResult>,
cellTowers: List<CellTower>,
beacons: List<Beacon>,
beacons: List<BluetoothBeacon>,
reportTimestamp: Instant = Instant.now()
) = reportDb.withTransaction {
val report = Report(null, reportTimestamp, false, null)
Expand All @@ -38,7 +38,7 @@ class ScanReportCreator(context: Context) {
val cellTowerEntities = cellTowers.map { CellTowerEntity.fromCellTower(it, reportTimestamp, reportId) }
reportDb.cellTowerDao().insertAll(*cellTowerEntities.toTypedArray())

val bluetoothBeaconEntities = beacons.map { BluetoothBeaconEntity.fromBeacon(reportId, reportTimestamp, it) }
val bluetoothBeaconEntities = beacons.map { BluetoothBeaconEntity.fromBluetoothBeacon(reportId, reportTimestamp, it) }
reportDb.bluetoothBeaconDao().insertAll(*bluetoothBeaconEntities.toTypedArray())

Timber.i("Inserted report with ${wifiAccessPointEntities.size} Wi-Fi access points, ${cellTowerEntities.size} cell towers and ${bluetoothBeaconEntities.size} Bluetooth beacons to DB")
Expand Down
29 changes: 12 additions & 17 deletions app/src/main/java/xyz/malkki/neostumbler/scanner/ScannerService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,24 @@ import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.altbeacon.beacon.Beacon
import timber.log.Timber
import xyz.malkki.neostumbler.MainActivity
import xyz.malkki.neostumbler.R
import xyz.malkki.neostumbler.StumblerApplication
import xyz.malkki.neostumbler.common.LocationWithSource
import xyz.malkki.neostumbler.domain.CellTower
import xyz.malkki.neostumbler.constants.PreferenceKeys
import xyz.malkki.neostumbler.domain.BluetoothBeacon
import xyz.malkki.neostumbler.domain.CellTower
import xyz.malkki.neostumbler.extensions.buffer
import xyz.malkki.neostumbler.extensions.checkMissingPermissions
import xyz.malkki.neostumbler.extensions.combineAny
import xyz.malkki.neostumbler.extensions.filterNotNullPairs
import xyz.malkki.neostumbler.extensions.isWifiScanThrottled
import xyz.malkki.neostumbler.extensions.timestampMillis
import xyz.malkki.neostumbler.location.LocationSourceProvider
import xyz.malkki.neostumbler.scanner.source.BeaconLibraryBluetoothBeaconSource
import xyz.malkki.neostumbler.scanner.source.MultiSubscriptionCellInfoSource
import xyz.malkki.neostumbler.scanner.source.TelephonyManagerCellInfoSource
import xyz.malkki.neostumbler.utils.getBeaconFlow
import xyz.malkki.neostumbler.utils.getWifiScanFlow
import java.util.Locale
import kotlin.math.abs
Expand Down Expand Up @@ -226,28 +226,23 @@ class ScannerService : Service() {
}

val beaconsFlow = if (hasBluetoothScanPermission()) {
getBeaconFlow(this@ScannerService)
BeaconLibraryBluetoothBeaconSource(this@ScannerService).getBluetoothBeaconFlow()
.buffer(SCAN_BUFFER_PERIOD)
.map { beacons ->
val now = System.currentTimeMillis()
val now = SystemClock.elapsedRealtime()

beacons.flatten()
//Beacon library seems to sometimes return very old results -> filter them
.filter { (it.lastCycleDetectionTimestamp - now).milliseconds < BEACON_MAX_AGE }
.groupBy { it.bluetoothAddress }
.groupBy { it.macAddress }
.mapValues { beacon ->
beacon.value.maxBy {
it.lastCycleDetectionTimestamp
}
beacon.value.maxBy { it.timestamp }
}
.values.toList()
.values
//Beacon library seems to sometimes return very old results -> filter them
.filter { (now - it.timestamp).milliseconds < BEACON_MAX_AGE }
}
.map { beacons ->
if (beacons.isNotEmpty()) {
val avgTimestamp = beacons.map { it.lastCycleDetectionTimestamp }.average().roundToLong()
val avgTimestampElapsedRealtime = SystemClock.elapsedRealtime() - (System.currentTimeMillis() - avgTimestamp)

Timestamped(avgTimestampElapsedRealtime, beacons)
Timestamped(beacons.map { it.timestamp }.average().roundToLong(), beacons)
} else {
null
}
Expand All @@ -268,7 +263,7 @@ class ScannerService : Service() {
channelFlow {
val mutex = Mutex()

var reportData: Triple<Timestamped<List<CellTower>>?, Timestamped<List<ScanResult>>?, Timestamped<List<Beacon>>?>? = null
var reportData: Triple<Timestamped<List<CellTower>>?, Timestamped<List<ScanResult>>?, Timestamped<List<BluetoothBeacon>>?>? = null

launch {
reportDataFlow.collect {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package xyz.malkki.neostumbler.scanner.source

import android.content.Context
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.map
import org.altbeacon.beacon.Beacon
import org.altbeacon.beacon.BeaconManager
import org.altbeacon.beacon.Region
import xyz.malkki.neostumbler.domain.BluetoothBeacon
import kotlin.random.Random

class BeaconLibraryBluetoothBeaconSource(context: Context) : BluetoothBeaconSource {
private val appContext = context.applicationContext

private fun getBeaconFlow(context: Context): Flow<List<Beacon>> = callbackFlow {
val beaconManager = BeaconManager.getInstanceForApplication(context)

val rangeNotifier: (Collection<Beacon>, Region) -> Unit = { beacons: Collection<Beacon>, _: Region ->
trySendBlocking(beacons.toList())
}

beaconManager.addRangeNotifier(rangeNotifier)

val region = Region("all_beacons_${Random.Default.nextInt(0, Int.MAX_VALUE)}", null, null, null)

beaconManager.startRangingBeacons(region)

awaitClose {
beaconManager.stopRangingBeacons(region)

beaconManager.removeRangeNotifier(rangeNotifier)
}
}

override fun getBluetoothBeaconFlow(): Flow<List<BluetoothBeacon>> = getBeaconFlow(appContext).map { beacons ->
beacons.map { beacon ->
BluetoothBeacon.fromBeacon(beacon)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package xyz.malkki.neostumbler.scanner.source

import kotlinx.coroutines.flow.Flow
import xyz.malkki.neostumbler.domain.BluetoothBeacon

interface BluetoothBeaconSource {
fun getBluetoothBeaconFlow(): Flow<List<BluetoothBeacon>>
}
31 changes: 0 additions & 31 deletions app/src/main/java/xyz/malkki/neostumbler/utils/beaconFlow.kt

This file was deleted.

0 comments on commit 9db3d88

Please sign in to comment.