Розробка мобільного додатку для носимих IoT-пристроїв (Wearables)

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

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Розробка мобільного додатку для носимих IoT-пристроїв (Wearables)
Складний
від 1 тижня до 3 місяців
Часті запитання

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

Етапи розробки

Останні роботи

  • 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

Розробка мобільних компаньйонів для носимих IoT-пристроїв

Носимі пристрої — трекери активності, медичні патчі, промислові «мітки» робітника — живуть у постійному протиріччі: батарея маленька, даних потрібно багато, зв'язок нестабільна. Розробка додатка-компаньйона для такого пристрою — це передусім робота з BLE, управління енергоспоживанням та синхронізація накопленних даних. Загальні патерни роботи з BLE-периферією описані в окремому розділі з передачі даних з фітнес-браслетів; тут сосредоточимось на специфіці кастомних IoT-носимих.

BLE GATT-профіль кастомного пристрою

Кастомне носимо — не Apple Watch та не Fitbit. У нього свій GATT-сервіс з проприетарними UUID, які назначає виробник пристрою. Перша завдача — отримати специфікацію GATT від firmware-команди або реверс-інжинірити її через Nordic nRF Connect.

Типовий GATT-профіль промислового носимого:

Service UUID Characteristic Properties Опис
0x1800 Device Name Read Стандарт GAP
0x180F Battery Level Read, Notify Стандарт BAS
{custom}-0001 Raw Sensor Data Notify Потік IMU/датчиків
{custom}-0002 Buffered Data Read, Indicate Накопчені записи
{custom}-0003 Control Point Write Команди пристрою
{custom}-0004 Device Status Read, Notify Статус, помилки, uptime

Підключення та підписка на Notify-характеристику на Android через корутини:

class WearableRepository(private val context: Context) {
    private var gatt: BluetoothGatt? = null
    private val _sensorData = MutableSharedFlow<SensorFrame>(extraBufferCapacity = 64)
    val sensorData: SharedFlow<SensorFrame> = _sensorData.asSharedFlow()

    suspend fun connect(device: BluetoothDevice): Result<Unit> = withContext(Dispatchers.IO) {
        val connected = CompletableDeferred<Boolean>()

        gatt = device.connectGatt(context, false, object : BluetoothGattCallback() {
            override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    gatt.discoverServices()
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    connected.complete(false)
                    scheduleReconnect(device)
                }
            }

            override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    enableSensorNotify(gatt)
                    connected.complete(true)
                }
            }

            override fun onCharacteristicChanged(
                gatt: BluetoothGatt,
                characteristic: BluetoothGattCharacteristic,
                value: ByteArray,
            ) {
                if (characteristic.uuid == SENSOR_DATA_UUID) {
                    val frame = SensorFrame.fromBytes(value)
                    _sensorData.tryEmit(frame)
                }
            }
        }, BluetoothDevice.TRANSPORT_LE)

        if (connected.await()) Result.success(Unit)
        else Result.failure(IOException("Connection failed"))
    }
}

Критичний момент: BluetoothGattCallback працює на виділеному binder-потоці Android, не на main thread. Всі виклики gatt.writeCharacteristic() теж повинні йти послідовно — паралельні GATT-операції вызивают GATT_BUSY (133) та рандомні дисконнекти.

Управління чергою GATT-операцій

Найчастіший джерело крешів при роботі з BLE — паралельні GATT-запити. Android BLE stack не підтримує конкурентні операції. Потрібна черга:

class GattOperationQueue {
    private val queue = Channel<GattOperation>(capacity = Channel.UNLIMITED)
    private val executor = CoroutineScope(Dispatchers.IO + SupervisorJob())

    init {
        executor.launch {
            for (operation in queue) {
                operation.execute()
                // Чекаємо callback перед наступною операцією
                operation.awaitCompletion()
            }
        }
    }

    suspend fun enqueue(operation: GattOperation) {
        queue.send(operation)
    }
}

Без такої черги проект з кількома пристроями гарантовано отримає onCharacteristicWrite з status=133 на частині пристроїв.

Синхронізація накопленних даних

Носимо пишет дані у внутрішній буфер (flash або SRAM) коли телефон недоступен. При підключенні потрібно вичитать весь буфер — іноді кілька тисяч записів по 20 байт кожна.

Протокол вичитки через Indicate-характеристику:

suspend fun syncBufferedData(): List<SensorRecord> {
    val records = mutableListOf<SensorRecord>()
    var offset = 0

    do {
        // Запитуємо порцію даних командою у Control Point
        writeControlPoint(ReadBufferCommand(offset = offset, count = 50))

        // Чекаємо Indicate з відповіддю
        val chunk = awaitIndicate(BUFFERED_DATA_UUID, timeout = 5.seconds)
        val parsed = SensorRecord.parseChunk(chunk)
        records.addAll(parsed)
        offset += parsed.size

        // Останній чанк — флаг кінця буфера у заголовку
    } while (!SensorRecord.isLastChunk(chunk))

    // Підтверджуємо синхронізацію — пристрій може очистити буфер
    writeControlPoint(AckSyncCommand(recordsReceived = records.size))
    return records
}

MTU negotiation перед синхронізацією (requestMtu(512)) пришвидшує передачу: замість 20 байт per notification отримуємо до 509 байт. На iOS MTU 185 байт за замовчанням, negotiate до 512 на Bluetooth 5.0+.

iOS: CoreBluetooth та дозволи

На iOS вся робота з BLE через CoreBluetooth. Фоновий режим вимагає background mode bluetooth-central у Info.plist. Без нього підписка на Notify обривається коли додаток йде у фон — дані з пристрою втрачаються.

class WearableManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    private var centralManager: CBCentralManager!
    private var peripheral: CBPeripheral?

    override init() {
        super.init()
        // CBCentralManagerOptionRestoreIdentifierKey — відновлення після kill додатка
        centralManager = CBCentralManager(delegate: self,
            queue: DispatchQueue(label: "ble.queue"),
            options: [CBCentralManagerOptionRestoreIdentifierKey: "WearableSession"])
    }

    func centralManager(_ central: CBCentralManager,
                        willRestoreState dict: [String: Any]) {
        // Відновлюємо підключення після перезапуску процесу ОС
        if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
            as? [CBPeripheral], let p = peripherals.first {
            peripheral = p
            peripheral?.delegate = self
        }
    }
}

CBCentralManagerOptionRestoreIdentifierKey — без цього при перезапуску процесу на iOS втрачається сесія BLE та пристрій потрібно переподключати вручну.

Оновлення прошивки по воздуху (DFU)

Для Nordic nRF-чипів — бібліотека iOSDFULibrary (Swift) та Android-DFU-Library (Kotlin). Для STM32WB — ST BLE Mesh DFU. Показуємо прогрес з байтами та відсотками, блокуємо інші операції на час DFU, обробляємо переривання — пристрій повинен уметь відновити завантаження з місця переривання (DFU resume).

Розробка мобільного компаньйона для кастомного носимого IoT-пристрою з BLE GATT, синхронізацією буфера та DFU: 8–14 тижнів залежно від складності GATT-профіля та вимог до фонової роботи. Вартість розраховується індивідуально після вивчення специфікації пристрою.