@@ -42,6 +42,12 @@ import com.geeksville.mesh.Portnums.PortNum
42
42
import kotlinx.coroutines.flow.MutableStateFlow
43
43
import kotlinx.coroutines.flow.asStateFlow
44
44
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
45
51
46
52
data class SearchMatch (
47
53
val logIndex : Int ,
@@ -156,6 +162,9 @@ class LogFilterManager {
156
162
}
157
163
}
158
164
165
+ private const val HEX_FORMAT = " %02x"
166
+
167
+ @Suppress(" TooManyFunctions" )
159
168
@HiltViewModel
160
169
class DebugViewModel @Inject constructor(
161
170
private val meshLogRepository : MeshLogRepository ,
@@ -206,29 +215,41 @@ class DebugViewModel @Inject constructor(
206
215
messageType = log.message_type,
207
216
formattedReceivedDate = TIME_FORMAT .format(log.received_date),
208
217
logMessage = annotateMeshLogMessage(log),
218
+ decodedPayload = decodePayloadFromMeshLog(log),
209
219
)
210
220
}.toImmutableList()
211
221
212
222
/* *
213
223
* Transform the input [MeshLog] by enhancing the raw message with annotations.
214
224
*/
215
225
private fun annotateMeshLogMessage (meshLog : MeshLog ): String {
216
- val annotated = when (meshLog.message_type) {
226
+ return when (meshLog.message_type) {
217
227
" Packet" -> meshLog.meshPacket?.let { packet ->
218
- annotateRawMessage(meshLog.raw_message, packet.from, packet.to)
219
- }
220
-
228
+ annotatePacketLog(packet)
229
+ } ? : meshLog.raw_message
221
230
" NodeInfo" -> meshLog.nodeInfo?.let { nodeInfo ->
222
231
annotateRawMessage(meshLog.raw_message, nodeInfo.num)
223
- }
224
-
232
+ } ? : meshLog.raw_message
225
233
" MyNodeInfo" -> meshLog.myNodeInfo?.let { nodeInfo ->
226
234
annotateRawMessage(meshLog.raw_message, nodeInfo.myNodeNum)
227
- }
235
+ } ? : meshLog.raw_message
236
+ else -> meshLog.raw_message
237
+ }
238
+ }
228
239
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 \n decoded {\n $decodedText \n }"
249
+ } else {
250
+ baseText
230
251
}
231
- return annotated ? : meshLog.raw_message
252
+ return annotateRawMessage(result, packet.from, packet.to)
232
253
}
233
254
234
255
/* *
@@ -274,6 +295,7 @@ class DebugViewModel @Inject constructor(
274
295
val messageType : String ,
275
296
val formattedReceivedDate : String ,
276
297
val logMessage : String ,
298
+ val decodedPayload : String? = null ,
277
299
)
278
300
279
301
companion object {
@@ -295,4 +317,54 @@ class DebugViewModel @Inject constructor(
295
317
}
296
318
297
319
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
+ }
298
370
}
0 commit comments