Інтеграція Eddystone для визначення proximity в Android-приложенні
Eddystone — відкритий формат BLE-маяків від Google, на відміну від проприетарного iBeacon. Три основних фрейма: Eddystone-UID (16 байт ідентифікатора, аналог UUID/major/minor), Eddystone-URL (фізичний веб — маяк трансліює URL прямо в браузер без приложення), Eddystone-TLM (телеметрія: напруга батареї, температура, лічильник пакетів). Більшість проектів використовує UID для proximity та TLM для моніторингу стану парку маяків.
Що ломається без правильної налаштування
Nearby API проти прямого BLE-сканування
Google просувала Nearby Messages API як високорівневий спосіб роботи з Eddystone. З 2023 року Nearby Messages API deprecated. Проекти, які на неї завязані, отримують попередження при запуску та скоро — повний відмова сервісу. Правильний шлях: пряме сканування через BluetoothLeScanner з фільтром по Service UUID 0xFEAA (Eddystone) та парсинг Advertisement Data вручну.
ScanFilter та енергоспоживання
Сканування без фільтра в SCAN_MODE_LOW_LATENCY — це повна нагрузка на BLE-чип, батарея садиться за кілька годин. Правильно:
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 потрібен, якщо маяки з низьким txPower або через перешкоди — інакше пакети відфільтруються ще на рівні залізо. На сучасних пристроях з Android 12+ потрібна дозвіл BLUETOOTH_SCAN (не ACCESS_FINE_LOCATION для скану без визначення місцезнаходження — але це працює тільки при neverForLocation=true у manifest). Плутанина з дозволами забила не один реліз.
Парсинг UID-фрейма
Advertisement payload Eddystone-UID у Service Data:
Байт 0: 0x00 — тип фрейма (UID)
Байт 1: TX Power (signed int8, для калібрування дистанції)
Байти 2-11: Namespace (10 байт)
Байти 12-17: Instance (6 байт)
Зміщення: Service Data починається після AD Type 0x16 та UUID 0xAA 0xFE. Якщо парсити неправильно — Namespace та Instance перепутаються або зміщуться на байт. Це не падання приложення — просто неправильний ідентифікатор маяка, який ніколи не спіпаде з серверною базою.
Архітектура сканера
Сканування BLE неможна тримати в Activity — вона знищується. Використовуємо ForegroundService з FOREGROUND_SERVICE_TYPE_LOCATION (Android 14+) або WorkManager з ExpeditedWork для коротких сесій. Результати сканування відправляємо через BroadcastReceiver або Channel<BeaconEvent> в 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)
}
}
Розрахунок дистанції
Дистанція по RSSI — приблизна. Використовуємо формулу path-loss model:
distance = 10 ^ ((txPower - rssi) / (10 * n))
де n — коефіцієнт середовища (2.0 для відкритого простору, 3.0–4.0 для офісу з перегородками, до 4.5 для складу з металевими стелажами). Коефіцієнт визначається експериментально на конкретному об'єкті — взяти «стандартний» 2.0 та скаржитися на погану точність неможливо.
Моніторинг TLM-фреймів
Якщо парк маяків великий (ритейл, склад), TLM-фрейми — єдиний спосіб зрозуміти, що батарея маяка на 3% або чип перегрівся. Парсимо аналогічно UID, тип фрейма 0x20. Voltage — у mV (uint16, big-endian), Temperature — 8.8 fixed-point. Результати агрегуємо на сервері через MQTT або WebSocket з сканування пристрою.
Терміни
Базова інтеграція Eddystone-UID з розрахунком proximity — 4–7 днів. З фоновим скануванням, TLM-мониторингом та серверною аналітикою — 2–4 тижні. Оцінка після аналізу вимог до точності та розміру парку маяків.







