BiosignalProvider
The HR/HRV/RR source. Stream-based: the engine subscribes once per session and unsubscribes on stop.Contract (Dart)
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
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
| Implementation | Package | Notes |
|---|---|---|
MockBiosignalProvider | all three | Deterministic with seed; safe for CI. |
WatchBiosignalProvider | Dart | Bridges through SessionChannel. isAvailable = true optimistically. |
WearBiosignalProvider | Kotlin | Reads from synheart-wear adapters. |
HealthConnectBiosignalProvider | Kotlin | Reads from Health Connect on the phone. |
| HealthKit provider | Swift | Under SynheartSessionHealthKit. |
| Wear (watchOS) provider | Swift | Under SynheartSessionWear. |
- BLE HRM: hosts can build a
BiosignalProvideroversynheart-wear’s BLE HRM client. - Custom mocks for replay tests.
BehaviorProvider
The behavioral signal source. Pull-based: the engine callscurrentSnapshot() at each frame tick.
Contract
null return means “no snapshot available right now”; the engine omits the behavior field on that frame.
BehaviorSnapshot
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
| Implementation | Package | Notes |
|---|---|---|
MockBehaviorProvider | all three | Returns stable mid-range values for tests. |
synheart_behavior-backed provider | Dart, Kotlin, Swift | Apps wire BehaviorProvider to a synheart_behavior BehaviorEngine; the snapshots come from the active behavior session. |
synheart-core (which depends on both packages).
Integration with the engine
LiveSessionEnginestill emitsSessionStarted, frames withsample_count = 0,hr_mean_bpm = 0.0, no behavior, and a finalSessionSummary.- This is the deterministic-no-data path useful for testing the framing logic itself.
Provider state machine (informal)
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:
BiosignalFrameevents → emit eachBiosignalSampledirectly to the controller.SessionFrameevents → synthesise one sample frommetrics.hr_mean_bpmwithrrIntervalMs = 60000.0 / bpm.SessionStarted,SessionSummary,SessionError→ ignored (lifecycle, not biosignal data).
Authoring a provider
A customBiosignalProvider 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 fromstartStreaming(). - Not block the calling thread — the engine subscribes synchronously.
BehaviorProvider should:
- Maintain its own internal accumulators.
- Return a fresh
BehaviorSnapshoton eachcurrentSnapshot()call (ornullwhen no recent activity). - Use real
DateTime.now().millisecondsSinceEpochfor the snapshot timestamp.
Related
- Lifecycle — when the engine subscribes/unsubscribes.
- Watch Protocol —
BiosignalBatchandBiosignalSampleon the wire.