Інтеграція 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+.

Продуктивність та батарея

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

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