Реалізація передачи даних з фітнес-браслета по BLE у мобільний додаток
Більшість фітнес-браслетів на ринку — закриті пристрої з проприетарним GATT-профілем. Xiaomi Mi Band, Huawei Band, Fitbit — у кожного своє «колдовство» поверх стандартного Bluetooth LE. Якщо браслет кастомний (розроблено під ваш продукт на Nordic nRF52840 або Dialog DA14531), ситуація простіша: у вас є документація на GATT-сервіси. Далі — робота з обома сценаріями.
Стандартні GATT-профіли: що реально підтримують браслети
Bluetooth SIG визначив профіли для носимих: Heart Rate Profile (HRP), Cycling Speed and Cadence (CSC), Running Speed and Cadence (RSC). Браслет з сертифікацією реалізує ці сервіси передбачувано.
Heart Rate Measurement characteristic (UUID 0x2A37):
// Формат пакета Heart Rate Measurement
fun parseHeartRate(data: ByteArray): HeartRateMeasurement {
val flags = data[0].toInt()
val hrFormat16bit = (flags and 0x01) != 0
val energyExpended = (flags and 0x08) != 0
val rrIntervalPresent = (flags and 0x10) != 0
var offset = 1
val bpm = if (hrFormat16bit) {
val value = ((data[offset + 1].toInt() and 0xFF) shl 8) or (data[offset].toInt() and 0xFF)
offset += 2
value
} else {
data[offset++].toInt() and 0xFF
}
val rrIntervals = mutableListOf<Double>()
if (rrIntervalPresent) {
while (offset + 1 < data.size) {
val raw = ((data[offset + 1].toInt() and 0xFF) shl 8) or (data[offset].toInt() and 0xFF)
rrIntervals.add(raw / 1024.0 * 1000.0) // у мілісекундах
offset += 2
}
}
return HeartRateMeasurement(bpm, rrIntervals)
}
RR-інтервали — час між ударами серця — потрібні для HRV (Heart Rate Variability). Багато додатків ігнорують їх та втрачають цінні дані для аналізу стресу.
Сканування та фільтрація
Не сканудемо «все підряд» — тільки потрібні пристрої. На Android:
fun startScan(onDevice: (BluetoothDevice) -> Unit) {
val filters = listOf(
ScanFilter.Builder()
.setServiceUuid(ParcelUuid(HEART_RATE_SERVICE_UUID))
.build(),
)
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build()
scanner.startScan(filters, settings, object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
onDevice(result.device)
}
})
// Принудово зупиняємо через 10 секунд — нескінчений скан вбиває батарею
Handler(Looper.getMainLooper()).postDelayed({ scanner.stopScan(this) }, 10_000)
}
На iOS фоновий скан вимагає background mode bluetooth-central та фільтр по serviceUUIDs — без фільтра CoreBluetooth не доставляє события у фоні.
Робота з проприетарним браслетом без документації
Якщо браслет комерційний та документації немає — реверс-інжиниринг через nRF Connect та Wireshark (Bluetooth HCI snoop log на Android, PacketLogger на Mac для iOS). Включаємо Settings → Developer Options → Enable Bluetooth HCI snoop log, відтворюємо синхронізацію через офіціальний додаток, аналізуємо лог у Wireshark.
Зазвичай знаходимо: UUID сервісу синхронізації, sequence команд ініціалізації, формат даних (часто без документації — «угадуємо» за значеннями: перші 4 байти — timestamp Unix, наступні 2 — кроки, та ін.).
Надійне переподключення
Браслети відключаються. Телефон йде у фон. Посилання BluetoothGatt застарівають. Стратегія переподключення:
private fun scheduleReconnect(device: BluetoothDevice) {
reconnectJob?.cancel()
reconnectJob = scope.launch {
var attempt = 0
while (isActive) {
delay(minOf(1000L * (1 shl attempt), 30_000L)) // експоненціальний backoff до 30 сек
val result = connect(device)
if (result.isSuccess) break
attempt++
}
}
}
Експоненціальний backoff з потолком 30 секунд — баланс між швидкістю відновлення та навантаженням на BLE-стек.
Реалізація синхронізації даних з фітнес-браслета по BLE (кроки, ЧСС, сон, HRV): 3–5 тижнів. Реверс-інжиниринг проприетарного протоколу додає 1–2 тижні. Вартість розраховується індивідуально.







