Реалізація потокового зчитування даних з медичних IoT-пристроїв
Медичні IoT-пристрої — портативні ЕКГ (AliveCor KardiaMobile, Холтер-монітори), пульсоксиметри (Nonin, Masimo), глюкометри з BLE (Abbott Libre, Dexcom G7), тонометри (Omron, Withings) — працюють за стандартними Bluetooth LE профілями Bluetooth SIG або за пропрієтарними протоколами. Розробка мобільного клієнта для таких пристроїв знаходиться на перетині кількох вимог: стрімінг сирого сигналу в реальному часі, клінічна точність обробки та відповідність регуляторним нормам (FDA 21 CFR Part 11, MDR в Європі, Росздравнагляд).
Ми — команда з 10+ років досвіду в медичній IoT-розробці. За цей час ми реалізували потоковий стрімінг для 15+ типів пристроїв, включаючи багатоканальні ЕКГ та CGM-сенсори. Наші рішення сертифіковані та пройшли аудит вендорів (Abbott, Masimo). Гарантуємо відповідність стандартам та конфіденційність даних (GDPR, 152-ФЗ). Оцінимо ваш проект безкоштовно — пишіть нам.
Чому стрімінг з BLE-медичних пристроїв — технічно складне завдання?
BLE — не TCP/IP. Вузький MTU (за замовчуванням 23 байти), часті переривання зв'язку, бінарні протоколи з нестандартними форматами чисел (IEEE-11073 SFLOAT, 24-bit signed). Крім того, медичні дані вимагають високої надійності: втрата одного пакета може спотворити клінічну картину. Наш досвід дозволяє вирішувати ці проблеми на рівні проектування — ми використовуємо кругові буфери, механізми повторної передачі та пропрієтарні профілі з CRC.
Стандартні медичні 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 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) } 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. Для багатоканального потрібен negotiate 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.
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 (Європа). Додаток-«переглядач» без клінічних рішень зазвичай виходить за периметр регулювання — але це питання вирішується з юристами в галузі медичного права до початку розробки.
Що входить у розробку під ключ?
Ми надаємо повний комплект: документацію за протоколом пристрою, вихідний код модуля BLE-комунікації, тестовий стенд, інтеграцію з HealthKit/Google Fit, керівництво користувача та навчання команди замовника. При необхідності допомагаємо з сертифікацією.
Терміни та вартість
Розробка мобільного клієнта для медичного IoT-пристрою з потоковим зчитуванням даних, клінічно точним парсингом та інтеграцією з HealthKit: 10–16 тижнів. Складність суттєво зростає при пропрієтарному протоколі пристрою або вимогах FHIR-інтеграції. Вартість розраховується індивідуально після аналізу специфікації пристрою та регуляторного контексту. Економія за рахунок готових модулів — до 40% бюджету.
Як ми гарантуємо клінічну точність?
Ми використовуємо референсні парсери, схвалені виробниками пристроїв. Кожен модуль проходить навантажувальне тестування (стрімінг 24 години без втрат) і порівнюється з еталонним обладнанням. У результаті — дані, придатні для клінічного використання.
Зв'яжіться з нами для оцінки вашого проекту
Залиште заявку на нашому сайті або напишіть у Telegram — ми проаналізуємо специфікацію вашого IoT-пристрою, підберемо оптимальне архітектурне рішення та назвемо точні терміни. Замовте розробку вже сьогодні та отримайте консультацію безкоштовно.







