Overview
The Synheart Session Kotlin SDK providesSynheartSession, a timer-driven session engine that buffers heart-rate samples from a BiosignalProvider, emits typed SessionEvents on a SharedFlow, and delivers a SessionSummary when the session ends. An optional BehaviorProvider adds behavioral signals to each frame. The bundled wrappers for synheart-wear and synheart-behavior use reflection, so those libraries are only needed at runtime.
WatchSessionRelay runs a session on a companion Wear OS watch instead and streams the watch’s events back.
The SDK computes mean HR locally. HRV (
hr_sdnn_ms, rmssd_ms, pnn50) is supplied by the host through ingestHsiMetrics() and is 0.0 until then.Installation
Maven Central
kotlinx-coroutines-core is an api dependency, so Flow and SharedFlow are available to your code without adding it yourself.
Local Module
Requirements
- Android API 21+ (
minSdk 21) - Java 17 bytecode
- The library is built with Kotlin 2.3.0 and depends on
kotlin-stdlib2.3.0
Quick Start
Mock Mode (Development)
startSession returns a hot SharedFlow<SessionEvent> that replays up to 1024 events, so a collector that subscribes after the start still sees SessionStarted. The flow never completes on its own; end collection on SessionSummary or SessionErrorEvent (for example with transformWhile, as above).
Real BLE Heart Rate Data
SessionErrorEvent with SENSOR_UNAVAILABLE and ends.
Health Connect Heart Rate Data
readSnapshot() every 2 seconds by default (pollIntervalMs). Each sample’s rr_intervals_ms is derived from BPM (60000 / bpm), not measured.
Session Configuration
SessionConfig
ComputeProfile
Session Events
SessionEvent is a sealed class. Every subclass has sessionId and toMap(), which returns the snake_case wire map shared with the other SDKs.
SessionStarted
Emitted immediately bystartSession.
SessionFrame
Emitted everyemitIntervalSec while the window holds at least one sample (frames are skipped while it is empty).
metrics keys:
BiosignalFrame
Emitted everyrawEmitIntervalSec when includeRawSamples = true and the window is not empty. seq is a separate counter from SessionFrame.seq.
timestamp_ms, bpm, source, and, when the provider supplies them, rr_intervals_ms and device_id.
SessionSummary
Emitted once when the duration elapses orstopSession is called.
SessionErrorEvent
Emitted instead of a summary when the session ends abnormally — for example when the provider throws fromstartStreaming, which ends the session with SENSOR_UNAVAILABLE.
On the wire (
toMap()), the code is under error_code.
Error codes (SessionErrorCode):
API misuse throws instead:
startSession throws SessionError.InvalidState if any session is already running (whatever its id) or the instance is disposed, and stopSession throws it if no session is running or the id does not match. See Errors.
HRV Metrics
dispose() or when sessionId does not match the running session.
Behavioral Signals
SynheartSession accepts an optional BehaviorProvider. The engine pulls a snapshot at each frame and once more for the summary.
Mock Behavior (Development)
Production (via synheart-behavior)
BehaviorSdkProvider reports isAvailable from the SDK’s isInitialized() and reads getCurrentStats() at each frame. The behavior field is only present when a BehaviorProvider is configured, reports isAvailable, and returns a snapshot.
Companion Watch Sessions (Wear OS)
WatchSessionRelay drives a session on a paired Wear OS watch over the Wearable Data Layer. The watch’s companion app runs the session; the relay sends commands and decodes the watch’s events into SessionEvents. It is independent of SynheartSession.
The Play Services libraries are compileOnly in the SDK, so a host that uses the relay must add them itself:
await() from kotlinx-coroutines-play-services. Without play-services-wearable (or on a device without Google Play services), status() reports supported = false and startSession emits a SessionErrorEvent rather than crashing.
startSession emits a SessionErrorEvent with SENSOR_UNAVAILABLE instead of throwing when Play Services is unavailable, when no node accepts the command, or when the watch sends nothing within 10 seconds. A node can accept the message while no app answers it — the Data Layer delivers only between apps with the same package name and signing certificate.
WatchStatus has supported, reachable, paired, installed, and canStartSession (supported && reachable). Wear OS gives no separate paired/installed signal, so paired and installed track reachable.
The message format is described in Watch Protocol.
Architecture
Custom Provider
Any class implementingBiosignalProvider can drive the session:
API Reference
SynheartSession
Resources
- Repository: synheart-session-kotlin
- Issues: GitHub Issues