Skip to main content
The session SDK decouples the engine from data sources via two abstractions. This page covers the contracts, the bundled implementations, and the integration semantics with the engine.

BiosignalProvider

The HR/HRV/RR source. Stream-based: the engine subscribes once per session and unsubscribes on stop.

Contract (Dart)

Kotlin (interface BiosignalProvider) and Swift (protocol BiosignalProvider) mirror this. name is a debug identifier (not displayed to users). isAvailable is a self-report — the engine still subscribes when false if the host explicitly opts in.

BiosignalSample

Required: timestampMs (epoch ms), bpm (instantaneous heart rate). Optional: rrIntervalMs (single inter-beat interval), accelerometer (tri-axis, present on watch builds), spo2 (when the wearable reports it).

Engine consumption

LiveSessionEngine subscribes on startSession and pushes each sample into a fixed-capacity ring buffer sized at windowSec. There is no provider-side rate limiting; the engine drops oldest samples on overflow. The accelerometer and SpO₂ fields are not consumed by LiveSessionEngine today — they are forwarded only when the engine emits BiosignalFrame events (raw streaming opt-in). The watch companions populate them at source.

Bundled implementations

External providers (host-supplied):
  • BLE HRM: hosts can build a BiosignalProvider over synheart-wear’s BLE HRM client.
  • Custom mocks for replay tests.

BehaviorProvider

The behavioral signal source. Pull-based: the engine calls currentSnapshot() at each frame tick.

Contract

A null return means “no snapshot available right now”; the engine omits the behavior field on that frame.

BehaviorSnapshot

The shape is field-for-field aligned with synheart_behavior’s BehaviorStats so the two SDKs share a contract without import dependencies. JSON serialisation uses snake_case. When emitted on a SessionFrame.behavior field, only the non-null fields appear (the toJson strips nulls except appSwitchesPerMinute and timestamp).

Bundled implementations

The session SDK does not bundle a default real-data behavior provider; that wiring lives in synheart-core (which depends on both packages).

Integration with the engine

Both providers are optional. With neither configured:
  • LiveSessionEngine still emits SessionStarted, frames with sample_count = 0, hr_mean_bpm = 0.0, no behavior, and a final SessionSummary.
  • This is the deterministic-no-data path useful for testing the framing logic itself.

Provider state machine (informal)

Providers must tolerate stopStreaming() being called multiple times. They must stop emitting after stopStreaming() returns; samples emitted after the engine has unsubscribed are dropped silently.

Watch provider specifics

WatchBiosignalProvider:
  • BiosignalFrame events → emit each BiosignalSample directly to the controller.
  • SessionFrame events → synthesise one sample from metrics.hr_mean_bpm with rrIntervalMs = 60000.0 / bpm.
  • SessionStarted, SessionSummary, SessionError → ignored (lifecycle, not biosignal data).
Channel errors are non-fatal: the provider swallows them so the engine doesn’t terminate the session on watch reconnect/disconnect.

Authoring a provider

A custom BiosignalProvider should:
  • Allocate any sensor resources (BLE GATT subscribe, HK authorization, etc.) on startStreaming().
  • Release them on stopStreaming().
  • Emit samples on the broadcast Stream<BiosignalSample> returned from startStreaming().
  • Not block the calling thread — the engine subscribes synchronously.
A custom BehaviorProvider should:
  • Maintain its own internal accumulators.
  • Return a fresh BehaviorSnapshot on each currentSnapshot() call (or null when no recent activity).
  • Use real DateTime.now().millisecondsSinceEpoch for the snapshot timestamp.
  • Lifecycle — when the engine subscribes/unsubscribes.
  • Watch ProtocolBiosignalBatch and BiosignalSample on the wire.