Реалізація потокового зчитування даних з медичних IoT-пристроїв

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

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

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

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Реалізація потокового зчитування даних з медичних IoT-пристроїв
Складний
~1-2 тижні
Часті запитання

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

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

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

  • 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-пристроїв

Медичні IoT-пристрої — портативні ЕКГ (AliveCor KardiaMobile, Холтер-монітори), пульсоксиметри (Nonin, Masimo), глюкометри з BLE (Abbott Libre, Dexcom G7), тонометри (Omron, Withings) — працюють за стандартними профілями Bluetooth LE від Bluetooth SIG або за закритими протоколами. Розробка мобільного клієнта для таких пристроїв знаходиться на перетині кількох вимог, які рідко зустрічаються разом: потокове зчитування сирового сигналу в реальному часі, клінічна точність обробки та відповідність нормативним вимогам (FDA 21 CFR Part 11, MDR в Європі, вимоги МОЗ).

Стандартні медичні GATT-профілі

Для сумісних пристроїв Bluetooth SIG визначив профілі:

Профіль UUID Пристрій
Health Thermometer (HTP) 0x1809 Термометри
Blood Pressure (BLP) 0x1810 Тонометри
Pulse Oximeter (PLX) 0x1822 Пульсоксиметри
Glucose Profile (GLP) 0x1808 Глюкометри
Continuous Glucose (CGP) 0x181F CGM-сенсори (Libre, Dexcom)
ECG Profile 0x1843 ЕКГ-пристрої

Приклад розбору Blood Pressure Measurement (UUID 0x2A35):

func parseBloodPressure(_ data: Data) -> BloodPressureReading {
    var offset = 0
    let flags = data[offset]; offset += 1

    let isMMHg = (flags & 0x01) == 0
    let timestampPresent = (flags & 0x02) != 0
    let pulseRatePresent = (flags & 0x04) != 0

    // Значення у форматі IEEE-11073 SFLOAT (16-bit)
    let systolic  = parseSFloat(high: data[offset + 1], low: data[offset])
    offset += 2
    let diastolic = parseSFloat(high: data[offset + 1], low: data[offset])
    offset += 2
    let map       = parseSFloat(high: data[offset + 1], low: data[offset])
    offset += 2

    return BloodPressureReading(systolic: systolic, diastolic: diastolic,
                                 meanArterialPressure: map, inMMHg: isMMHg)
}

// IEEE-11073 SFLOAT: 4-bit експонента + 12-bit мантиса
func parseSFloat(high: UInt8, low: UInt8) -> Double {
    let rawValue = Int16(high) << 8 | Int16(low)
    let exponent = Int(rawValue >> 12)
    let mantissa = Int(rawValue & 0x0FFF)
    let signedMantissa = mantissa > 0x07FF ? mantissa - 0x1000 : mantissa
    return Double(signedMantissa) * pow(10.0, Double(exponent))
}

IEEE-11073 SFLOAT — це не стандартний float32 і не int16 у одиницях 0,1. Плутанина тут приводить до систолічного тиску "1270 мм рт.ст." на екрані — класична помилка.

Потоковість ЕКГ: буфер і MTU

Портативні ЕКГ — найбільш вимоглива задача. AliveCor KardiaMobile 6L подає 12-канальну ЕКГ на 300 sps. Через стандартний BLE Notify (MTU 23 байти = 20 байт payload) пропускної здатності ледве вистачає на 1-канальну ЕКГ на 250 sps. Для багатоканальної потрібно договорити MTU 247+ байт:

gatt.requestMtu(247)

// Один пакет ЕКГ: timestamp(4) + 12 каналів * 3 байти = 40 байт
// При MTU 247: ~5 кадрів на notification = 250 sps * 12 каналів = 3000 значень/сек
data class EcgPacket(
    val timestamp: Long,
    val samples: Array<IntArray>,  // [channel][sample], signed 24-bit
)

fun parseEcgNotification(data: ByteArray): EcgPacket {
    var offset = 0
    val timestamp = ByteBuffer.wrap(data, offset, 4).int.also { offset += 4 }
    val samples = Array(12) { IntArray(data.size / 36) }  // 12 каналів

    var sampleIdx = 0
    while (offset + 36 <= data.size) {
        for (ch in 0..11) {
            // 24-bit signed little-endian
            val raw = (data[offset].toInt() and 0xFF) or
                      ((data[offset + 1].toInt() and 0xFF) shl 8) or
                      ((data[offset + 2].toInt()) shl 16)
            samples[ch][sampleIdx] = raw
            offset += 3
        }
        sampleIdx++
    }
    return EcgPacket(timestamp, samples)
}

