Android SBeacon Framework Beacon Scanning - scopetech/Mobile-Public GitHub Wiki

Beacon Scanning

SBeacon SDK v1.6.13

This guide covers scanning for SBeacon devices, retrieving scan results, and best practices for battery efficiency.


Start/Stop Scanning

Bind scanning to your UI lifecycle to conserve battery:

class BeaconsFragment : Fragment() {

    override fun onResume() {
        super.onResume()
        startBeaconsScan()
    }

    override fun onPause() {
        super.onPause()
        stopBeaconsScan()
    }

    private fun startBeaconsScan() {
        BLEManager.retrieveInstance(requireContext()).startBeaconsScan()

        // Start updating UI with scan results
        startPeriodicUIUpdate()
    }

    private fun stopBeaconsScan() {
        BLEManager.retrieveInstance(requireContext()).stopBeaconsScan()

        // Stop UI updates
        stopPeriodicUIUpdate()
    }
}

Scanning Methods

Method Description
startBeaconsScan() Start continuous scanning for SBeacon devices
stopBeaconsScan() Stop the current scan
startQuickScan() Perform a brief scan (useful for quick discovery)

Retrieve Beacon List

Poll for discovered beacons periodically:

private var updateJob: Job? = null

private fun startPeriodicUIUpdate() {
    updateJob = lifecycleScope.launch {
        while (isActive) {
            updateBeaconList()
            delay(1000) // Update every second
        }
    }
}

private fun stopPeriodicUIUpdate() {
    updateJob?.cancel()
    updateJob = null
}

private fun updateBeaconList() {
    lifecycleScope.launch(Dispatchers.IO) {
        val now = System.currentTimeMillis()
        val beacons = BLEManager.retrieveInstance(requireContext()).allBeacons()
            .filter { beacon ->
                // Show paired beacons always
                beacon.paired ||
                // Show unpaired beacons seen within last 2 minutes
                now - beacon.lastTimeUpdated < 120_000
            }
            .sortedBy { !it.paired }  // Paired beacons first

        withContext(Dispatchers.Main) {
            // Update your RecyclerView or UI
            adapter.submitList(beacons)
        }
    }
}

Filtering Strategy

Beacon State Visibility
Paired Always shown regardless of last seen time
Unpaired Shown if discovered within last 2 minutes

The 2-minute window prevents stale unpaired beacons from cluttering the list while keeping recently discovered devices visible.


Beacon Properties

When displaying beacons, useful properties include:

val beacon: ScopeBeacon = ...

// Identification
beacon.macAddress      // Unique identifier
beacon.name            // Display name
beacon.minorId         // Beacon minor ID
beacon.majorId         // Beacon major ID

// State
beacon.paired          // Whether beacon is paired
beacon.isCalibrated    // Whether accelerometer is calibrated
beacon.tripStatus      // Current trip status

// Timing
beacon.lastTimeUpdated // Last communication time
beacon.lastTimeScanned // Last scan detection time

// Battery
beacon.batteryVoltage  // Current battery voltage
beacon.lastBatteryUpdate // When battery was last read

Check if Beacon is in Range

// Built-in helper method
val isInRange = beacon.isInRange()

// Uses 60-second threshold internally
// System.currentTimeMillis() - lastTimeUpdated < 60_000

Best Practices

Battery Efficiency

  • Start scanning in onResume(), stop in onPause()
  • Don't scan continuously when the UI isn't visible
  • Use polling intervals of 1-2 seconds for UI updates

Threading

// Always access beacon list from IO dispatcher
lifecycleScope.launch(Dispatchers.IO) {
    val beacons = BLEManager.retrieveInstance(context).allBeacons()

    // Switch to Main for UI updates
    withContext(Dispatchers.Main) {
        updateUI(beacons)
    }
}

Filter Stale Data

// Example: Only show beacons seen in last 2 minutes
val recentThreshold = System.currentTimeMillis() - (2 * 60 * 1000)
val recentBeacons = allBeacons.filter {
    it.paired || it.lastTimeUpdated > recentThreshold
}

Next Steps