Интеграция Bluetooth Low Energy (BLE) в мобильное приложение

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Интеграция Bluetooth Low Energy (BLE) в мобильное приложение
Сложный
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Интеграция Bluetooth Low Energy (BLE) в мобильное приложение

BLE-интеграция — это не просто «подключить устройство». Это конечный автомат с дюжиной состояний, каждое из которых может завершиться ошибкой: адаптер выключен, устройство вне зоны, сервис не найден, характеристика не поддерживает запись. Приложения, которые не обрабатывают эти состояния явно, стабильно крэшатся при первом реальном использовании.

Архитектура BLE-стека

BLE работает по модели GATT (Generic Attribute Profile). Периферийное устройство (датчик, браслет, замок) предоставляет Services — логические группы функций. Каждый Service содержит Characteristics — конкретные значения для чтения, записи или подписки (notify/indicate).

Пример: фитнес-браслет имеет Service 0x180D (Heart Rate). В нём Characteristic 0x2A37 — текущий пульс с флагом notify. Приложение подписывается и получает данные без опроса.

Конечный автомат подключения

Минимальный набор состояний, который нужно обрабатывать:

IDLE → SCANNING → DISCOVERED → CONNECTING → CONNECTED → DISCOVERING_SERVICES
     → SERVICES_READY → SUBSCRIBING → READY

И ошибки на каждом переходе: таймаут сканирования, разрыв соединения в CONNECTING, gattStatus != GATT_SUCCESS при discovery, потеря соединения в READY.

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-спецификации характеристики
    }
}

Проблемы, которые встречаются в каждом проекте

didDisconnectPeripheral без предупреждения. BLE-соединение рвётся при выходе из зоны, разряде устройства, системном вмешательстве. Нужен автоматический реконнект: при получении didDisconnectPeripheralcentralManager.connect(peripheral) с экспоненциальным backoff.

Сканирование с allowDuplicates: true убивает батарею. Включаем только если нужны постоянные обновления RSSI для измерения расстояния. Для обычного поиска — false.

State restoration. Если приложение убито системой во время BLE-сессии, CBCentralManagerOptionRestoreIdentifierKey позволяет восстановить состояние при следующем запуске. Без этого устройство считает себя подключённым, а приложение не знает об этом.

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)

Статус 133 (GATT_ERROR) — самый частый при подключении на Android. Причины: устройство уже подключено в другом процессе, кэш GATT устарел. Лечится: gatt.close() + gatt.refresh() (через рефлексию) + повторное подключение через 1-2 секунды.

Android 12+ разрешения. BLUETOOTH_SCAN и BLUETOOTH_CONNECT — обязательны. Старый BLUETOOTH и BLUETOOTH_ADMIN больше не работают на API 31+.

Производительность и battery

MTU по умолчанию — 23 байта. Запрашиваем через gatt.requestMtu(512) / peripheral.maximumWriteValueLength(for: .withResponse). На больших передачах (прошивка OTA, данные датчиков) это разница между 30 секундами и 3 минутами.

Срок интеграции: 3-5 дней — базовое подключение с notify. Сложные сценарии (OTA update, мультиперифери, background mode) — 1-2 недели. Стоимость рассчитывается индивидуально.