mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Annotated debug panel of to/from fields with hex form (#830)
This commit is contained in:
parent
d75188f03c
commit
e32a1dadea
5 changed files with 130 additions and 3 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<DebugAdapter.DebugViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
private val colorAnnotation = ContextCompat.getColor(context, R.color.colorAnnotation)
|
||||
private var logs = emptyList<MeshLog>()
|
||||
|
||||
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<MeshLog>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MeshLog>): List<MeshLog> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,4 +10,5 @@
|
|||
<color name="colorMenuItem">#FFFFFF</color>
|
||||
<color name="selectedColor">#67EA94</color>
|
||||
<color name="unselectedColor">#AAAAAA</color>
|
||||
<color name="colorAnnotation">#039BE5</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -12,4 +12,5 @@
|
|||
<color name="unselectedColor">#212121</color>
|
||||
<color name="buttonColor">#67EA94</color>
|
||||
<color name="colourGrey">#535353</color>
|
||||
<color name="colorAnnotation">#0288D1</color>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue