SynheartSession, not fields of SessionConfig:
BiosignalProvider
The heart-rate source. The engine starts it once per session and stops it when the session ends.Contract (Dart)
Stream-based:Contract (Kotlin and Swift)
Callback-based:name is a debug identifier. The Dart live engine skips the subscription when isAvailable is false. The Kotlin and Swift engines do not check isAvailable; they call startStreaming, and if it throws, the session emits a sensor_unavailable error event and ends.
BiosignalSample
Dart:timestampMs: Int64, bpm, rrIntervalsMs, deviceId, source). source is required and is copied into raw biosignal_frame samples. Kotlin and Swift samples have no accelerometer or SpO₂ fields.
Engine consumption
The engine pushes each sample into a sliding window (see Lifecycle) and computeshr_mean_bpm from it. There is no provider-side rate limiting.
RR intervals, accelerometer, and SpO₂ are not used to compute any metric. Kotlin and Swift copy RR intervals into raw biosignal_frame samples; the Dart live engine does not emit raw frames.
Bundled implementations
Seeded mocks are Dart-only. The Dart SDK has no BLE HRM, Health Connect, or HealthKit provider; implement
BiosignalProvider over your source.
BehaviorProvider
The behavioral signal source. Pull-based: the engine callscurrentSnapshot() at each frame and once more for the summary.
Contract
interface BehaviorProvider) and Swift (protocol BehaviorProvider) have the same three members. The engine skips the provider when isAvailable is false. A null snapshot means “nothing right now”; the engine omits the behavior field on that frame.
BehaviorSnapshot
BehaviorStats, so the two SDKs share a contract without an import dependency. Kotlin and Swift define the same fields.
On a frame’s behavior map, keys are snake_case and only non-null fields appear; app_switches_per_minute and timestamp are always present.
Bundled implementations
The Dart SDK does not bundle a synheart_behavior wrapper; implement
BehaviorProvider over your behavior source.
Integration with the engine
MockBiosignalProvider. In the Dart live mode, with no biosignal provider:
LiveSessionEnginestill emitsSessionStarted, frames withsample_count = 0andhr_mean_bpm = 0.0, and a finalSessionSummary.- This is useful for testing the framing logic itself.
Provider state machine (informal)
stopStreaming() being called more than once — the Kotlin and Swift engines call it on session end and again on dispose(). They should stop emitting after stopStreaming() returns.
Watch provider specifics
WatchBiosignalProvider (Dart):
BiosignalFrameevents → eachBiosignalSampleis passed to the engine directly.SessionFrameevents → one sample synthesised frommetrics.hr_mean_bpm(when it is above 0) withrrIntervalMs = 60000.0 / bpm.SessionStarted,SessionSummary,SessionError→ ignored (lifecycle, not biosignal data).
Authoring a provider
A customBiosignalProvider should:
- Allocate sensor resources (BLE GATT subscribe, HealthKit authorization, etc.) on
startStreaming()and release them onstopStreaming(). - Dart: emit samples on the
Stream<BiosignalSample>returned fromstartStreaming(). - Kotlin / Swift: call
onSamplefor each sample, with asourcetag that describes the real source. ThrowSessionError.SensorUnavailable/.sensorUnavailablefromstartStreamingif the source cannot start. - Not block the calling thread —
startStreamingis called synchronously fromstartSession.
BehaviorProvider should:
- Maintain its own internal accumulators.
- Return a fresh
BehaviorSnapshoton eachcurrentSnapshot()call (ornullwhen no recent activity). - Use the current epoch milliseconds for the snapshot timestamp.
Related
- Lifecycle — when the engine starts and stops providers.
- Watch Protocol — the watch events
WatchBiosignalProviderconsumes.