mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(example): Add packet log and UI improvements (#4455)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
c44d2f3268
commit
f1520eb383
6 changed files with 258 additions and 97 deletions
|
|
@ -31,7 +31,14 @@ import androidx.activity.ComponentActivity
|
|||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import org.meshtastic.core.service.IMeshService
|
||||
|
||||
private const val TAG: String = "MeshServiceExample"
|
||||
|
|
@ -63,6 +70,7 @@ class MainActivity : ComponentActivity() {
|
|||
private val meshtasticReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
Log.d(TAG, "BroadcastReceiver onReceive: ${intent?.action}")
|
||||
intent?.let { viewModel.handleIncomingIntent(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -81,16 +89,19 @@ class MainActivity : ComponentActivity() {
|
|||
addAction("com.geeksville.mesh.MESH_DISCONNECTED")
|
||||
addAction("com.geeksville.mesh.MESSAGE_STATUS")
|
||||
addAction("com.geeksville.mesh.RECEIVED.TEXT_MESSAGE_APP")
|
||||
addAction("com.geeksville.mesh.RECEIVED.POSITION_APP")
|
||||
addAction("com.geeksville.mesh.RECEIVED.TELEMETRY_APP")
|
||||
addAction("com.geeksville.mesh.RECEIVED.NODEINFO_APP")
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(meshtasticReceiver, intentFilter, RECEIVER_NOT_EXPORTED)
|
||||
registerReceiver(meshtasticReceiver, intentFilter, RECEIVER_EXPORTED)
|
||||
} else {
|
||||
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||
registerReceiver(meshtasticReceiver, intentFilter)
|
||||
}
|
||||
|
||||
setContent { MaterialTheme { MainScreen(viewModel) } }
|
||||
setContent { ExampleTheme { MainScreen(viewModel) } }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
@ -104,8 +115,6 @@ class MainActivity : ComponentActivity() {
|
|||
Log.i(TAG, "Attempting to bind to Mesh Service...")
|
||||
val intent = Intent("com.geeksville.mesh.Service")
|
||||
|
||||
// Query the package manager to find an app that handles this service action.
|
||||
// This is more resilient than hardcoding a package name, which might change with flavors.
|
||||
val resolveInfo =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.queryIntentServices(intent, PackageManager.ResolveInfoFlags.of(0))
|
||||
|
|
@ -144,3 +153,18 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExampleTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
|
||||
val colorScheme =
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
darkTheme -> darkColorScheme()
|
||||
else -> lightColorScheme()
|
||||
}
|
||||
|
||||
MaterialTheme(colorScheme = colorScheme, content = content)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
package com.meshtastic.android.meshserviceexample
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -28,16 +29,22 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.BatteryUnknown
|
||||
import androidx.compose.material.icons.automirrored.rounded.Message
|
||||
import androidx.compose.material.icons.automirrored.rounded.Send
|
||||
import androidx.compose.material.icons.rounded.AccountCircle
|
||||
import androidx.compose.material.icons.rounded.ExpandLess
|
||||
import androidx.compose.material.icons.rounded.ExpandMore
|
||||
import androidx.compose.material.icons.rounded.GpsFixed
|
||||
import androidx.compose.material.icons.rounded.GpsOff
|
||||
import androidx.compose.material.icons.rounded.Hub
|
||||
|
|
@ -77,6 +84,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -113,6 +121,27 @@ fun TitledCard(title: String, content: @Composable () -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SectionHeader(title: String, expanded: Boolean, onExpandClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth().clickable { onExpandClick() },
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(text = title, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Icon(
|
||||
imageVector = if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
|
||||
contentDescription = if (expanded) "Collapse" else "Expand",
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainScreen(viewModel: MeshServiceViewModel) {
|
||||
|
|
@ -177,6 +206,7 @@ private fun TopBarTitle(isConnected: Boolean, connectionState: String) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
private fun MainContent(
|
||||
viewModel: MeshServiceViewModel,
|
||||
innerPadding: PaddingValues,
|
||||
|
|
@ -186,6 +216,11 @@ private fun MainContent(
|
|||
val myId by viewModel.myId.collectAsState()
|
||||
val nodes by viewModel.nodes.collectAsState()
|
||||
val lastMessage by viewModel.message.collectAsState()
|
||||
val packetLog by viewModel.packetLog.collectAsState()
|
||||
|
||||
var nodesExpanded by remember { mutableStateOf(false) }
|
||||
var logExpanded by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize(),
|
||||
|
|
@ -194,20 +229,98 @@ private fun MainContent(
|
|||
) {
|
||||
item { MyInfoSection(myId, myNodeInfo) }
|
||||
item { TitledCard(title = "Messaging") { MessagingSection(viewModel, lastMessage) } }
|
||||
if (nodes.isNotEmpty()) {
|
||||
item {
|
||||
TitledCard(title = "Mesh Nodes (${nodes.size})") {
|
||||
NodeListContent(nodes, viewModel, snackbarHostState)
|
||||
|
||||
item {
|
||||
SectionHeader(
|
||||
title = "Mesh Nodes (${nodes.size})",
|
||||
expanded = nodesExpanded,
|
||||
onExpandClick = { nodesExpanded = !nodesExpanded },
|
||||
)
|
||||
}
|
||||
|
||||
if (nodesExpanded) {
|
||||
if (nodes.isEmpty()) {
|
||||
item { EmptyNodeState() }
|
||||
} else {
|
||||
items(nodes) { node ->
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
val nodeLabel = node.user?.longName ?: node.user?.id ?: "Unknown Node"
|
||||
NodeItem(node) { action ->
|
||||
scope.launch {
|
||||
when (action) {
|
||||
"traceroute" -> {
|
||||
viewModel.requestTraceroute(node.num)
|
||||
snackbarHostState.showSnackbar("Traceroute requested for $nodeLabel")
|
||||
}
|
||||
"telemetry" -> {
|
||||
viewModel.requestTelemetry(node.num)
|
||||
snackbarHostState.showSnackbar("Telemetry requested for $nodeLabel")
|
||||
}
|
||||
"neighbors" -> {
|
||||
viewModel.requestNeighborInfo(node.num)
|
||||
snackbarHostState.showSnackbar("Neighbor info requested for $nodeLabel")
|
||||
}
|
||||
"position" -> {
|
||||
viewModel.requestPosition(node.num)
|
||||
snackbarHostState.showSnackbar("Position requested for $nodeLabel")
|
||||
}
|
||||
"userinfo" -> {
|
||||
viewModel.requestUserInfo(node.num)
|
||||
snackbarHostState.showSnackbar("User info requested for $nodeLabel")
|
||||
}
|
||||
"connstatus" -> {
|
||||
viewModel.requestDeviceConnectionStatus(node.num)
|
||||
snackbarHostState.showSnackbar("Connection status requested for $nodeLabel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item { EmptyNodeState() }
|
||||
}
|
||||
|
||||
item {
|
||||
SectionHeader(title = "Packet Log", expanded = logExpanded, onExpandClick = { logExpanded = !logExpanded })
|
||||
}
|
||||
|
||||
if (logExpanded) {
|
||||
item {
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Box(modifier = Modifier.padding(16.dp)) { PacketLogContent(packetLog) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item { ActionButtons(viewModel, snackbarHostState) }
|
||||
item { Spacer(modifier = Modifier.height(16.dp)) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PacketLogContent(log: List<String>) {
|
||||
Column(modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp).verticalScroll(rememberScrollState())) {
|
||||
if (log.isEmpty()) {
|
||||
Text(
|
||||
text = "No packets yet.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
)
|
||||
} else {
|
||||
log.forEach { entry ->
|
||||
Text(
|
||||
text = entry,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
modifier = Modifier.padding(vertical = 2.dp),
|
||||
)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.2f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MyInfoSection(myId: String?, myNodeInfo: org.meshtastic.core.model.MyNodeInfo?) {
|
||||
TitledCard(title = "My Node Information") {
|
||||
|
|
@ -237,54 +350,6 @@ private fun EmptyNodeState() {
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NodeListContent(
|
||||
nodes: List<NodeInfo>,
|
||||
viewModel: MeshServiceViewModel,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
nodes.forEachIndexed { index, node ->
|
||||
val nodeLabel = node.user?.longName ?: node.user?.id ?: "Unknown Node"
|
||||
NodeItem(node) { action ->
|
||||
scope.launch {
|
||||
when (action) {
|
||||
"traceroute" -> {
|
||||
viewModel.requestTraceroute(node.num)
|
||||
snackbarHostState.showSnackbar("Traceroute requested for $nodeLabel")
|
||||
}
|
||||
"telemetry" -> {
|
||||
viewModel.requestTelemetry(node.num)
|
||||
snackbarHostState.showSnackbar("Telemetry requested for $nodeLabel")
|
||||
}
|
||||
"neighbors" -> {
|
||||
viewModel.requestNeighborInfo(node.num)
|
||||
snackbarHostState.showSnackbar("Neighbor info requested for $nodeLabel")
|
||||
}
|
||||
"position" -> {
|
||||
viewModel.requestPosition(node.num)
|
||||
snackbarHostState.showSnackbar("Position requested for $nodeLabel")
|
||||
}
|
||||
"userinfo" -> {
|
||||
viewModel.requestUserInfo(node.num)
|
||||
snackbarHostState.showSnackbar("User info requested for $nodeLabel")
|
||||
}
|
||||
"connstatus" -> {
|
||||
viewModel.requestDeviceConnectionStatus(node.num)
|
||||
snackbarHostState.showSnackbar("Connection status requested for $nodeLabel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index < nodes.size - 1) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessagingSection(viewModel: MeshServiceViewModel, lastMessage: String) {
|
||||
var textToSend by remember { mutableStateOf("") }
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ import org.meshtastic.core.model.NodeInfo
|
|||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.service.IMeshService
|
||||
import org.meshtastic.proto.PortNum
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
|
||||
private const val TAG = "MeshServiceViewModel"
|
||||
|
|
@ -61,15 +64,20 @@ class MeshServiceViewModel : ViewModel() {
|
|||
private val _connectionState = MutableStateFlow("UNKNOWN")
|
||||
val connectionState: StateFlow<String> = _connectionState.asStateFlow()
|
||||
|
||||
private val _packetLog = MutableStateFlow<List<String>>(emptyList())
|
||||
val packetLog: StateFlow<List<String>> = _packetLog.asStateFlow()
|
||||
|
||||
fun onServiceConnected(service: IMeshService?) {
|
||||
meshService = service
|
||||
_serviceConnectionStatus.value = true
|
||||
updateAllData()
|
||||
addToLog("Service Connected")
|
||||
}
|
||||
|
||||
fun onServiceDisconnected() {
|
||||
meshService = null
|
||||
_serviceConnectionStatus.value = false
|
||||
addToLog("Service Disconnected")
|
||||
}
|
||||
|
||||
private fun updateAllData() {
|
||||
|
|
@ -92,7 +100,9 @@ class MeshServiceViewModel : ViewModel() {
|
|||
fun updateConnectionState() {
|
||||
meshService?.let {
|
||||
try {
|
||||
_connectionState.value = it.connectionState() ?: "UNKNOWN"
|
||||
val state = it.connectionState() ?: "UNKNOWN"
|
||||
_connectionState.value = state
|
||||
addToLog("Connection State: $state")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to get connection state", e)
|
||||
}
|
||||
|
|
@ -109,7 +119,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
dataType = PortNum.TEXT_MESSAGE_APP.value,
|
||||
from = DataPacket.ID_LOCAL,
|
||||
time = System.currentTimeMillis(),
|
||||
id = service.packetId, // Correctly sync with radio's ID
|
||||
id = service.packetId,
|
||||
status = MessageStatus.UNKNOWN,
|
||||
hopLimit = 3,
|
||||
channel = 0,
|
||||
|
|
@ -117,8 +127,10 @@ class MeshServiceViewModel : ViewModel() {
|
|||
)
|
||||
service.send(packet)
|
||||
Log.d(TAG, "Message sent successfully, assigned ID: ${packet.id}")
|
||||
addToLog("Sent: $text (ID: ${packet.id})")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to send message", e)
|
||||
addToLog("Failed to send message: ${e.message}")
|
||||
}
|
||||
} ?: Log.w(TAG, "MeshService is not bound, cannot send message")
|
||||
}
|
||||
|
|
@ -146,6 +158,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
fun startProvideLocation() {
|
||||
try {
|
||||
meshService?.startProvideLocation()
|
||||
addToLog("Started GPS sharing")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to start providing location", e)
|
||||
}
|
||||
|
|
@ -154,6 +167,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
fun stopProvideLocation() {
|
||||
try {
|
||||
meshService?.stopProvideLocation()
|
||||
addToLog("Stopped GPS sharing")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to stop providing location", e)
|
||||
}
|
||||
|
|
@ -164,6 +178,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
try {
|
||||
it.requestTraceroute(Random.nextInt(), nodeNum)
|
||||
Log.i(TAG, "Traceroute requested for node $nodeNum")
|
||||
addToLog("Requested Traceroute for $nodeNum")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to request traceroute", e)
|
||||
}
|
||||
|
|
@ -173,9 +188,9 @@ class MeshServiceViewModel : ViewModel() {
|
|||
fun requestTelemetry(nodeNum: Int) {
|
||||
meshService?.let {
|
||||
try {
|
||||
// DEVICE_METRICS_FIELD_NUMBER = 1
|
||||
it.requestTelemetry(Random.nextInt(), nodeNum, 1)
|
||||
Log.i(TAG, "Telemetry requested for node $nodeNum")
|
||||
addToLog("Requested Telemetry for $nodeNum")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to request telemetry", e)
|
||||
}
|
||||
|
|
@ -187,6 +202,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
try {
|
||||
it.requestNeighborInfo(Random.nextInt(), nodeNum)
|
||||
Log.i(TAG, "Neighbor info requested for node $nodeNum")
|
||||
addToLog("Requested Neighbors for $nodeNum")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to request neighbor info", e)
|
||||
}
|
||||
|
|
@ -198,6 +214,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
try {
|
||||
it.requestPosition(nodeNum, Position(0.0, 0.0, 0))
|
||||
Log.i(TAG, "Position requested for node $nodeNum")
|
||||
addToLog("Requested Position for $nodeNum")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to request position", e)
|
||||
}
|
||||
|
|
@ -209,6 +226,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
try {
|
||||
it.requestUserInfo(nodeNum)
|
||||
Log.i(TAG, "User info requested for node $nodeNum")
|
||||
addToLog("Requested User Info for $nodeNum")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to request user info", e)
|
||||
}
|
||||
|
|
@ -220,6 +238,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
try {
|
||||
it.getDeviceConnectionStatus(Random.nextInt(), nodeNum)
|
||||
Log.i(TAG, "Device connection status requested for node $nodeNum")
|
||||
addToLog("Requested Connection Status for $nodeNum")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to request device connection status", e)
|
||||
}
|
||||
|
|
@ -231,6 +250,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
try {
|
||||
it.requestReboot(Random.nextInt(), 0)
|
||||
Log.w(TAG, "Local reboot requested!")
|
||||
addToLog("Requested Local Reboot")
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to request reboot", e)
|
||||
}
|
||||
|
|
@ -247,6 +267,7 @@ class MeshServiceViewModel : ViewModel() {
|
|||
"com.geeksville.mesh.MESH_CONNECTED",
|
||||
"com.geeksville.mesh.MESH_DISCONNECTED",
|
||||
-> updateConnectionState()
|
||||
|
||||
"com.geeksville.mesh.MESSAGE_STATUS" -> handleMessageStatus(intent)
|
||||
else ->
|
||||
if (action.startsWith("com.geeksville.mesh.RECEIVED.")) {
|
||||
|
|
@ -271,18 +292,37 @@ class MeshServiceViewModel : ViewModel() {
|
|||
val id = intent.getIntExtra("com.geeksville.mesh.PacketId", 0)
|
||||
val status = intent.getParcelableCompat("com.geeksville.mesh.Status", MessageStatus::class.java)
|
||||
Log.d(TAG, "Message Status for ID $id: $status")
|
||||
addToLog("Msg Status ID $id: $status")
|
||||
}
|
||||
|
||||
private fun handleReceivedPacket(action: String, intent: Intent) {
|
||||
val packet = intent.getParcelableCompat("com.geeksville.mesh.Payload", DataPacket::class.java) ?: return
|
||||
val packet = intent.getParcelableCompat("com.geeksville.mesh.Payload", DataPacket::class.java)
|
||||
if (packet == null) {
|
||||
Log.e(TAG, "Received packet extra was NULL for action: $action")
|
||||
addToLog("Error: Packet payload was null for $action")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Packet received: $packet")
|
||||
|
||||
if (packet.dataType == PortNum.TEXT_MESSAGE_APP.value) {
|
||||
val receivedText = packet.bytes?.utf8() ?: ""
|
||||
_message.value = "From ${packet.from}: $receivedText"
|
||||
addToLog("Received Text from ${packet.from}: $receivedText")
|
||||
} else {
|
||||
_message.value = "Received port ${action.substringAfterLast(".")} packet"
|
||||
val type = action.substringAfterLast(".")
|
||||
addToLog("Received $type from ${packet.from}. Check Logcat for details.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToLog(entry: String) {
|
||||
val timestamp = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
|
||||
val logEntry = "[$timestamp] $entry"
|
||||
Log.d(TAG, "Log: $logEntry")
|
||||
@Suppress("MagicNumber")
|
||||
_packetLog.value = (listOf(logEntry) + _packetLog.value).take(50)
|
||||
}
|
||||
|
||||
private fun <T : Parcelable> Intent.getParcelableCompat(key: String, clazz: Class<T>): T? =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getParcelableExtra(key, clazz)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue