mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
add decoded payload to debug panel (#2472)
This commit is contained in:
parent
9339958398
commit
085ccf566f
3 changed files with 130 additions and 10 deletions
|
|
@ -42,6 +42,12 @@ import com.geeksville.mesh.Portnums.PortNum
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.AdminProtos
|
||||
import com.geeksville.mesh.PaxcountProtos
|
||||
import com.geeksville.mesh.StoreAndForwardProtos
|
||||
|
||||
data class SearchMatch(
|
||||
val logIndex: Int,
|
||||
|
|
@ -156,6 +162,9 @@ class LogFilterManager {
|
|||
}
|
||||
}
|
||||
|
||||
private const val HEX_FORMAT = "%02x"
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class DebugViewModel @Inject constructor(
|
||||
private val meshLogRepository: MeshLogRepository,
|
||||
|
|
@ -206,6 +215,7 @@ class DebugViewModel @Inject constructor(
|
|||
messageType = log.message_type,
|
||||
formattedReceivedDate = TIME_FORMAT.format(log.received_date),
|
||||
logMessage = annotateMeshLogMessage(log),
|
||||
decodedPayload = decodePayloadFromMeshLog(log),
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
|
|
@ -213,22 +223,33 @@ class DebugViewModel @Inject constructor(
|
|||
* Transform the input [MeshLog] by enhancing the raw message with annotations.
|
||||
*/
|
||||
private fun annotateMeshLogMessage(meshLog: MeshLog): String {
|
||||
val annotated = when (meshLog.message_type) {
|
||||
return when (meshLog.message_type) {
|
||||
"Packet" -> meshLog.meshPacket?.let { packet ->
|
||||
annotateRawMessage(meshLog.raw_message, packet.from, packet.to)
|
||||
}
|
||||
|
||||
annotatePacketLog(packet)
|
||||
} ?: meshLog.raw_message
|
||||
"NodeInfo" -> meshLog.nodeInfo?.let { nodeInfo ->
|
||||
annotateRawMessage(meshLog.raw_message, nodeInfo.num)
|
||||
}
|
||||
|
||||
} ?: meshLog.raw_message
|
||||
"MyNodeInfo" -> meshLog.myNodeInfo?.let { nodeInfo ->
|
||||
annotateRawMessage(meshLog.raw_message, nodeInfo.myNodeNum)
|
||||
}
|
||||
|
||||
else -> null
|
||||
} ?: meshLog.raw_message
|
||||
else -> meshLog.raw_message
|
||||
}
|
||||
return annotated ?: meshLog.raw_message
|
||||
}
|
||||
|
||||
private fun annotatePacketLog(packet: MeshProtos.MeshPacket): String {
|
||||
val builder = packet.toBuilder()
|
||||
val hasDecoded = builder.hasDecoded()
|
||||
val decoded = if (hasDecoded) builder.decoded else null
|
||||
if (hasDecoded) builder.clearDecoded()
|
||||
val baseText = builder.build().toString().trimEnd()
|
||||
val result = if (hasDecoded && decoded != null) {
|
||||
val decodedText = decoded.toString().trimEnd().prependIndent(" ")
|
||||
"$baseText\ndecoded {\n$decodedText\n}"
|
||||
} else {
|
||||
baseText
|
||||
}
|
||||
return annotateRawMessage(result, packet.from, packet.to)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -274,6 +295,7 @@ class DebugViewModel @Inject constructor(
|
|||
val messageType: String,
|
||||
val formattedReceivedDate: String,
|
||||
val logMessage: String,
|
||||
val decodedPayload: String? = null,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
@ -295,4 +317,54 @@ class DebugViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun setSelectedLogId(id: String?) { _selectedLogId.value = id }
|
||||
|
||||
/**
|
||||
* Attempts to fully decode the payload of a MeshLog's MeshPacket using the appropriate protobuf definition,
|
||||
* based on the portnum of the packet.
|
||||
*
|
||||
* For known portnums, the payload is parsed into its corresponding proto message and returned as a string.
|
||||
* For text and alert messages, the payload is interpreted as UTF-8 text.
|
||||
* For unknown portnums, the payload is shown as a hex string.
|
||||
*
|
||||
* @param log The MeshLog containing the packet and payload to decode.
|
||||
* @return A human-readable string representation of the decoded payload, or an error message if decoding fails,
|
||||
* or null if the log does not contain a decodable packet.
|
||||
*/
|
||||
private fun decodePayloadFromMeshLog(log: MeshLog): String? {
|
||||
var result: String? = null
|
||||
val packet = log.meshPacket
|
||||
if (packet == null || !packet.hasDecoded()) {
|
||||
result = null
|
||||
} else {
|
||||
val portnum = packet.decoded.portnumValue
|
||||
val payload = packet.decoded.payload.toByteArray()
|
||||
result = try {
|
||||
when (portnum) {
|
||||
PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
PortNum.ALERT_APP_VALUE ->
|
||||
payload.toString(Charsets.UTF_8)
|
||||
PortNum.POSITION_APP_VALUE ->
|
||||
MeshProtos.Position.parseFrom(payload).toString()
|
||||
PortNum.WAYPOINT_APP_VALUE ->
|
||||
MeshProtos.Waypoint.parseFrom(payload).toString()
|
||||
PortNum.NODEINFO_APP_VALUE ->
|
||||
MeshProtos.User.parseFrom(payload).toString()
|
||||
PortNum.TELEMETRY_APP_VALUE ->
|
||||
TelemetryProtos.Telemetry.parseFrom(payload).toString()
|
||||
PortNum.ROUTING_APP_VALUE ->
|
||||
MeshProtos.Routing.parseFrom(payload).toString()
|
||||
PortNum.ADMIN_APP_VALUE ->
|
||||
AdminProtos.AdminMessage.parseFrom(payload).toString()
|
||||
PortNum.PAXCOUNTER_APP_VALUE ->
|
||||
PaxcountProtos.Paxcount.parseFrom(payload).toString()
|
||||
PortNum.STORE_FORWARD_APP_VALUE ->
|
||||
StoreAndForwardProtos.StoreAndForward.parseFrom(payload).toString()
|
||||
else -> payload.joinToString(" ") { HEX_FORMAT.format(it) }
|
||||
}
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
"Failed to decode payload: ${e.message}"
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,6 +237,14 @@ internal fun DebugItem(
|
|||
color = colorScheme.onSurface
|
||||
)
|
||||
)
|
||||
// Show decoded payload if available
|
||||
if (!log.decodedPayload.isNullOrBlank()) {
|
||||
DecodedPayloadBlock(
|
||||
decodedPayload = log.decodedPayload,
|
||||
isSelected = isSelected,
|
||||
colorScheme = colorScheme
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -780,3 +788,42 @@ private suspend fun exportAllLogs(context: Context, logs: List<UiMeshLog>) = wit
|
|||
warn("Error:IOException: " + e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DecodedPayloadBlock(
|
||||
decodedPayload: String,
|
||||
isSelected: Boolean,
|
||||
colorScheme: ColorScheme
|
||||
) {
|
||||
val commonTextStyle = TextStyle(
|
||||
fontSize = if (isSelected) 10.sp else 8.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = colorScheme.primary
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.debug_decoded_payload),
|
||||
style = commonTextStyle,
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "{",
|
||||
style = commonTextStyle,
|
||||
modifier = Modifier.padding(start = 8.dp, bottom = 2.dp)
|
||||
)
|
||||
Text(
|
||||
text = decodedPayload,
|
||||
softWrap = true,
|
||||
style = TextStyle(
|
||||
fontSize = if (isSelected) 10.sp else 8.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
),
|
||||
modifier = Modifier.padding(start = 16.dp, bottom = 0.dp)
|
||||
)
|
||||
Text(
|
||||
text = "}",
|
||||
style = commonTextStyle,
|
||||
modifier = Modifier.padding(start = 8.dp, bottom = 4.dp)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue