From e32a1dadea52999ffe5d315c36c99245e2ee8c08 Mon Sep 17 00:00:00 2001 From: Mike Cumings Date: Fri, 2 Feb 2024 18:55:41 -0800 Subject: [PATCH] Annotated debug panel of to/from fields with hex form (#830) --- .../mesh/database/entity/MeshLog.kt | 13 +++ .../com/geeksville/mesh/ui/DebugAdapter.kt | 39 ++++++++- .../com/geeksville/mesh/ui/DebugFragment.kt | 79 ++++++++++++++++++- app/src/main/res/values-night/colors.xml | 1 + app/src/main/res/values/colors.xml | 1 + 5 files changed, 130 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/MeshLog.kt b/app/src/main/java/com/geeksville/mesh/database/entity/MeshLog.kt index a1a1bd448..0697376a3 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/MeshLog.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/MeshLog.kt @@ -42,6 +42,19 @@ data class MeshLog(@PrimaryKey val uuid: String, return null } + val myNodeInfo: MeshProtos.MyNodeInfo? + get() { + if (message_type == "MyNodeInfo") { + val builder = MeshProtos.MyNodeInfo.newBuilder() + try { + TextFormat.getParser().merge(raw_message, builder) + return builder.build() + } catch (e: IOException) { + } + } + return null + } + val position: MeshProtos.Position? get() { return meshPacket?.run { diff --git a/app/src/main/java/com/geeksville/mesh/ui/DebugAdapter.kt b/app/src/main/java/com/geeksville/mesh/ui/DebugAdapter.kt index 82984ff34..f81316a30 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/DebugAdapter.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/DebugAdapter.kt @@ -1,21 +1,25 @@ package com.geeksville.mesh.ui import android.content.Context +import android.text.SpannedString import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.text.toSpannable import androidx.recyclerview.widget.RecyclerView import com.geeksville.mesh.R import com.geeksville.mesh.database.entity.MeshLog import java.text.DateFormat -import java.util.* +import java.util.Date class DebugAdapter internal constructor( context: Context ) : RecyclerView.Adapter() { private val inflater: LayoutInflater = LayoutInflater.from(context) + private val colorAnnotation = ContextCompat.getColor(context, R.color.colorAnnotation) private var logs = emptyList() private val timeFormat: DateFormat = @@ -35,11 +39,36 @@ class DebugAdapter internal constructor( override fun onBindViewHolder(holder: DebugViewHolder, position: Int) { val current = logs[position] holder.logTypeView.text = current.message_type - holder.logRawMessage.text = current.raw_message + holder.logRawMessage.text = annotateMessage(current) val date = Date(current.received_date) holder.logDateReceivedView.text = timeFormat.format(date) } + /** + * Enhance the raw message by visually distinguishing the annotations prior to when + * the data was added to the database. + * + * @see com.geeksville.mesh.ui.DebugFragment.annotateMeshLogs + */ + private fun annotateMessage(current: MeshLog): CharSequence { + val spannable = current.raw_message.toSpannable() + REGEX_ANNOTATED_NODE_ID.findAll(spannable).toList().reversed().forEach { + spannable.setSpan( + android.text.style.StyleSpan(android.graphics.Typeface.ITALIC), + it.range.first, + it.range.last + 1, + SpannedString.SPAN_EXCLUSIVE_EXCLUSIVE + ) + spannable.setSpan( + android.text.style.ForegroundColorSpan(colorAnnotation), + it.range.first, + it.range.last + 1, + SpannedString.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + return spannable + } + internal fun setLogs(logs: List) { this.logs = logs notifyDataSetChanged() @@ -47,4 +76,10 @@ class DebugAdapter internal constructor( override fun getItemCount() = logs.size + private companion object { + /** + * Regex to match the node ID annotations in the MeshLog raw message text. + */ + val REGEX_ANNOTATED_NODE_ID = Regex("\\(![0-9a-fA-F]{8}\\)$", RegexOption.MULTILINE) + } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt index fa63b4fab..29bdbd835 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt @@ -9,10 +9,16 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.asLiveData import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.geeksville.mesh.CoroutineDispatchers import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.MeshLog import com.geeksville.mesh.databinding.FragmentDebugBinding import com.geeksville.mesh.model.UIViewModel import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import java.util.Locale +import javax.inject.Inject @AndroidEntryPoint class DebugFragment : Fragment() { @@ -24,6 +30,9 @@ class DebugFragment : Fragment() { private val model: UIViewModel by activityViewModels() + @Inject + lateinit var dispatchers: CoroutineDispatchers + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -47,7 +56,11 @@ class DebugFragment : Fragment() { binding.closeButton.setOnClickListener { parentFragmentManager.popBackStack() } - model.meshLog.asLiveData().observe(viewLifecycleOwner) { logs -> + model.meshLog + .map(this::annotateMeshLogs) + .flowOn(dispatchers.default) + .asLiveData() + .observe(viewLifecycleOwner) { logs -> logs?.let { adapter.setLogs(it) } } } @@ -56,4 +69,68 @@ class DebugFragment : Fragment() { super.onDestroyView() _binding = null } + + /** + * Transform the input list by enhancing the raw message with annotations. + */ + private fun annotateMeshLogs(logs: List): List { + return logs.map { meshLog -> + val annotated = when (meshLog.message_type) { + "Packet" -> { + meshLog.meshPacket?.let { packet -> + annotateRawMessage(meshLog.raw_message, packet.from, packet.to) + } + } + "NodeInfo" -> { + meshLog.nodeInfo?.let { nodeInfo -> + annotateRawMessage(meshLog.raw_message, nodeInfo.num) + } + } + "MyNodeInfo" -> { + meshLog.myNodeInfo?.let { nodeInfo -> + annotateRawMessage(meshLog.raw_message, nodeInfo.myNodeNum) + } + } + else -> null + } + if (annotated == null) { + meshLog + } else { + meshLog.copy(raw_message = annotated) + } + } + } + + /** + * Annotate the raw message string with the node IDs provided, in hex, if they are present. + */ + private fun annotateRawMessage(rawMessage: String, vararg nodeIds: Int): String { + val msg = StringBuilder(rawMessage) + var mutated = false + nodeIds.forEach { nodeId -> + mutated = mutated or msg.annotateNodeId(nodeId) + } + return if (mutated) { + return msg.toString() + } else { + rawMessage + } + } + + /** + * Look for a single node ID integer in the string and annotate it with the hex equivalent + * if found. + */ + private fun StringBuilder.annotateNodeId(nodeId: Int): Boolean { + val nodeIdStr = nodeId.toUInt().toString() + indexOf(nodeIdStr).takeIf { it >= 0 }?.let { idx -> + insert(idx + nodeIdStr.length, " (${nodeId.asNodeId()})") + return true + } + return false + } + + private fun Int.asNodeId(): String { + return "!%08x".format(Locale.getDefault(), this) + } } \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 484e4b50d..ea8a3511c 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -10,4 +10,5 @@ #FFFFFF #67EA94 #AAAAAA + #039BE5 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 322875037..5a1a72b62 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -12,4 +12,5 @@ #212121 #67EA94 #535353 + #0288D1