Medical IoT Device Data Streaming in Mobile App

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Medical IoT Device Data Streaming in Mobile App
Complex
~1-2 weeks
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Real-time Data Streaming from Medical IoT Devices

Medical IoT devices—portable ECGs (AliveCor KardiaMobile, Holter monitors), pulse oximeters (Nonin, Masimo), Bluetooth-capable glucose meters (Abbott Libre, Dexcom G7), blood pressure monitors (Omron, Withings)—operate using standard Bluetooth LE profiles defined by Bluetooth SIG or proprietary protocols. Developing a mobile client for such devices sits at the intersection of several rarely-combined requirements: real-time raw signal streaming, clinically accurate processing, and regulatory compliance (FDA 21 CFR Part 11, MDR in Europe, Roszdravnadzor requirements).

Standard Medical GATT Profiles

Bluetooth SIG defines profiles for compatible devices:

Profile UUID Device
Health Thermometer (HTP) 0x1809 Thermometers
Blood Pressure (BLP) 0x1810 Blood pressure monitors
Pulse Oximeter (PLX) 0x1822 Pulse oximeters
Glucose Profile (GLP) 0x1808 Glucose meters
Continuous Glucose (CGP) 0x181F CGM sensors (Libre, Dexcom)
ECG Profile 0x1843 ECG devices

Example parsing 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

    // Values in IEEE-11073 SFLOAT format (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 is neither a standard float32 nor an int16 in units of 0.1. Confusion here leads to systolic blood pressure readings of "1270 mmHg" on screen—a classic error.

ECG Streaming: Buffer and MTU

Portable ECGs are the most demanding task. AliveCor KardiaMobile 6L outputs 12-channel ECG at 300 sps. Through standard BLE Notify (MTU 23 bytes = 20 bytes payload), bandwidth barely supports 1-channel ECG at 250 sps. For multi-channel, negotiate MTU 247+ bytes:

gatt.requestMtu(247)

// One ECG packet: timestamp(4) + 12 channels * 3 bytes = 40 bytes
// At MTU 247: ~5 frames per notification = 250 sps * 12 channels = 3000 values/sec
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 channels

    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 is necessary because 16-bit is insufficient for clinical ECG amplitude (range ±5 mV at 1 µV resolution = 10,000 levels, requires minimum 14 bits; clinically, 24 bits is standard).

Signal Rendering and Buffer

ECG streaming on-screen has strict memory and FPS requirements. A circular buffer for 10 seconds at 300 sps = 3,000 points per channel = 36,000 values for 12 channels. Store in FloatArray to avoid object allocation in the render thread.

On Android—custom View with Canvas, draw via Paint.setPathEffect(null) and accumulate Path directly. On iOS—CALayer + Core Graphics or Metal for high load. ChartsUI and MPAndroidChart are unsuitable for ECG—they're not designed for continuous append in 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()  // request redraw
    }

    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() without postInvalidateOnAnimation() minimizes latency. Vsync naturally caps rendering to 60/120 FPS.

Storage and Transmission: FHIR and GDPR

Medical data is personally identifiable information with the highest protection level. On-device storage: encryption via Android Keystore / iOS Data Protection (class NSFileProtectionComplete). Server transmission: only TLS 1.2+, preferably mTLS.

For integration with medical systems (EHR, HL7)—format data as FHIR R4: Observation resource for measurements, DiagnosticReport for ECG reports. Apple HealthKit stores data in FHIR-compatible format since iOS 12.

// Save blood pressure measurement to 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])
}

Regulatory Requirements and Constraints

If the application qualifies as a medical device (provides diagnosis, recommends treatment)—registration with Roszdravnadzor (Russia) or CE MDR (Europe) is required. A "viewer" application without clinical decision-making typically falls outside regulatory scope, but this must be confirmed with medical law specialists before development begins.

Developing a mobile client for medical IoT device with real-time data streaming, clinically accurate parsing, and HealthKit integration: 10–16 weeks. Complexity increases significantly with proprietary device protocols or FHIR integration requirements. Cost is individually quoted after device specification and regulatory context analysis.