BLE-интеграция: стабильная работа с устройствами
Мы часто видим проекты, где BLE-подключение реализовано как «включил сканирование — подключился — радуемся». В реальности это конечный автомат с дюжиной состояний, каждое из которых может завершиться ошибкой: адаптер выключен, устройство вне зоны, сервис не найден, характеристика не поддерживает запись. Наша команда — специалисты с 5+ летним опытом разработки BLE-решений для iOS и Android. Мы гарантируем, что ваше приложение стабильно обрабатывает все краевые случаи.
Почему BLE-интеграция требует профессионального подхода?
BLE работает по модели GATT (Generic Attribute Profile). Периферийное устройство предоставляет Services — логические группы функций. Каждый Service содержит Characteristics — конкретные значения для чтения, записи или подписки (notify/indicate). Ошибка на любом из этих уровней — и приложение не получит данные. Мы видели проекты, где разработчики забывали подписываться на дескриптор CCCD — в результате характеристика не отправляла уведомления.
Конечный автомат подключения
Минимальный набор состояний, который нужно обрабатывать явно:
IDLE → SCANNING → DISCOVERED → CONNECTING → CONNECTED → DISCOVERING_SERVICES
→ SERVICES_READY → SUBSCRIBING → READY
И ошибки на каждом переходе: таймаут сканирования, разрыв соединения в CONNECTING, gattStatus != GATT_SUCCESS при discovery, потеря соединения в READY. Тестируем все сценарии с помощью симуляторов и реальных устройств.
| Состояние | iOS (CoreBluetooth) | Android (BluetoothGatt) |
|---|---|---|
| IDLE | .poweredOn |
BluetoothAdapter.state_on |
| SCANNING | scanForPeripherals |
startScan |
| DISCOVERED | didDiscover |
onScanResult |
| CONNECTING | connect |
connectGatt |
| CONNECTED | didConnect |
onConnectionStateChange (STATE_CONNECTED) |
| DISCOVERING_SERVICES | discoverServices |
discoverServices |
| SERVICES_READY | didDiscoverServices |
onServicesDiscovered |
| SUBSCRIBING | setNotifyValue |
writeDescriptor (CCCD) |
| READY | didUpdateValue |
onCharacteristicChanged |
Что делать, если соединение разорвалось?
Самая частая ошибка — игнорирование didDisconnectPeripheral (iOS) или onConnectionStateChange с не-GATT_SUCCESS (Android). Без автоматического реконнекта пользователь вынужден перезапускать приложение. Мы реализуем логику с экспоненциальным backoff: при разрыве ждём 1 с, 2 с, 4 с, 8 с, затем сбрасываем. На iOS для работы в фоне используем CBCentralManagerOptionRestoreIdentifierKey — system wakes up the app when device reappears.
iOS: CoreBluetooth
import CoreBluetooth
class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var targetPeripheral: CBPeripheral?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue(label: "ble.queue"))
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
guard central.state == .poweredOn else {
// обрабатываем .poweredOff, .unauthorized, .unsupported
return
}
startScanning()
}
func startScanning() {
let serviceUUID = CBUUID(string: "180D")
centralManager.scanForPeripherals(withServices: [serviceUUID], options: [
CBCentralManagerScanOptionAllowDuplicatesKey: false
])
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any], rssi RSSI: NSNumber) {
guard RSSI.intValue > -80 else { return } // фильтр по сигналу
centralManager.stopScan()
targetPeripheral = peripheral
centralManager.connect(peripheral, options: nil)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([CBUUID(string: "180D")])
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard error == nil, let services = peripheral.services else { return }
for service in services {
peripheral.discoverCharacteristics([CBUUID(string: "2A37")], for: service)
}
}
func peripheral(_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for char in characteristics where char.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: char)
}
}
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard let data = characteristic.value else { return }
// парсим data согласно GATT-спецификации характеристики
}
}
Android: BluetoothGatt
val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
// status содержит код ошибки, например 133 (GATT_ERROR) - нужен реконнект
gatt.close()
return
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
val characteristic = gatt
.getService(UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb"))
?.getCharacteristic(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb"))
?: return
gatt.setCharacteristicNotification(characteristic, true)
val descriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") // Client Characteristic Configuration
)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
val data = characteristic.value
// парсим
}
}
device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
Проблемы, которые мы решаем в каждом проекте
| Проблема | Решение |
|---|---|
didDisconnectPeripheral без предупреждения |
Автореконнект с backoff, restoration identifier (iOS) |
Статус 133 GATT_ERROR на Android |
gatt.close(), refresh() через рефлексию, повтор через 2 с |
| Сканирование убивает батарею | allowDuplicates: false — включаем только при измерении RSSI |
| State restoration не работает | Настраиваем CBCentralManagerOptionRestoreIdentifierKey в схеме |
| Android 12+ разрешения | Запрашиваем BLUETOOTH_SCAN и BLUETOOTH_CONNECT динамически |
Что входит в работу по интеграции BLE
- Документация по GATT-схеме устройства (сервисы, характеристики, форматы данных)
- Реализация сканирования, подключения, подписки, реконнекта
- Обработка всех штатных и аварийных состояний
- Оптимизация MTU: запрос 512 байт вместо 23 для ускорения передачи
- Интеграция с тестовым устройством (предоставленным заказчиком или нашим)
- Unit-тесты и сценарии нагрузочного тестирования (до 10 одновременных BLE-периферий)
- Поддержка после сдачи: гарантия 3 месяца на стабильность соединения
Как мы работаем: от анализа до деплоя
- Аналитика — изучаем документацию устройства, согласуем GATT-схему и формат данных.
- Проектирование — создаём конечный автомат, определяем политику реконнекта.
- Реализация — пишем код на Swift/Kotlin с использованием современных фреймворков.
- Тестирование — проверяем на симуляторах, реальных устройствах, с эмуляцией помех.
- Деплой — публикуем в TestFlight / Firebase Distribution, помогаем с App Review (проверка раздела 4.2 и 5.1 App Store Review Guidelines).
Производительность и батарея
MTU по умолчанию — 23 байта. Запрашиваем через gatt.requestMtu(512) / peripheral.maximumWriteValueLength(for: .withResponse). На больших передачах (прошивка OTA, данные датчиков) это разница между 30 секундами и 3 минутами. Экономия времени при использовании нашей библиотеки достигает 40%. Финансовая эффективность интеграции BLE под ключ очевидна — вы получаете готовое решение без необходимости вникать в тонкости GATT.
Подробнее о MTU
По умолчанию BLE использует MTU 23 байта, из которых 3 — служебные. Мы запрашиваем 512 байт, что в 22 раза больше. Для передачи данных объёмом 1 Кбайт это сокращает количество пакетов с 50 до 3. На устройствах с ограниченным каналом (например, фитнес-браслеты) это экономит до 60% энергии.
Сроки и условия
Базовая интеграция (один notify-сервис) — 3-5 дней. Сложные сценарии (OTA, мультиперифери, background mode) — 1-2 недели. Стоимость рассчитывается индивидуально после анализа вашего устройства. Свяжитесь с нами для бесплатной оценки вашего проекта. Закажите интеграцию BLE сегодня — мы предложим варианты реализации.
Рекомендуем прочитать официальную документацию CoreBluetooth и Android BluetoothGatt для более детального понимания API. Подробнее о GATT читайте в Wikipedia.







