DeviceSession

One connected device. Owns a supervised CoroutineScope cancelled on disconnect; the handshake awaits suspend acks from the GattConnection instead of polling a shared event log with magic delays (the legacy design). Fallible ops return Result.

Created by BrainAccessClient; not constructed directly.

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
val battery: StateFlow<Float>
Link copied to clipboard
val charging: StateFlow<Boolean?>

Charger state from the SIG Battery Level Status characteristic (0x2BED), best-effort: true = charging, false = discharging, null = unknown or not exposed by this firmware.

Link copied to clipboard
val contact: StateFlow<BooleanArray>

Per-active-channel electrode contact from the latest chunk; false = not connected.

Link copied to clipboard

Hardware generation, resolved during initialize from the Device Information Service Model Number (the C core's ba_device_model); until then, and whenever the string is missing or names a different montage than the advertised name, the V2 board of model. Decides which IMU blocks the stream footer carries and how EegChunk.accel is scaled — a HALO V3 has an accelerometer and gyroscope, a HALO V2 has neither.

Link copied to clipboard
Link copied to clipboard
val info: StateFlow<DeviceInfo?>

Device Information Service strings; populated once during initialize.

Link copied to clipboard
val missedSamples: StateFlow<Long>

Cumulative device-side samples lost over the air (gap-filled), this session.

Link copied to clipboard
Link copied to clipboard
val state: StateFlow<DeviceState>
Link copied to clipboard

Whether this device exposes the vendor firmware-update service (see updateFirmware).

Link copied to clipboard

Whether this device can stream OutputMode.TEST_SIGNAL: firmware 3.7.0+ exposes the output-mode characteristic, older firmware does not (see stream).

Functions

Link copied to clipboard
suspend fun configure(newConfig: DeviceConfig): Result<Unit>

Apply a new configuration (writes the config blob and restarts sample indexing). Fails with BrainAccessError.ConfigRejected when the device refuses the blob; the previous config then stays current.

Link copied to clipboard

The config currently applied to the device.

Link copied to clipboard
suspend fun disconnect()

Stop streaming, unsubscribe, disconnect, and cancel this session's scope.

Link copied to clipboard
suspend fun initialize(): Result<Unit>

Bring the device to DeviceState.Ready: discover, raise MTU, read+subscribe battery, subscribe stream data, and write the stored (or default) config.

Link copied to clipboard
fun stream(outputMode: OutputMode = OutputMode.NORMAL): Flow<EegChunk>

Cold stream of decoded chunks. On collection it starts the device stream (control 0x01) and decodes each notification into one EegChunk; on completion it stops the stream (control 0x00, best-effort). Notifications are buffered UNBOUNDED between the BLE callback and the decoder: the transport's callback bridge (Nordic callbackFlow) would otherwise silently discard packets once its default 64-slot buffer fills behind a transiently slow collector — a drop the decoder then gap-fills and misattributes to missedSamples as device-side loss. A backlog here costs memory (~500 B/packet), never data; only a consumer that is sustainedly slower than real time can grow it. Each chunk also carries per-active-channel EegChunk.contact; device-side losses (gap-filled samples) accumulate into missedSamples.

Link copied to clipboard
fun updateFirmware(firmware: ByteArray): Flow<OtaProgress>

Flash firmware onto the device (vendor OTA service; see OtaControl for the wire contract). Cold flow: collection performs the update and reports OtaProgress; the terminal emission is OtaProgress.Done, after which the device applies the image and typically reboots — expect the session to go DeviceState.Disconnected. On any failure (NAK, timeout, link drop) the device keeps its previous firmware.