Skip to content

meshtastic/meshtastic-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

meshtastic-sdk

Kotlin Multiplatform SDK for Meshtastic mesh-network radios. One library. Connects to Meshtastic devices over BLE, TCP, or USB-serial from Android, JVM, and iOS. Wasm/browser is on the roadmap — see docs/future/wasm-rpc-roadmap.md.

License: GPL-3.0-only Maven Central CI API Docs

📚 API Reference (Dokka) — published from main.

Status: pre-1.0. APIs may change between minor versions; see docs/versioning.md. Track docs/ for spec evolution.

What this is

A Kotlin library that talks to Meshtastic radios using the device's PhoneAPI (the same protocol the official Meshtastic-Android and Meshtastic-Apple apps use). It owns the wire-protocol details — handshake, NodeDB, ACK correlation, channel decryption, deferred-decrypt, retries — and exposes them as ergonomic Kotlin coroutines + flows + sealed types.

Use it to build:

  • Companion apps (Android, iOS, desktop) that don't want to reinvent the protocol.
  • Headless gateways and mesh telemetry collectors on JVM/server.
  • (post-1.0) Web tools (wasm) via a sidecar RPC server — design parked in docs/future/wasm-rpc-roadmap.md.

This SDK does not include UI components, navigation, or storage policy — see docs/decisions/000-charter.md for the explicit non-goals.

Install

// build.gradle.kts
dependencies {
    implementation("org.meshtastic:sdk-core:0.1.0")
    implementation("org.meshtastic:sdk-transport-tcp:0.1.0")     // pick a transport
    implementation("org.meshtastic:sdk-storage-sqldelight:0.1.0") // pick a storage
}

Heads up: 0.1.0 is not yet on Maven Central — the first release tag has not been cut. Until then, use the snapshot repository below.

Available transport modules: transport-ble, transport-tcp, transport-serial (single multiplatform module covering JVM and Android). Available storage modules: storage-sqldelight. Or roll your own StorageProvider. Optionally, depend on sdk-bom to align all module versions; see bom/README.md for usage and docs/versioning.md for the versioning policy.

Snapshot artifacts

Every push to main publishes 0.1.0-SNAPSHOT (and successor versions) to the Sonatype Central snapshot repository:

// settings.gradle.kts (or root build.gradle.kts repositories block)
dependencyResolutionManagement {
    repositories {
        mavenCentral()
        maven("https://central.sonatype.com/repository/maven-snapshots/") {
            mavenContent { snapshotsOnly() }
        }
    }
}

// build.gradle.kts
dependencies {
    implementation("org.meshtastic:sdk-core:0.1.0-SNAPSHOT")
}

Snapshots are rebuilt on every commit; pin to a specific commit by checking out a Git submodule for reproducibility.

Roadmap (post-1.0, non-breaking adds): transport-mqtt-proxy, transport-rpc, host-rpc-server, wasmJs browser support — see docs/future/wasm-rpc-roadmap.md.

Full module matrix: docs/architecture/module-graph.md.

Quick start

import org.meshtastic.sdk.storage.sqldelight.SqlDelightStorageProvider
import org.meshtastic.sdk.RadioClient
import org.meshtastic.sdk.SendOutcome
import org.meshtastic.sdk.transport.tcp.TcpTransport

suspend fun run() {
    val client = RadioClient.Builder()
        .transport(TcpTransport(host = "meshtastic.local", port = 4403))
        .storage(SqlDelightStorageProvider(baseDir = "/tmp"))   // empty string = in-memory
        .build()

    client.connect()                                   // throws MeshtasticException on failure

    val handle = client.sendText("hello mesh")
    when (val outcome = handle.await()) {              // suspends until terminal
        SendOutcome.Success    -> println("acked or rebroadcast heard")
        is SendOutcome.Failure -> println("failed: ${outcome.reason}")
    }
    // Or observe progress:
    //   handle.state.collect { println(it) }   // Queued → Sent → Acked/Delivered/Failed
}

To observe NodeDB changes alongside sending, run the collector in its own coroutine so it doesn't block the rest of your flow. client.nodes never completes — collect it from a launch { … } (or use take(N) for a bounded sample):

import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.meshtastic.sdk.NodeChange
import org.meshtastic.sdk.RadioClient

suspend fun observeAndSend(client: RadioClient) = coroutineScope {
    val nodesJob = launch {
        client.nodes.collect { change ->
            when (change) {
                is NodeChange.Snapshot -> println("seeded with ${change.nodes.size} nodes")
                is NodeChange.Added    -> println("+ ${change.node.user?.long_name}")
                is NodeChange.Updated  -> println("~ ${change.node.user?.long_name} (${change.changed})")
                is NodeChange.Removed  -> println("- ${change.nodeId}")
            }
        }
    }

    client.sendText("hello mesh").await()  // not blocked by the collector
    nodesJob.cancel()                      // stop observing when you're done
}

Notes:

  • Wire-generated proto fields are snake_case (e.g. user.long_name, not user.longName).
  • On Android, also set AndroidContextHolder.context = applicationContext once in your Application.onCreate() before constructing a SqlDelightStorageProvider — see the integration guide.
  • For Android BLE, USB-serial setup, and SqlDelightStorageProvider Android Context wiring, see the integration guide.

Picking a transport

The example above wires TcpTransport. The other two transports plug in identically — only the Builder.transport(...) line changes. Each ships in its own artifact; pull in the one(s) you need.

// BLE — multiplatform (Android / iOS / JVM-desktop via Kable).
// Android: see AndroidManifest checklist + permission notes in the integration guide.
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import com.juul.kable.Peripheral
import com.juul.kable.Scanner
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.meshtastic.sdk.transport.ble.BleTransport
import org.meshtastic.sdk.storage.sqldelight.SqlDelightStorageProvider
import org.meshtastic.sdk.RadioClient

