Skip to content

Commit 085ccf5

Browse files
authored
add decoded payload to debug panel (#2472)
1 parent 9339958 commit 085ccf5

File tree

3 files changed

+129
-9
lines changed

3 files changed

+129
-9
lines changed

app/src/main/java/com/geeksville/mesh/model/DebugViewModel.kt

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ import com.geeksville.mesh.Portnums.PortNum
4242
import kotlinx.coroutines.flow.MutableStateFlow
4343
import kotlinx.coroutines.flow.asStateFlow
4444
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
45+
import com.google.protobuf.InvalidProtocolBufferException
46+
import com.geeksville.mesh.MeshProtos
47+
import com.geeksville.mesh.TelemetryProtos
48+
import com.geeksville.mesh.AdminProtos
49+
import com.geeksville.mesh.PaxcountProtos
50+
import com.geeksville.mesh.StoreAndForwardProtos
4551

4652
data class SearchMatch(
4753
val logIndex: Int,
@@ -156,6 +162,9 @@ class LogFilterManager {
156162
}
157163
}
158164

165+
private const val HEX_FORMAT = "%02x"
166+
167+
@Suppress("TooManyFunctions")
159168
@HiltViewModel
160169
class DebugViewModel @Inject constructor(
161170
private val meshLogRepository: MeshLogRepository,
@@ -206,29 +215,41 @@ class DebugViewModel @Inject constructor(
206215
messageType = log.message_type,
207216
formattedReceivedDate = TIME_FORMAT.format(log.received_date),
208217
logMessage = annotateMeshLogMessage(log),
218+
decodedPayload = decodePayloadFromMeshLog(log),
209219
)
210220
}.toImmutableList()
211221

212222
/**
213223
* Transform the input [MeshLog] by enhancing the raw message with annotations.
214224
*/
215225
private fun annotateMeshLogMessage(meshLog: MeshLog): String {
216-
val annotated = when (meshLog.message_type) {
226+
return when (meshLog.message_type) {
217227
"Packet" -> meshLog.meshPacket?.let { packet ->
218-
annotateRawMessage(meshLog.raw_message, packet.from, packet.to)
219-
}
220-
228+
annotatePacketLog(packet)
229+
} ?: meshLog.raw_message
221230
"NodeInfo" -> meshLog.nodeInfo?.let { nodeInfo ->
222231
annotateRawMessage(meshLog.raw_message, nodeInfo.num)
223-
}
224-
232+
} ?: meshLog.raw_message
225233
"MyNodeInfo" -> meshLog.myNodeInfo?.let { nodeInfo ->
226234
annotateRawMessage(meshLog.raw_message, nodeInfo.myNodeNum)
227-
}
235+
} ?: meshLog.raw_message
236+
else -> meshLog.raw_message
237+
}
238+
}
228239

229-
else -> null
240+
private fun annotatePacketLog(packet: MeshProtos.MeshPacket): String {
241+
val builder = packet.toBuilder()
242+
val hasDecoded = builder.hasDecoded()
243+
val decoded = if (hasDecoded) builder.decoded else null
244+
if (hasDecoded) builder.clearDecoded()
245+
val baseText = builder.build().toString().trimEnd()
246+
val result = if (hasDecoded && decoded != null) {
247+
val decodedText = decoded.toString().trimEnd().prependIndent(" ")
248+
"$baseText\ndecoded {\n$decodedText\n}"
249+
} else {
250+
baseText
230251
}
231-
return annotated ?: meshLog.raw_message
252+
return annotateRawMessage(result, packet.from, packet.to)
232253
}
233254

