Skip to main content

Overview

The Synheart Session Kotlin SDK provides a native SessionEngine that captures heart rate data, computes session metrics on-device, and optionally fuses behavioral signals alongside HR data. The engine is timer-driven, emitting session frames at configurable intervals and delivering a session summary when complete. The SDK supports pluggable BiosignalProvider (for HR sources) and BehaviorProvider (for behavioral signals) via reflection-based wrappers.

Installation

Maven Central

dependencies {
    implementation 'ai.synheart:synheart-session:0.2.0'
}

Local Module

// settings.gradle
include ':synheart-session'
project(':synheart-session').projectDir = new File('../synheart-session-kotlin')

// build.gradle
dependencies {
    implementation project(':synheart-session')
}

Requirements

  • Android API 21+
  • Kotlin 1.9+

Quick Start

Mock Mode (Development)

import ai.synheart.session.*

val config = SessionConfig(
    sessionId = UUID.randomUUID().toString(),
    mode = SessionMode.FOCUS,
    durationSec = 300,
    profile = ComputeProfile(windowSec = 60, emitIntervalSec = 5)
)

val engine = SessionEngine()
engine.start(config) { event ->
    when (event["type"] as? String) {
        "session_started" -> Log.d("Session", "Started")
        "session_frame" -> {
            val metrics = event["metrics"] as? Map<*, *>
            Log.d("Session", "HR: ${metrics?.get("hr_mean_bpm")} bpm")
        }
        "session_summary" -> Log.d("Session", "Complete")
        "session_error" -> Log.e("Session", "Error: ${event["message"]}")
    }
}

// Stop early (optional)
engine.stop(config.sessionId)

Real BLE Heart Rate Data

import ai.synheart.session.*

// Wrap any BLE HRM client as a BiosignalProvider (reflection-based, no compile dep needed)
val provider = WearBiosignalProvider(bleHrmClient)
val engine = SessionEngine(provider)

engine.start(config) { event ->
    // Same event handling — metrics are now computed from real HR data
}

Health Connect Heart Rate Data

import ai.synheart.session.*

val provider = HealthConnectBiosignalProvider(healthConnectAdapter, context)

if (provider.isAvailable) {
    val engine = SessionEngine(provider)

    engine.start(config) { event ->
        // Metrics are computed from Health Connect HR data
        // (Wear OS watches, Samsung Health, Fitbit, etc.)
    }
}

Session Configuration

val config = SessionConfig(
    sessionId = UUID.randomUUID().toString(),
    mode = SessionMode.FOCUS,        // or SessionMode.BREATHING
    durationSec = 60,                // session duration in seconds
    profile = ComputeProfile(
        windowSec = 10,              // sliding window size
        emitIntervalSec = 3          // session frame interval
    )
)

SessionConfig

ParameterTypeRequiredDefaultDescription
sessionIdStringNoUUIDUnique session identifier
modeSessionModeYes-Session mode (FOCUS or BREATHING)
durationSecIntYes-Session duration in seconds
profileComputeProfileNo60s/5sWindow and emit configuration
includeRawSamplesBooleanNofalseEnable raw biosignal frame streaming
windowLabelString?NonullOptional label for the session window

ComputeProfile

ParameterTypeDefaultDescription
windowSecInt60Sliding window size in seconds
emitIntervalSecInt5Session frame emission interval in seconds
rawEmitIntervalSecInt?nullRaw biosignal frame interval (defaults to emitIntervalSec)

Session Events

The engine callback receives Map<String, Any> dictionaries with a "type" key.

session_started

// event["type"] == "session_started"
// event["session_id"] == "..."
// event["started_at_ms"] == 1700000000000

session_frame

