RadioInterfaceService

Interface for the low-level radio interface that handles raw byte communication.

This is the transport layer — it manages the raw hardware connection (BLE, TCP, Serial, USB) to a Meshtastic radio. Its connectionState reflects whether the physical link is up or down, before any handshake or config-loading logic is applied.

Important: UI and feature modules should never observe connectionState directly. Instead, they should use ServiceRepository.connectionState, which is the canonical app-level connection state that accounts for handshake progress, light-sleep policy, and other higher-level concerns. The only legitimate consumer of this transport-level flow is MeshConnectionManager, which bridges transport state changes into the app-level ServiceRepository.connectionState.

See also

Properties

Link copied to clipboard
abstract val activeSession: StateFlow<RadioSessionContext?>

The transport session that still owns lifecycle completion, or null after teardown drains every admitted operation. Implementations close admission before teardown, so isSessionActive and the run helpers reject new work immediately even while this flow temporarily retains the draining session.

Link copied to clipboard
abstract val connectionError: Flow<String>

Flow of user-facing connection error messages (e.g. permission failures).

Link copied to clipboard
abstract val connectionState: StateFlow<ConnectionState>

Transport-level connection state of the radio hardware.

Link copied to clipboard
abstract val currentDeviceAddressFlow: StateFlow<String?>

Flow of the current device address.

Link copied to clipboard

Whether this build carries the packet capture the replay transport needs. False means selecting a replay address would fall back to the plain mock, so callers offering a replay device must hide it.

Link copied to clipboard
abstract val meshActivity: Flow<MeshActivity>

Flow of radio activity events.

Link copied to clipboard
abstract val mockTransportEnabled: StateFlow<Boolean>

Whether the virtual demo transports may be offered and bound right now.

Link copied to clipboard
abstract val receivedData: Flow<ReceivedRadioFrame>

Flow of raw data received from the radio, bound to the transport session that admitted each frame.

Link copied to clipboard
abstract val serviceScope: CoroutineScope

The scope in which interface-related coroutines should run.

Link copied to clipboard
abstract val sessionGeneration: StateFlow<Long>

Monotonically increasing generation bumped on every transport start (including same-address reconnect). Consumers use this to discard state retained from a previous transport instance. Stub implementations that never start a real transport expose a constant zero flow.

Link copied to clipboard

The device types supported by this platform's radio interface.

Functions

Link copied to clipboard
abstract fun connect()

Initiates the connection to the radio.

Link copied to clipboard

Consumes and returns the GATT cache invalidation request. Returns true exactly once after requestGattCacheInvalidationOnNextConnect was called, then resets to false.

Link copied to clipboard
abstract suspend fun disconnect()

Explicitly tears down the active transport, sending a polite ToRadio(disconnect = true) goodbye frame first when a transport is live. Safe to call when nothing is connected — implementations must no-op in that case. Suspends until the teardown completes.

Link copied to clipboard
abstract fun getDeviceAddress(): String?

Returns the current device address.

Link copied to clipboard
abstract fun handleFromRadio(bytes: ByteArray)

Called when the transport has received raw data from the radio.

Link copied to clipboard

Returns whether session still owns transport admission. Implementations must make this reflect the admission gate, not only activeSession, because the draining session may remain published after new work is rejected.

Link copied to clipboard
abstract fun onConnect()

Called when the transport has successfully established a connection.

Link copied to clipboard
abstract fun onDisconnect(isPermanent: Boolean, errorMessage: String? = null, reason: TransportDisconnectReason? = null)

Called when the transport has disconnected.

Requests that the next BLE transport connection invalidates Android's GATT service cache before service discovery. Used after OTA firmware updates where the device reboots with a potentially different BLE service table on the same MAC address.

Link copied to clipboard
abstract fun resetReceivedBuffer()

Drains any bytes currently buffered in receivedData without emitting them to collectors.

Link copied to clipboard
abstract suspend fun restartTransport()

Silent in-place transport restart for handshake stalls: tears down the active transport and re-establishes it in place, without touching the connection-request gate or the selected device address.

Link copied to clipboard
abstract fun runIfSessionActive(session: RadioSessionContext, block: () -> Unit): Boolean

Runs block only while session owns transport admission. Implementations must make the admission check and synchronous side effect atomic with teardown.

Link copied to clipboard
open suspend fun runWhileSessionActive(session: RadioSessionContext, block: suspend () -> Unit): Boolean

Runs block while holding the same lifecycle lease, without exposing the lease token. Implementations may serialize this convenience path to preserve handshake ordering; independently deferred work should use runWithSessionLease so it can acquire its own lease before its parent operation returns.

Link copied to clipboard
abstract suspend fun runWithSessionLease(session: RadioSessionContext, block: suspend (RadioSessionLease) -> Unit): Boolean

Acquires a lifecycle lease for suspend block. Once admitted, teardown closes admission to later work and waits for this block to finish before publishing session completion or starting a replacement transport. block may use RadioSessionLease.isCurrent for transaction-bound checks that must remain valid through commit even after teardown has closed new admission. Implementations must acquire and release the lease through the same admission state used by teardown; comparing only activeSession cannot satisfy the draining contract. Callers must keep the block bounded and must not invoke transport lifecycle methods from inside it.

Link copied to clipboard
open fun sendToRadio(bytes: ByteArray)

Sends bytes when a transport is available; callers that need admission evidence use trySendToRadio.

Link copied to clipboard
abstract fun setDeviceAddress(deviceAddr: String?): Boolean

Sets the device address to connect to.

Link copied to clipboard
abstract fun toInterfaceAddress(interfaceId: InterfaceId, rest: String): String

Constructs a full radio address for the specific interface type.

Link copied to clipboard
abstract fun trySendToRadio(bytes: ByteArray): Boolean

Attempts to dispatch bytes to the active transport.