234255
/**
@@ -274,6 +295,7 @@ class DebugViewModel @Inject constructor(
274295
val messageType: String,
275296
val formattedReceivedDate: String,
276297
val logMessage: String,
298+
val decodedPayload: String? = null,
277299
)
278300

279301
companion object {
@@ -295,4 +317,54 @@ class DebugViewModel @Inject constructor(
295317
}
296318

297319
fun setSelectedLogId(id: String?) { _selectedLogId.value = id }
320+
321+
/**
322+
* Attempts to fully decode the payload of a MeshLog's MeshPacket using the appropriate protobuf definition,
323+
* based on the portnum of the packet.
324+
*
325+
* For known portnums, the payload is parsed into its corresponding proto message and returned as a string.
326+
* For text and alert messages, the payload is interpreted as UTF-8 text.
327+
* For unknown portnums, the payload is shown as a hex string.
328+
*
329+
* @param log The MeshLog containing the packet and payload to decode.
330+
* @return A human-readable string representation of the decoded payload, or an error message if decoding fails,
331+
* or null if the log does not contain a decodable packet.
332+
*/
333+
private fun decodePayloadFromMeshLog(log: MeshLog): String? {
334+
var result: String? = null
335+
val packet = log.meshPacket
336+
if (packet == null || !packet.hasDecoded()) {
337+
result = null
338+
} else {
339+
val portnum = packet.decoded.portnumValue
340+
val payload = packet.decoded.payload.toByteArray()
341+
result = try {
342+
when (portnum) {
343+
PortNum.TEXT_MESSAGE_APP_VALUE,
344+
PortNum.ALERT_APP_VALUE ->
345+
payload.toString(Charsets.UTF_8)
346+
PortNum.POSITION_APP_VALUE ->
347+
MeshProtos.Position.parseFrom(payload).toString()
348+
PortNum.WAYPOINT_APP_VALUE ->
349+
MeshProtos.Waypoint.parseFrom(payload).toString()
350+
PortNum.NODEINFO_APP_VALUE ->
351+
MeshProtos.User.parseFrom(payload).toString()
352+
PortNum.TELEMETRY_APP_VALUE ->
353+
TelemetryProtos.Telemetry.parseFrom(payload).toString()
354+
PortNum.ROUTING_APP_VALUE ->
355+
MeshProtos.Routing.parseFrom(payload).toString()
356+
PortNum.ADMIN_APP_VALUE ->
357+
AdminProtos.AdminMessage.parseFrom(payload).toString()
358+
PortNum.PAXCOUNTER_APP_VALUE ->
359+
PaxcountProtos.Paxcount.parseFrom(payload).toString()
360+
PortNum.STORE_FORWARD_APP_VALUE ->
361+
StoreAndForwardProtos.StoreAndForward.parseFrom(payload).toString()
362+
else -> payload.joinToString(" ") { HEX_FORMAT.format(it) }
363+
}
364+
} catch (e: InvalidProtocolBufferException) {
365+
"Failed to decode payload: ${e.message}"
366+
}
367+
}
368+
return result
369+
}
298370
}

app/src/main/java/com/geeksville/mesh/ui/debug/Debug.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,14 @@ internal fun DebugItem(
237237
color = colorScheme.onSurface
238238
)
239239
)
240+
// Show decoded payload if available
241+
if (!log.decodedPayload.isNullOrBlank()) {
242+
DecodedPayloadBlock(
243+
decodedPayload = log.decodedPayload,
244+
isSelected = isSelected,
245+
colorScheme = colorScheme
246+
)
247+
}
240248
}
241249
}
242250
}
@@ -780,3 +788,42 @@ private suspend fun exportAllLogs(context: Context, logs: List<UiMeshLog>) = wit
780788
warn("Error:IOException: " + e.toString())
781789
}
782790
}
791+
792+
@Composable
793+
private fun DecodedPayloadBlock(
794+
decodedPayload: String,
795+
isSelected: Boolean,
796+
colorScheme: ColorScheme
797+
) {
798+
val commonTextStyle = TextStyle(
799+
fontSize = if (isSelected) 10.sp else 8.sp,
800+
fontWeight = FontWeight.Bold,
801+
color = colorScheme.primary
802+
)
803+
804+
Text(
805+
text = stringResource(id = R.string.debug_decoded_payload),
806+
style = commonTextStyle,
807+
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
808+
)
809+
Text(
810+
text = "{",
811+
style = commonTextStyle,
812+
modifier = Modifier.padding(start = 8.dp, bottom = 2.dp)
813+
)
814+
Text(
815+
text = decodedPayload,
816+
softWrap = true,
817+
style = TextStyle(
818+
fontSize = if (isSelected) 10.sp else 8.sp,
819+
fontFamily = FontFamily.Monospace,
820+
color = colorScheme.onSurface.copy(alpha = 0.8f)
821+
),
822+
modifier = Modifier.padding(start = 16.dp, bottom = 0.dp)
823+
)
824+
Text(
825+
text = "}",
826+
style = commonTextStyle,
827+
modifier = Modifier.padding(start = 8.dp, bottom = 4.dp)
828+
)
829+
}

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
<string name="text_messages">Text messages</string>
166166
<string name="channel_invalid">This Channel URL is invalid and can not be used</string>
167167
<string name="debug_panel">Debug Panel</string>
168+
<string name="debug_decoded_payload">Decoded Payload:</string>
168169
<string name="debug_logs_export">Export Logs</string>
169170
<string name="debug_last_messages">500 last messages</string>
170171
<string name="debug_filters">Filters</string>

0 commit comments

Comments
 (0)