Реализация передачи данных с фитнес-браслета по 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 сканирование в фоне требует bluetooth-central background mode и фильтр по 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 недели. Стоимость рассчитывается индивидуально.







