Android Setup
Prerequisites
- Android Studio Hedgehog or later
minSdk24+,targetSdk34+
Add the SDK
Download ara-sdk.aar from the latest GitHub Release
and place it in your app's libs/ directory:
app/
└── libs/
└── ara-sdk.aar
app/build.gradle.kts
dependencies {
implementation(files("libs/ara-sdk.aar"))
}
Permissions
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<!-- Required for UDP LAN broadcast -->
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
Quick start
import com.aramesh.sdk.v1.Engine
import com.aramesh.sdk.v1.Migration
val migrations = listOf(
Migration(
version = 1,
description = "messages table",
sql = """
CREATE TABLE IF NOT EXISTS messages (
id TEXT PRIMARY KEY,
content TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL DEFAULT 0
) STRICT;
""".trimIndent(),
sync = listOf("messages"),
)
)
val dbPath = filesDir.absolutePath + "/ara.db"
val node = Ara.open(context, dbPath, migrations)
// Write
node.exec(
"INSERT INTO messages (id, content, created_at) VALUES (?, ?, ?)",
listOf("msg-1", "Hello mesh", System.currentTimeMillis()),
)
// Read
val rows = node.query("SELECT id, content FROM messages ORDER BY created_at DESC")
rows.forEach { row ->
println("${row["id"]}: ${row["content"]}")
}
// Close when done (e.g. in ViewModel.onCleared())
node.close()
Add a transport
// UDP LAN (no infrastructure needed)
node.addTransportUDP()
// Meshtastic LoRa via USB serial (requires USB OTG cable)
node.addTransportMeshtastic(portPath = "/dev/ttyUSB0", channel = 0)
// MQTT (requires broker)
node.addTransportMQTT(brokerUrl = "tcp://192.168.1.100:1883", networkId = "my-network")
Meshtastic setup
Requires:
- USB OTG cable (Android device → Meshtastic radio)
- Meshtastic radio flashed with standard firmware
Find serial port
Use the usb-serial-for-android library to discover and connect to the Meshtastic device:
// build.gradle
dependencies {
implementation("com.github.mik3y:usb-serial-for-android:3.6.0")
}
import com.hoho.android.usbserial.driver.UsbSerialProber
// Discover available serial ports
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val drivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
if (drivers.isEmpty()) {
// No serial devices found
return
}
// Use the first available driver
val driver = drivers[0]
val deviceName = driver.device.deviceName // e.g. "/dev/bus/usb/1.1/1.0"
// Request permission if needed
if (manager.openDevice(driver.device) == null) {
val intent = PendingIntent.getBroadcast(this, 0, Intent("com.aramesh.USB_PERMISSION"), 0)
manager.requestPermission(driver.device, intent)
}
// Pass the device path to Ara
node.addTransportMeshtastic(deviceName, 0)
The device name is typically /dev/bus/usb/{bus}.{port}/{device} but can be mapped to /dev/ttyUSB* on some devices.
Message ordering
Sort synced messages by rowid rather than created_at to avoid ordering issues from
clock skew between devices:
val rows = node.query("SELECT * FROM messages ORDER BY rowid ASC LIMIT 200")
rowid increments at local insert time — locally sent messages appear at the bottom as
you send them; synced messages appear at the bottom when they arrive, regardless of the
sender's clock.
ViewModel pattern
See the example app for a
complete AndroidViewModel implementation with coroutines, state flow, and polling.