Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Meshtastic Android
https://github.com/meshtastic/meshtastic-android
Admin
Meshtastic Android is an open-source Android app and Compose Desktop client that enables mesh radio
...
Tokens:
46,264
Snippets:
458
Trust Score:
8.5
Update:
2 weeks ago
Context
Skills
Chat
Benchmark
85.9
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Meshtastic Android Meshtastic Android is an open-source Kotlin Multiplatform (KMP) application that provides a companion app for Meshtastic mesh radios. It enables off-grid communication through LoRa-based mesh networking, allowing users to send text messages, share locations, and exchange data without cellular or internet connectivity. The app supports Android devices, desktop platforms (Windows, macOS, Linux), and is architected for future iOS support. The core functionality includes connecting to Meshtastic radios via Bluetooth Low Energy (BLE), TCP/IP, Serial/USB, and MQTT protocols. The app manages the mesh network node database, handles message routing and delivery confirmation, provides real-time telemetry from connected devices, and offers configuration management for radio settings. The modular architecture separates concerns into core modules (service, model, network, BLE, repository) and feature modules (messaging, map, settings, nodes, connections). ## IMeshService AIDL Interface The primary Android API for external applications to interact with Meshtastic radios. Third-party apps can bind to this service to send messages, receive broadcasts, and manage radio configuration. ```kotlin // Binding to the Meshtastic service from an external Android app val intent = Intent().apply { setClassName( "com.geeksville.mesh", "org.meshtastic.core.service.MeshService" ) } // Alternative using action string val intent = Intent("com.geeksville.mesh.Service") // Bind to service context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) // ServiceConnection implementation private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, binder: IBinder) { val meshService = IMeshService.Stub.asInterface(binder) // Get local node ID val myId = meshService.myId // e.g., "!1234abcd" // Get all nodes in the mesh val nodes: List<NodeInfo> = meshService.nodes // Get local node hardware info val myNodeInfo: MyNodeInfo? = meshService.myNodeInfo // Check connection state val state = meshService.connectionState() // "Connected", "Disconnected", etc. } override fun onServiceDisconnected(name: ComponentName?) { // Handle disconnection } } ``` ## DataPacket - Message Data Model The core data structure for sending and receiving messages over the mesh network. Supports text messages, waypoints, and custom application data with delivery status tracking. ```kotlin import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.MessageStatus import org.meshtastic.proto.PortNum // Create a text message to broadcast on channel 0 val broadcastMessage = DataPacket( to = DataPacket.ID_BROADCAST, // "^all" for broadcast channel = 0, text = "Hello mesh network!" ) // Create a direct message to a specific node val directMessage = DataPacket( to = "!1234abcd", // Node ID in hex format channel = 0, text = "Private message", replyId = null // Optional: ID of message being replied to ) // Create a message with custom binary data val customPacket = DataPacket( to = "!1234abcd", bytes = customPayload.toByteString(), dataType = PortNum.PRIVATE_APP.value, // Custom app port number channel = 0, wantAck = true, hopLimit = 3 ) // Send via IMeshService meshService.send(directMessage) // Check message status after sending when (directMessage.status) { MessageStatus.QUEUED -> println("Waiting to send") MessageStatus.ENROUTE -> println("Sent, awaiting ACK") MessageStatus.DELIVERED -> println("Delivered successfully") MessageStatus.ERROR -> println("Delivery failed: ${directMessage.errorMessage}") else -> {} } // Access message properties val hopsAway = directMessage.hopsAway // Number of hops the message traveled val signalQuality = directMessage.snr // Signal-to-noise ratio val rssi = directMessage.rssi // Received signal strength ``` ## MeshtasticIntent - Broadcast Actions Constants for Android broadcast intents that notify external apps about mesh events. Register receivers to handle incoming messages, node changes, and connection status updates. ```kotlin import org.meshtastic.core.api.MeshtasticIntent // Register broadcast receivers for mesh events val filter = IntentFilter().apply { addAction(MeshtasticIntent.ACTION_RECEIVED_TEXT_MESSAGE_APP) addAction(MeshtasticIntent.ACTION_NODE_CHANGE) addAction(MeshtasticIntent.ACTION_MESH_CONNECTED) addAction(MeshtasticIntent.ACTION_MESH_DISCONNECTED) addAction(MeshtasticIntent.ACTION_MESSAGE_STATUS) } val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { MeshtasticIntent.ACTION_RECEIVED_TEXT_MESSAGE_APP -> { val payload = intent.getParcelableExtra<DataPacket>(MeshtasticIntent.EXTRA_PAYLOAD) println("Received: ${payload?.text} from ${payload?.from}") } MeshtasticIntent.ACTION_NODE_CHANGE -> { val nodeInfo = intent.getParcelableExtra<NodeInfo>(MeshtasticIntent.EXTRA_NODEINFO) println("Node updated: ${nodeInfo?.user?.longName}") } MeshtasticIntent.ACTION_MESSAGE_STATUS -> { val packetId = intent.getIntExtra(MeshtasticIntent.EXTRA_PACKET_ID, 0) val status = intent.getStringExtra(MeshtasticIntent.EXTRA_STATUS) println("Message $packetId status: $status") } MeshtasticIntent.ACTION_MESH_CONNECTED -> { println("Radio connected") } } } } context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED) ``` ## RadioInterfaceService - Transport Abstraction The low-level interface for managing radio communication. Handles raw byte transport over BLE, TCP, Serial, or MQTT connections with reactive state management. ```kotlin import org.meshtastic.core.repository.RadioInterfaceService import org.meshtastic.core.model.ConnectionState import kotlinx.coroutines.flow.collect class MeshConnection( private val radioService: RadioInterfaceService ) { // Observe connection state reactively suspend fun observeConnection() { radioService.connectionState.collect { state -> when (state) { is ConnectionState.Connected -> println("Connected to radio") is ConnectionState.Connecting -> println("Connecting...") is ConnectionState.Disconnected -> println("Disconnected") is ConnectionState.DeviceSleep -> println("Device in sleep mode") } } } // Observe received data packets suspend fun observeReceivedData() { radioService.receivedData.collect { bytes -> // Process raw protobuf bytes from radio val fromRadio = FromRadio.ADAPTER.decode(bytes) println("Received ${bytes.size} bytes from radio") } } // Set device address (BLE MAC, IP:port, serial path) fun connectToDevice(address: String) { // BLE: "AA:BB:CC:DD:EE:FF" // TCP: "192.168.1.100:4403" // Serial: "/dev/ttyUSB0" radioService.setDeviceAddress(address) radioService.connect() } // Send raw data to radio fun sendToRadio(packet: ByteArray) { radioService.sendToRadio(packet) } // Check supported transport types val supportedTypes = radioService.supportedDeviceTypes // Returns: [DeviceType.BLE, DeviceType.TCP, DeviceType.SERIAL] } ``` ## BleRadioInterface - Bluetooth Low Energy Transport Handles BLE connections to Meshtastic radios using the Kable library for cross-platform compatibility. Manages device discovery, bonding, automatic reconnection, and MTU negotiation. ```kotlin import org.meshtastic.core.network.radio.BleRadioInterface import org.meshtastic.core.ble.BleScanner import org.meshtastic.core.ble.BleConnectionFactory import org.meshtastic.core.ble.MeshtasticBleConstants import kotlin.time.Duration.Companion.seconds // Scan for Meshtastic BLE devices class BluetoothScanner( private val scanner: BleScanner ) { suspend fun findDevices() { scanner.scan( timeout = 10.seconds, serviceUuid = MeshtasticBleConstants.SERVICE_UUID, address = null // or specific MAC address ).collect { device -> println("Found device: ${device.name} at ${device.address}") // device.address format: "AA:BB:CC:DD:EE:FF" } } } // Create BLE radio interface (internal usage) val bleInterface = BleRadioInterface( serviceScope = coroutineScope, scanner = bleScanner, bluetoothRepository = bluetoothRepository, connectionFactory = connectionFactory, service = radioInterfaceService, address = "AA:BB:CC:DD:EE:FF" ) // Interface automatically connects on init and handles: // - Device bonding and discovery // - Automatic reconnection on disconnect // - MTU negotiation for optimal packet sizes // - fromRadio and logRadio characteristic subscriptions // Send packet to radio bleInterface.handleSendToRadio(packetBytes) // Close connection when done bleInterface.close() ``` ## TCPInterface - Network Transport TCP/IP transport for connecting to Meshtastic radios over WiFi or wired network connections. Uses the shared StreamFrameCodec for packet framing. ```kotlin import org.meshtastic.core.network.radio.TCPInterface import org.meshtastic.core.network.transport.StreamFrameCodec // Default Meshtastic TCP port val TCP_PORT = StreamFrameCodec.DEFAULT_TCP_PORT // 4403 // Connect via TCP (internal usage) val tcpInterface = TCPInterface( service = radioInterfaceService, dispatchers = coroutineDispatchers, address = "192.168.1.100:4403" // or "hostname:port" ) // Send packet tcpInterface.handleSendToRadio(packetBytes) // TCP interface features: // - Automatic heartbeat to maintain connection // - Reconnection on disconnect // - Shared StreamFrameCodec for packet framing // Close connection tcpInterface.close() ``` ## NodeRepository - Mesh Node Management Repository interface for accessing and managing the mesh network's node database. Provides reactive flows for node lists, filtering, and local device information. ```kotlin import org.meshtastic.core.repository.NodeRepository import org.meshtastic.core.model.NodeSortOption import kotlinx.coroutines.flow.collect class NodeManager(private val nodeRepository: NodeRepository) { // Get our local node info suspend fun getLocalInfo() { val myNodeInfo = nodeRepository.myNodeInfo.value println("Local node: ${myNodeInfo?.myNodeNum}") val ourNode = nodeRepository.ourNodeInfo.value println("User: ${ourNode?.user?.longName}") println("Position: ${ourNode?.position?.latitude}, ${ourNode?.position?.longitude}") } // Observe all nodes with filtering and sorting suspend fun observeNodes() { nodeRepository.getNodes( sort = NodeSortOption.LAST_HEARD, filter = "", // Search string includeUnknown = true, onlyOnline = false, onlyDirect = false // Only nodes 0 hops away ).collect { nodes -> nodes.forEach { node -> println(""" Node: ${node.user?.longName} ID: ${node.user?.id} Hardware: ${node.user?.hwModelString} Online: ${node.isOnline} Last heard: ${node.lastHeard} Hops away: ${node.hopsAway} Battery: ${node.batteryStr} Position: ${node.validPosition?.latitude}, ${node.validPosition?.longitude} """.trimIndent()) } } } // Get specific node by ID fun getNode(userId: String) = nodeRepository.getNode(userId) // Get online/total counts reactively suspend fun observeCounts() { nodeRepository.onlineNodeCount.collect { online -> println("Online nodes: $online") } nodeRepository.totalNodeCount.collect { total -> println("Total nodes: $total") } } // Delete nodes suspend fun cleanupOldNodes() { val oldNodes = nodeRepository.getNodesOlderThan(lastHeard = 1609459200) nodeRepository.deleteNodes(oldNodes.map { it.num }) } } ``` ## RadioController - High-Level Radio Control The central interface for controlling the radio and mesh network operations. Abstracts transport details and provides methods for configuration, messaging, and device management. ```kotlin import org.meshtastic.core.model.RadioController import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.Position class MeshController(private val radioController: RadioController) { // Observe connection state suspend fun monitorConnection() { radioController.connectionState.collect { state -> println("Connection: $state") } } // Send a message suspend fun sendMessage(text: String, destination: String, channel: Int = 0) { val packet = DataPacket( to = destination, channel = channel, text = text ) radioController.sendMessage(packet) } // Device management suspend fun manageDevice(nodeNum: Int) { val requestId = radioController.getPacketId() // Request position from remote node radioController.requestPosition(nodeNum, Position(0.0, 0.0, 0)) // Request telemetry data radioController.requestTelemetry(requestId, nodeNum, typeValue = 1) // Request traceroute radioController.requestTraceroute(requestId, nodeNum) // Request neighbor info radioController.requestNeighborInfo(requestId, nodeNum) // Reboot device radioController.reboot(nodeNum, requestId) // Factory reset (use with caution!) radioController.factoryReset(nodeNum, requestId) } // Configuration management suspend fun configureRemoteNode(nodeNum: Int) { val requestId = radioController.getPacketId() // Begin batch edit session radioController.beginEditSettings(nodeNum) // Get current config radioController.getConfig(nodeNum, configType = 1, requestId) // Set configuration radioController.setConfig(nodeNum, config, requestId) // Set module configuration radioController.setModuleConfig(nodeNum, moduleConfig, requestId) // Commit changes radioController.commitEditSettings(nodeNum) } // Location sharing fun startLocationSharing() { radioController.startProvideLocation() } fun stopLocationSharing() { radioController.stopProvideLocation() } // Change connected device fun switchDevice(address: String) { radioController.setDeviceAddress(address) } } ``` ## SendMessageUseCase - Message Workflow Use case that orchestrates the complete message sending workflow including contact resolution, homoglyph encoding, database persistence, and durable delivery queue. ```kotlin import org.meshtastic.core.repository.usecase.SendMessageUseCase import org.meshtastic.core.model.DataPacket class MessagingService(private val sendMessageUseCase: SendMessageUseCase) { // Send broadcast message on channel 0 suspend fun sendBroadcast(text: String) { sendMessageUseCase.invoke( text = text, contactKey = "0${DataPacket.ID_BROADCAST}", // "0^all" replyId = null ) } // Send direct message to specific node suspend fun sendDirectMessage(text: String, nodeId: String) { // Contact key format: no channel prefix for DMs sendMessageUseCase.invoke( text = text, contactKey = nodeId, // e.g., "!1234abcd" replyId = null ) } // Send message on specific channel suspend fun sendChannelMessage(text: String, channelIndex: Int) { sendMessageUseCase.invoke( text = text, contactKey = "$channelIndex${DataPacket.ID_BROADCAST}", replyId = null ) } // Reply to a message suspend fun replyToMessage(text: String, contactKey: String, originalMessageId: Int) { sendMessageUseCase.invoke( text = text, contactKey = contactKey, replyId = originalMessageId ) } } ``` ## Channel and ChannelSet - Network Configuration Data models for managing mesh network channels including encryption keys, LoRa settings, and URL-based channel sharing. ```kotlin import org.meshtastic.core.model.Channel import org.meshtastic.core.model.util.toChannelSet import org.meshtastic.core.model.util.getChannelUrl import org.meshtastic.core.model.util.primaryChannel import org.meshtastic.core.common.util.CommonUri import org.meshtastic.proto.ChannelSettings import org.meshtastic.proto.Config.LoRaConfig.ModemPreset // Parse channel from Meshtastic URL val url = CommonUri.parse("https://meshtastic.org/e/#CgMSAQ") val channelSet = url.toChannelSet() // Access primary channel val primaryChannel = channelSet.primaryChannel println("Channel name: ${primaryChannel?.name}") // e.g., "LongFast" println("PSK: ${primaryChannel?.psk}") println("Radio frequency: ${primaryChannel?.radioFreq}") // Create channel URL for sharing (QR code friendly) val shareUrl = channelSet.getChannelUrl( upperCasePrefix = true, // More efficient QR encoding shouldAdd = false // Replace vs add to existing channels ) println("Share URL: $shareUrl") // Create custom channel val customChannel = Channel( settings = ChannelSettings( name = "MyChannel", psk = Channel.getRandomKey(32) // 256-bit AES key ), loraConfig = org.meshtastic.proto.Config.LoRaConfig( use_preset = true, modem_preset = ModemPreset.LONG_FAST, hop_limit = 3, tx_enabled = true ) ) // Channel hash for routing val channelHash = customChannel.hash ``` ## MQTTRepository - MQTT Bridge Interface for MQTT connectivity enabling mesh-to-internet bridging. Allows messages to be relayed through MQTT brokers for extended range communication. ```kotlin import org.meshtastic.core.network.repository.MQTTRepository import org.meshtastic.proto.MqttClientProxyMessage import kotlinx.coroutines.flow.collect class MqttBridge(private val mqttRepository: MQTTRepository) { // Subscribe to incoming MQTT messages suspend fun listenForMessages() { mqttRepository.proxyMessageFlow.collect { message -> // Process incoming mesh packets from MQTT println("Received MQTT message on topic: ${message.topic}") println("Payload size: ${message.data.size} bytes") } } // Publish message to MQTT fun publishMessage(topic: String, payload: ByteArray, retained: Boolean = false) { mqttRepository.publish( topic = topic, // e.g., "msh/US/2/e/LongFast/!12345678" data = payload, retained = retained ) } // Disconnect from MQTT broker fun disconnect() { mqttRepository.disconnect() } } ``` ## MessageViewModel - UI State Management ViewModel for the messaging feature providing reactive message lists, contact management, and message operations with proper lifecycle handling. ```kotlin import org.meshtastic.feature.messaging.MessageViewModel import org.meshtastic.core.model.DataPacket import kotlinx.coroutines.flow.collect class MessageScreen(private val viewModel: MessageViewModel) { // Observe connection state suspend fun observeConnection() { viewModel.connectionState.collect { state -> updateConnectionUI(state) } } // Get paged messages for a contact (Android with Paging3) fun getPagedMessages(contactKey: String) = viewModel.getMessagesFromPaged(contactKey) // Get non-paged messages (Desktop) suspend fun getMessages(contactKey: String) { viewModel.getMessagesFlow(contactKey, limit = 100).collect { messages -> messages.forEach { message -> println("${message.user.longName}: ${message.text}") } } } // Send message fun sendMessage(text: String, contactKey: String, replyId: Int? = null) { viewModel.sendMessage(text, contactKey, replyId) } // Send reaction emoji fun sendReaction(emoji: String, messageId: Int, contactKey: String) { viewModel.sendReaction(emoji, messageId, contactKey) } // Delete messages fun deleteMessages(messageIds: List<Long>) { viewModel.deleteMessages(messageIds) } // Mark messages as read fun markAsRead(contactKey: String, lastMessageUuid: Long, timestamp: Long) { viewModel.clearUnreadCount(contactKey, lastMessageUuid, timestamp) } // Get node info for a user fun getNodeInfo(userId: String) = viewModel.getNode(userId) // Toggle quick chat panel fun toggleQuickChat() = viewModel.toggleShowQuickChat() // Observe unread counts suspend fun observeUnread() { viewModel.unreadCount.collect { count -> updateUnreadBadge(count) } } } ``` ## NodeInfo and Position - Node Data Models Data classes representing mesh network nodes with user information, position, telemetry, and status tracking. ```kotlin import org.meshtastic.core.model.NodeInfo import org.meshtastic.core.model.MeshUser import org.meshtastic.core.model.Position import org.meshtastic.core.model.DeviceMetrics import org.meshtastic.proto.HardwareModel // NodeInfo provides comprehensive node data fun processNode(node: NodeInfo) { // User information val user = node.user println("Name: ${user?.longName}") println("Short name: ${user?.shortName}") println("ID: ${user?.id}") // e.g., "!1234abcd" println("Hardware: ${user?.hwModelString}") // e.g., "tbeam-1.1" println("Role: ${user?.role}") println("Licensed: ${user?.isLicensed}") // Position data val position = node.validPosition if (position != null) { println("Latitude: ${position.latitude}") println("Longitude: ${position.longitude}") println("Altitude: ${position.altitude}m") println("Satellites: ${position.satellitesInView}") println("Speed: ${position.groundSpeed} m/s") println("Heading: ${position.groundTrack}°") } // Device metrics val metrics = node.deviceMetrics println("Battery: ${node.batteryStr}") // e.g., "85%" println("Voltage: ${metrics?.voltage}V") println("Channel util: ${metrics?.channelUtilization}%") println("Air util TX: ${metrics?.airUtilTx}%") println("Uptime: ${metrics?.uptimeSeconds}s") // Environment metrics val env = node.environmentMetrics println("Temperature: ${env?.temperature}°C") println("Humidity: ${env?.relativeHumidity}%") println("Pressure: ${env?.barometricPressure} hPa") // Connection quality println("SNR: ${node.snr} dB") println("RSSI: ${node.rssi} dBm") println("Hops away: ${node.hopsAway}") println("Online: ${node.isOnline}") println("Last heard: ${node.lastHeard}") // Visual styling (for UI) val (foreground, background) = node.colors // Color pair based on node ID } // Calculate distance and bearing between nodes fun calculateDistance(myNode: NodeInfo, otherNode: NodeInfo) { val distanceMeters = myNode.distance(otherNode) val bearingDegrees = myNode.bearing(otherNode) // Get formatted distance string val distanceStr = myNode.distanceStr( otherNode, prefUnits = 0 // 0=Metric, 1=Imperial ) println("Distance: $distanceStr, Bearing: $bearingDegrees°") } ``` Meshtastic Android provides a comprehensive platform for building mesh networking applications. The primary integration patterns include binding to the IMeshService for Android apps, using MeshtasticIntent broadcasts for event-driven architectures, and leveraging the RadioController interface for direct radio control. External applications can send and receive messages, monitor node status, and configure radio settings through these well-defined APIs. For developers building on the Meshtastic platform, the recommended approach is to use the high-level SendMessageUseCase and RadioController interfaces which handle the complexities of message queuing, delivery confirmation, and transport abstraction. The underlying RadioTransport implementations (BLE, TCP, Serial, MQTT) are managed automatically based on the device address format, allowing applications to focus on business logic rather than connection management. The reactive StateFlow and SharedFlow patterns throughout the codebase enable efficient UI updates and background processing in both Android and desktop environments.