24-bit signed потрібен, тому що 16-bit недостатній для клінічної амплітуди ЕКГ (діапазон ±5 мВ при розрізненні 1 мкВ = 10 000 рівнів, потрібен мінімум 14 біт; клінічно використовують 24).

Буфер та відображення сигналу

Потокова ЕКГ на екрані — задача з жорсткими вимогами до пам'яті та FPS. Круговий буфер на 10 секунд при 300 sps = 3000 точок на канал = 36 000 значень для 12 каналів. Зберігаємо в FloatArray, щоб не розподіляти об'єкти в потоці відображення.

На Android — кастомний View з Canvas, малюємо через Paint.setPathEffect(null) та накопичуємо Path безпосередньо. На iOS — CALayer + Core Graphics або Metal для високого навантаження. ChartsUI та MPAndroidChart для ЕКГ не підходять — вони не розраховані на безперервний append в hot path.

class EcgView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
    : View(context, attrs) {

    private val buffer = CircularFloatBuffer(capacity = 3000)
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.GREEN
        strokeWidth = 1.5f
        style = Paint.Style.STROKE
    }

    fun appendSamples(samples: FloatArray) {
        buffer.append(samples)
        invalidate()  // просимо перерисування
    }

    override fun onDraw(canvas: Canvas) {
        val data = buffer.snapshot()
        val path = Path()
        val scaleX = width.toFloat() / data.size
        val scaleY = height / 2f

        data.forEachIndexed { i, value ->
            val x = i * scaleX
            val y = scaleY - value * scaleY / MAX_AMPLITUDE
            if (i == 0) path.moveTo(x, y) else path.lineTo(x, y)
        }
        canvas.drawPath(path, paint)
    }
}

invalidate() без postInvalidateOnAnimation() — для мінімальної затримки. Vsync сам обмежить до 60/120 FPS.

Зберігання та передача: FHIR та GDPR

Медичні дані — персональні дані з найвищим рівнем захисту. Зберігання на пристрої: шифрування через Android Keystore / iOS Data Protection (клас NSFileProtectionComplete). Передача на сервер — тільки по TLS 1.2+, переважно mTLS.

Для інтеграції з медичними системами (МІС, HL7) — форматування даних у FHIR R4: ресурс Observation для вимірювань, DiagnosticReport для ЕКГ-звітів. Apple HealthKit зберігає дані у форматі, сумісному з FHIR, починаючи з iOS 12.

// Збереження вимірювання артеріального тиску в HealthKit
func saveBloodPressure(_ reading: BloodPressureReading) async throws {
    let systolicType = HKQuantityType(.bloodPressureSystolic)
    let diastolicType = HKQuantityType(.bloodPressureDiastolic)
    let mmHg = HKUnit.millimeterOfMercury()

    let systolicSample = HKQuantitySample(type: systolicType,
        quantity: HKQuantity(unit: mmHg, doubleValue: reading.systolic),
        start: reading.timestamp, end: reading.timestamp)

    let diastolicSample = HKQuantitySample(type: diastolicType,
        quantity: HKQuantity(unit: mmHg, doubleValue: reading.diastolic),
        start: reading.timestamp, end: reading.timestamp)

    try await healthStore.save([systolicSample, diastolicSample])
}

Нормативні вимоги та обмеження

Якщо програма є медичним виробом (видає діагноз, рекомендує лікування) — потрібна реєстрація в МОЗ (Україна) або CE MDR (Європа). Програма-"переглядач" без клінічних рішень зазвичай виходить за периметр регулювання — але це питання вирішується з юристами в галузі медичного права до початку розробки.

Розробка мобільного клієнта для медичного IoT-пристрою з потоковим зчитуванням даних, клінічно точним розбором та інтеграцією з HealthKit: 10–16 тижнів. Складність значно зростає при проприетарному протоколі пристрою або вимогах FHIR-інтеграції. Вартість розраховується індивідуально після аналізу специфікації пристрою та нормативного контексту.