Eddystone Integration for Proximity Detection in Android Application
Eddystone is Google's open BLE beacon format, unlike proprietary iBeacon. Three main frames: Eddystone-UID (16 bytes identifier, analogous to UUID/major/minor), Eddystone-URL (physical web — beacon broadcasts URL directly to browser without app), Eddystone-TLM (telemetry: battery voltage, temperature, packet count). Most projects use UID for proximity and TLM for monitoring beacon fleet health.
What Breaks Without Proper Setup
Nearby API vs Direct BLE Scanning
Google promoted Nearby Messages API as high-level Eddystone method. Since 2023, Nearby Messages API deprecated. Projects dependent on it get warnings on launch and soon — full service denial. Correct path: direct scanning via BluetoothLeScanner with Service UUID 0xFEAA (Eddystone) filter and parse Advertisement Data manually.
ScanFilter and Power Consumption
Scanning without filter in SCAN_MODE_LOW_LATENCY — full BLE chip load, battery drains in hours. Correct:
val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("0000feaa-0000-1000-8000-00805f9b34fb"))
.build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.build()
MATCH_MODE_AGGRESSIVE needed if beacons have low txPower or through obstacles — otherwise packets filtered at hardware level. On modern devices with Android 12+, need BLUETOOTH_SCAN permission (not ACCESS_FINE_LOCATION for scan without location — only works with neverForLocation=true in manifest). Permissions confusion broke more than one release.
Parsing UID Frame
Eddystone-UID Service Data payload:
Byte 0: 0x00 — frame type (UID)
Byte 1: TX Power (signed int8, for distance calibration)
Bytes 2-11: Namespace (10 bytes)
Bytes 12-17: Instance (6 bytes)
Offset: Service Data starts after AD Type 0x16 and UUID 0xAA 0xFE. Wrong parsing — Namespace and Instance confused or shifted by byte. Not app crash — just wrong beacon ID, never matches server database.
Scanner Architecture
Cannot keep BLE scanning in Activity — destroyed. Use ForegroundService with FOREGROUND_SERVICE_TYPE_LOCATION (Android 14+) or WorkManager with ExpeditedWork for short sessions. Send scan results via BroadcastReceiver or Channel<BeaconEvent> to SharedViewModel.
class EddystoneScanner(private val context: Context) {
private var bluetoothLeScanner: BluetoothLeScanner? = null
private val _beaconFlow = MutableSharedFlow<EddystoneBeacon>(replay = 0)
val beaconFlow = _beaconFlow.asSharedFlow()
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
parseEddystoneUid(result)?.let { beacon ->
_beaconFlow.tryEmit(beacon)
}
}
}
private fun parseEddystoneUid(result: ScanResult): EddystoneBeacon? {
val serviceData = result.scanRecord
?.getServiceData(ParcelUuid.fromString("0000feaa-0000-1000-8000-00805f9b34fb"))
?: return null
if (serviceData.isEmpty() || serviceData[0] != 0x00.toByte()) return null
val txPower = serviceData[1].toInt()
val namespace = serviceData.sliceArray(2..11).toHexString()
val instance = serviceData.sliceArray(12..17).toHexString()
val rssi = result.rssi
return EddystoneBeacon(namespace, instance, rssi, txPower)
}
}
Distance Calculation
RSSI distance — approximate. Use path-loss model formula:
distance = 10 ^ ((txPower - rssi) / (10 * n))
where n — environment coefficient (2.0 for open space, 3.0–4.0 for office with partitions, up to 4.5 for warehouse with metal shelves). Coefficient determined experimentally on specific object — taking standard 2.0 and complaining about poor accuracy pointless.
TLM Frame Monitoring
For large beacon fleet (retail, warehouse), TLM frames — only way to know battery at 3% or chip overheated. Parse similarly to UID, frame type 0x20. Voltage — in mV (uint16, big-endian), Temperature — 8.8 fixed-point. Aggregate results on server via MQTT or WebSocket from scanning device.
Timeframes
Basic Eddystone-UID integration with proximity calculation — 4–7 days. With background scanning, TLM monitoring and server analytics — 2–4 weeks. Estimate after accuracy requirements and beacon fleet size analysis.







