Реализация потокового считывания данных с медицинских 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
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1054
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

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

Медицинские IoT-устройства — портативные ЭКГ (AliveCor KardiaMobile, Holter-мониторы), пульсоксиметры (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 exponent + 12-bit mantissa
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.

// Сохранение измерения давления в 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-интеграции. Стоимость рассчитывается индивидуально после анализа спецификации устройства и регуляторного контекста.