class BleQuickStartActivity : ComponentActivity() {
    private val permissions = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions(),
    ) { granted -> if (granted.values.all { it }) lifecycleScope.launch { connect() } }

    override fun onStart() {
        super.onStart()
        permissions.launch(arrayOf(
            android.Manifest.permission.BLUETOOTH_SCAN,
            android.Manifest.permission.BLUETOOTH_CONNECT,
        ))
    }

    private suspend fun connect() {
        // Filter by the Meshtastic GATT service UUID in real code.
        val ad = Scanner().advertisements.first()
        val client = RadioClient.Builder()
            .transport(BleTransport(Peripheral(ad), address = ad.identifier.toString()))
            .storage(SqlDelightStorageProvider(baseDir = filesDir.absolutePath))
            .build()
        client.connect()
        client.events.collect { println(it) }
    }
}

Full BLE setup (manifest entries, foreground service for API 34+, iOS Info.plist keys, JVM/desktop notes) is in the Android setup checklist and the per-transport BLE section of the integration guide.

If your first send doesn't work, check

Symptom Likely cause Fix
connect() throws MeshtasticException.TransportFailure on TCP Host unreachable or firmware TCP API disabled Verify the radio's IP/hostname and that WiFi/Ethernet is enabled. See TCP setup.
connect() hangs or fails repeatedly on BLE Device not bonded with the OS Pair the radio in your OS Bluetooth settings before connect(). See BLE platform requirements.
JvmSerialPorts.open(...) throws permission denied Serial device permission not granted On Linux, add the user to dialout. On Android, request USB permission first. See Serial (USB).
sendText returns SendFailure immediately on long messages Payload exceeds the 228-byte text limit Split the text or send a smaller payload. See Sending messages.
connect() throws MeshtasticException.HandshakeTimeout want_config_id reply never arrived from the device Power-cycle the radio, verify firmware ≥ 2.3, then retry. See Build a RadioClient.
// USB-serial — JVM (jSerialComm) and Android (usb-serial-for-android)
import org.meshtastic.sdk.transport.serial.JvmSerialPorts            // androidMain: AndroidSerialPorts

val portName  = JvmSerialPorts.list().first()           // e.g. "/dev/tty.usbserial-1410"
val transport = JvmSerialPorts.open(portName, baudRate = 115200)
// TCP / WiFi — all targets
import org.meshtastic.sdk.transport.tcp.TcpTransport

val transport = TcpTransport(host = "meshtastic.local", port = 4403)

Full per-platform setup (Android runtime permissions, foreground-service requirements on API 34+, USB intent filters, iOS Info.plist keys) is in the integration guide. For lifecycle, DI, and R8 patterns see the consumer guides index.

Full API reference: docs/api-reference.md.

Targets

All published modules target the same Kotlin Multiplatform target set via the shared meshtastic.kmp.library convention plugin: jvm, androidTarget (minSdk = 26), iosArm64, iosX64, and iosSimulatorArm64. Per-module behaviour is summarised below; see docs/architecture/module-graph.md for the authoritative dependency graph.

Platform support matrix

Module JVM Android (minSdk 26) iOS Arm64 iOS Sim Arm64 iOS X64 Notes
core Pure-Kotlin engine; no platform deps beyond :proto.
proto Generated kotlinx.serialization proto types.
transport-ble ✓¹ ¹ JVM uses BlueZ/BleZ-style adapter where available; see module README.
transport-tcp Built on kotlinx-io sockets.
transport-serial iOS targets compile (empty actuals) but no USB-serial API on iOS.
storage-sqldelight SQLDelight native driver on iOS, JDBC on JVM/Android.
testing In-memory fakes + TestClock; safe to use in commonTest.

✓ = supported and exercised in CI; — = not applicable (no platform API for this transport).

wasmJs (browser) remains on the post-1.0 roadmap — see docs/future/wasm-rpc-roadmap.md.

Documentation

Building from source

git clone --recurse-submodules git@github.com:meshtastic/meshtastic-sdk.git
cd meshtastic-sdk
./gradlew check                      # build + test + lint + checkKotlinAbi + detekt + :core:verifyModuleBoundary

Requirements:

  • JDK 21 (sdk install java 21-tem).
  • Android SDK with API 35 platform if building Android targets (set ANDROID_HOME).
  • Xcode + iOS 14+ SDK if building iOS targets (mac only).

Runtime requirements (consumers)

  • Android API 26+ (Android 8.0 Oreo). All sdk-* Android artifacts pin minSdk = 26. Apps with minSdk < 26 will fail to resolve the dependency at build time, and reflection-based loaders on older OS versions will fail at runtime — there is no graceful fallback. See the Android setup checklist.
  • iOS 14+ for the BLE/TCP transports.
  • JDK 17+ runtime for JVM consumers (bytecode is JDK 17, toolchain is JDK 21).

Local commands: see docs/ci-cd.md.

Contributing

We use the Developer Certificate of Origin (DCO). Sign every commit:

git commit -s -m "Your message"

See CONTRIBUTING.md for the full flow.

License

GPL-3.0-only. See LICENSE and docs/decisions/004-licensing.md.

Related Meshtastic projects

About

Kotlin Multiplatform SDK for Meshtastic mesh radios. Connect over BLE, TCP, or USB-serial from Android, JVM, and iOS. Handles the PhoneAPI wire protocol — handshake, NodeDB, ACK correlation, retries — and exposes it as coroutines, Flows, and sealed types.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages