RadioClient

The main entry point to the Meshtastic SDK.

A RadioClient manages a single radio device over a chosen transport (BLE, TCP, serial, etc.) and exposes a clean async/reactive API:

Drop-in philosophy: configure once with Builder, then observe and call:

val client = RadioClient.Builder()
.transport(TcpTransport(host = "192.168.1.42", port = 4403))
.storage(MyStorageProvider())
.build()

client.connect() // suspends until Connected or throws
client.sendText("Hello mesh!") // non-suspending; returns a MessageHandle

client.packets.collect { packet -> // reactive inbound stream
println(packet.decoded?.portnum)
}

client.disconnect() // graceful shutdown

Multi-radio: instantiate one RadioClient per physical radio. They share nothing.

No host plumbing: foreground services, permissions, notifications, and WorkManager are the app's responsibility. The SDK owns only the engine.

Resource management: call the suspending disconnect when done — typically from the same structured scope that owns the session, e.g. try { … } finally { client.disconnect() }. There is deliberately no blocking close()/AutoCloseable: a blocking bridge invites ANR on Android's main thread and deadlock on iOS main, and a radio session has no non-suspending way to tear down safely.

Since

0.1.0

Types

Link copied to clipboard
class Builder

DSL builder for RadioClient.

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

Admin API for device configuration and control. Available while connected.

Link copied to clipboard
val channels: StateFlow<List<Channel>?>

Channel list for the active session.

Link copied to clipboard
val configBundle: StateFlow<ConfigBundle?>

Most recently committed ConfigBundle for the active session.

Link copied to clipboard
val connection: StateFlow<ConnectionState>

The current connection state.

Link copied to clipboard
val events: Flow<MeshEvent>

Side-channel advisory events: queue status, transport errors, key-verification prompts, drop notifications, and identity-rebind signals (MeshEvent.IdentityRebound — emitted before the engine clears storage when the device reports a different NodeNum than the one previously persisted).

Link copied to clipboard

A typed flow of NeighborInfo domain objects, decoded from inbound NEIGHBORINFO_APP packets.

Link copied to clipboard
val RadioClient.neighborInfoMessages: Flow<MeshPacket>

Filters RadioClient.packets to only neighbor-info packets — those with decoded.portnum == [PortNum.NEIGHBORINFO_APP].

Link copied to clipboard
val nodes: Flow<NodeChange>

Node-change deltas. Every subscriber — early or late — first receives a NodeChange.Snapshot of the engine's current node map (seeded per-subscription via onSubscription), then live NodeChange.Added / NodeChange.Updated / NodeChange.Removed / NodeChange.WentOffline / NodeChange.CameOnline in causal order. A delta whose change is already reflected in the seeded snapshot re-applies idempotently. The handshake additionally emits a live NodeChange.Snapshot at Stage-2 commit (and after each reconnect), which existing subscribers must treat as a full replacement.

Link copied to clipboard
val ownNode: StateFlow<NodeInfo?>

The local node's NodeInfo, populated after handshake completes.

Link copied to clipboard
val packets: Flow<MeshPacket>

Inbound decoded packets.

Link copied to clipboard

Routing API for traceRoute and neighbor enumeration. Available while connected.

Link copied to clipboard

Store-and-Forward API for requesting stored messages. Available while connected.

Link copied to clipboard

Telemetry API for device and environment metrics. Available while connected.

Link copied to clipboard
val RadioClient.textMessages: Flow<MeshPacket>

Filters RadioClient.packets to only text-message packets — those with decoded.portnum == [PortNum.TEXT_MESSAGE_APP].

Functions

Link copied to clipboard
suspend fun RadioClient.awaitInitialNodeDb(): Map<NodeId, NodeInfo>

Suspend until the device finishes streaming its NodeDB during handshake, then return the current node-snapshot map.

Link copied to clipboard
suspend fun connect()

Initiate connection to the device.

Link copied to clipboard
suspend fun RadioClient.connectAndAwaitReady(timeout: Duration = 30.seconds): ConfigBundle

Connect and suspend until the handshake settles, returning the resolved ConfigBundle (HLP-34).

Link copied to clipboard
suspend fun disconnect()

Gracefully disconnect from the device.

Link copied to clipboard
fun RadioClient.nodeMap(): Flow<Map<NodeId, NodeInfo>>

The live node map for this client: RadioClient.nodes folded via asNodeMap.

Link copied to clipboard
suspend fun nodeSnapshot(): Map<NodeId, NodeInfo>

Pull a snapshot of all known nodes on demand.

Link copied to clipboard

Request a remote node to send its NodeInfo (user identity + position).

Link copied to clipboard
fun RadioClient.requestPosition(from: NodeId, channel: ChannelIndex = ChannelIndex(0)): MessageHandle

Request the current Position from node from.

Link copied to clipboard
fun send(packet: MeshPacket): MessageHandle

Enqueue an outbound packet for transmission.

suspend fun send(portnum: PortNum, payload: ByteArray, to: NodeId = NodeId.BROADCAST, channel: ChannelIndex = ChannelIndex(0), wantAck: Boolean = false, hopLimit: Int? = null): MessageHandle

Constructs a packet with decoded = Data(portnum, payload, want_response = false) and forwards to send. Exists so callers do not need to import org.meshtastic.proto.Data to send a typed application payload.

suspend fun send(portnum: PortNum, payload: ByteString, to: NodeId = NodeId.BROADCAST, channel: ChannelIndex = ChannelIndex(0), wantAck: Boolean = false, hopLimit: Int? = null): MessageHandle

Convenience: build a MeshPacket for the given portnum from an okio.ByteString payload.

Link copied to clipboard
suspend fun RadioClient.send(block: SendBuilder.() -> Unit): MessageHandle

DSL form of RadioClient.send. Builds a MeshPacket via SendBuilder and enqueues it.

Link copied to clipboard
suspend fun RadioClient.sendDirectMessage(to: NodeId, text: String, channel: ChannelIndex = ChannelIndex(0), wantAck: Boolean = true): MessageHandle

Send a unicast text message to to. Defaults wantAck to true.

Link copied to clipboard

Send a PKI-encrypted unicast text message to to.

Link copied to clipboard
suspend fun RadioClient.sendPosition(latLng: LatLng, to: NodeId = NodeId.BROADCAST, channel: ChannelIndex = ChannelIndex(0), wantAck: Boolean = false): MessageHandle

Send a Position payload to to (defaults to broadcast).

Link copied to clipboard
fun sendRaw(frame: ToRadio)

Sends a raw ToRadio frame to the device.

Link copied to clipboard
fun sendReaction(emoji: String, to: NodeId = NodeId.BROADCAST, channel: ChannelIndex = ChannelIndex(0), replyId: Int): MessageHandle

Convenience: send an emoji reaction to an existing message.

Link copied to clipboard
fun sendText(text: String, to: NodeId = NodeId.BROADCAST, channel: ChannelIndex = ChannelIndex(0), replyId: Int = 0): MessageHandle

Convenience: send a text message.

Link copied to clipboard
suspend fun <T> RadioClient.withConnection(teardownTimeout: Duration = 10.seconds, block: suspend RadioClient.() -> T): T

Run block against a connected session, guaranteeing teardown.