Contains flat metrics computed over the sliding window:
// event["type"] == "session_frame"
val metrics = event["metrics"] as? Map<String, Any>
val hr = metrics?.get("hr_mean_bpm") as? Double       // Mean HR in BPM
val sdnn = metrics?.get("hr_sdnn_ms") as? Double       // SDNN of RR intervals
val rmssd = metrics?.get("rmssd_ms") as? Double        // RMSSD approximation
val motionRms = metrics?.get("motion_rms_g") as? Double // RMS acceleration (optional)
FieldTypeDescription
hr_mean_bpmDoubleMean heart rate (BPM)
hr_sdnn_msDoubleSDNN of RR intervals (ms)
rmssd_msDoubleRMSSD approximation (ms)
sample_countIntNumber of HR samples in window
start_msLongWindow start timestamp (ms)
end_msLongWindow end timestamp (ms)
motion_rms_gDoubleRMS acceleration in g-force (optional, when accelerometer available)
motion_sample_countIntNumber of accelerometer samples in interval (optional)

biosignal_frame

Emitted when includeRawSamples = true. Contains raw biosignal samples for live display:
val samples = event["samples"] as? List<Map<String, Any>>
samples?.forEach { sample ->
    val bpm = sample["bpm"] as? Double
    val rr = sample["rr_interval_ms"] as? Double
}

session_summary

// event["type"] == "session_summary"
val duration = event["duration_actual_sec"] as? Int
val metrics = event["metrics"] as? Map<String, Any>

session_error

// event["type"] == "session_error"
val code = event["error_code"] as? String    // e.g. "sensor_unavailable"
val message = event["message"] as? String
Error codes:
CodeDescription
permission_deniedHealth data permissions not granted
sensor_unavailableHeart rate sensor not available
low_batteryDevice battery too low
os_terminatedOperating system terminated the session
invalid_stateInvalid session state (e.g., duplicate start)

Behavioral Signals

The SessionEngine accepts an optional BehaviorProvider that fuses behavioral data into session frames.

Mock Behavior (Development)

val engine = SessionEngine(behaviorProvider = MockBehaviorProvider())

engine.start(config) { event ->
    val behavior = event["behavior"] as? Map<*, *>
    Log.d("Session", "Stability: ${behavior?.get("stability_index")}")
}

Production (via synheart-behavior)

// Wrap any behavior SDK object as a BehaviorProvider (reflection-based)
val behaviorProvider = BehaviorSdkProvider(behaviorSdkInstance)
val engine = SessionEngine(behaviorProvider = behaviorProvider)
The "behavior" key is only present in frames when a BehaviorProvider is configured and returns data.

Architecture

SessionEngine(provider: BiosignalProvider, behaviorProvider: BehaviorProvider?)
  |
  +-- BiosignalProvider                (interface)
  |     +-- MockBiosignalProvider       (sinusoidal mock, 1 Hz)
  |     +-- WearBiosignalProvider       (wraps BLE HRM, reflection-based)
  |     +-- HealthConnectBiosignalProvider (wraps Health Connect, reflection-based)
  |
  +-- BehaviorProvider                 (interface, pull-based)
  |     +-- MockBehaviorProvider        (stable mid-range values)
  |     +-- BehaviorSdkProvider         (wraps behavior SDK, reflection-based)
  |
  +-- SampleRingBuffer           (thread-safe sliding window)
  +-- computeMetrics()           (pure HR -> HRV metrics)
  +-- SessionError               (sealed class with 5 subclasses)

Custom Provider

Any class implementing BiosignalProvider can drive the session engine:
class MyProvider : BiosignalProvider {
    override val isAvailable = true
    override val name = "my_source"

    override fun startStreaming(onSample: (BiosignalSample) -> Unit) {
        // Push BiosignalSample values into the callback
    }

    override fun stopStreaming() { }
}

val engine = SessionEngine(MyProvider())

API Reference

SessionEngine

ConstructorDescription
SessionEngine()Default mock biosignal provider, no behavior
SessionEngine(provider)Custom biosignal provider
SessionEngine(provider, behaviorProvider)Custom biosignal + behavioral providers
SessionEngine(behaviorProvider = bp)Default mock biosignal + behavioral provider
MethodDescription
start(config, callback)Start a session. Throws if already running.
stop(sessionId)Stop a running session.
getStatus()Returns status map or null if no active session.

Resources


Author: Israel Goytom