mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Added a debug panel
final
This commit is contained in:
parent
2dab8ccf19
commit
6ec16073c1
15 changed files with 484 additions and 68 deletions
|
|
@ -30,6 +30,8 @@ import androidx.appcompat.widget.Toolbar
|
|||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
|
|
@ -917,6 +919,15 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
Toast.makeText(applicationContext, item.title, Toast.LENGTH_SHORT).show()
|
||||
return true
|
||||
}
|
||||
R.id.debug -> {
|
||||
val fragmentManager: FragmentManager = supportFragmentManager
|
||||
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
|
||||
val nameFragment = DebugFragment()
|
||||
fragmentTransaction.add(R.id.mainActivityLayout, nameFragment)
|
||||
fragmentTransaction.addToBackStack(null)
|
||||
fragmentTransaction.commit()
|
||||
return true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.geeksville.mesh.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
|
||||
@Database(entities = [Packet::class], version = 1, exportSchema = false)
|
||||
abstract class MeshtasticDatabase : RoomDatabase() {
|
||||
abstract fun packetDao(): PacketDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: MeshtasticDatabase? = null
|
||||
|
||||
fun getDatabase(
|
||||
context: Context
|
||||
): MeshtasticDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
MeshtasticDatabase::class.java,
|
||||
"meshtastic_database"
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
INSTANCE = instance
|
||||
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.geeksville.mesh.database
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
|
||||
class PacketRepository(private val packetDao : PacketDao) {
|
||||
val allPackets : LiveData<List<Packet>> = packetDao.getAllPacket(500)
|
||||
|
||||
suspend fun insert(packet: Packet) {
|
||||
packetDao.insert(packet)
|
||||
}
|
||||
|
||||
suspend fun deleteAll() {
|
||||
packetDao.deleteAll()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.geeksville.mesh.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
|
||||
@Dao
|
||||
interface PacketDao {
|
||||
|
||||
@Query("Select * from packet order by rowid desc limit 0,:maxItem")
|
||||
fun getAllPacket(maxItem: Int): LiveData<List<Packet>>
|
||||
|
||||
@Insert
|
||||
fun insert(packet: Packet)
|
||||
|
||||
@Query("DELETE from packet")
|
||||
fun deleteAll()
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.geeksville.mesh.database.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
|
||||
@Entity(tableName = "packet")
|
||||
data class Packet(@PrimaryKey val uuid: String,
|
||||
@ColumnInfo(name = "type") val message_type: String,
|
||||
@ColumnInfo(name = "received_date") val received_date: Long,
|
||||
@ColumnInfo(name = "message") val raw_message: String
|
||||
) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -8,13 +8,20 @@ import android.os.RemoteException
|
|||
import android.view.Menu
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.IMeshService
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MyNodeInfo
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/// Given a human name, strip out the first letter of the first three words and return that as the initials for
|
||||
/// that user. If the original name is only one word, strip vowels from the original name and if the result is
|
||||
|
|
@ -37,10 +44,27 @@ fun getInitials(nameIn: String): String {
|
|||
}
|
||||
|
||||
class UIViewModel(app: Application) : AndroidViewModel(app), Logging {
|
||||
|
||||
private val repository: PacketRepository
|
||||
|
||||
val allPackets: LiveData<List<Packet>>
|
||||
|
||||
init {
|
||||
val packetsDao = MeshtasticDatabase.getDatabase(app).packetDao()
|
||||
repository = PacketRepository(packetsDao)
|
||||
allPackets = repository.allPackets
|
||||
debug("ViewModel created")
|
||||
}
|
||||
|
||||
|
||||
fun insertPacket(packet: Packet) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.insert(packet)
|
||||
}
|
||||
|
||||
fun deleteAllPacket() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteAll()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Return the current channel info
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ import com.geeksville.mesh.*
|
|||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.MeshProtos.ToRadio
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.util.*
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.gms.common.api.ResolvableApiException
|
||||
|
|
@ -163,6 +166,8 @@ class MeshService : Service(), Logging {
|
|||
/// The current state of our connection
|
||||
private var connectionState = ConnectionState.DISCONNECTED
|
||||
|
||||
private var packetRepo: PacketRepository? = null
|
||||
|
||||
/*
|
||||
see com.geeksville.mesh broadcast intents
|
||||
// RECEIVED_OPAQUE for data received from other nodes
|
||||
|
|
@ -481,6 +486,9 @@ class MeshService : Service(), Logging {
|
|||
|
||||
info("Creating mesh service")
|
||||
|
||||
val packetsDao = MeshtasticDatabase.getDatabase(applicationContext).packetDao()
|
||||
packetRepo = PacketRepository(packetsDao)
|
||||
|
||||
// Switch to the IO thread
|
||||
serviceScope.handledLaunch {
|
||||
loadSettings() // Load our last known node DB
|
||||
|
|
@ -964,6 +972,8 @@ class MeshService : Service(), Logging {
|
|||
// debug("Recieved: $packet")
|
||||
val p = packet.decoded
|
||||
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "packet", System.currentTimeMillis(), packet.toString())
|
||||
insertPacket(packetToSave)
|
||||
// If the rxTime was not set by the device (because device software was old), guess at a time
|
||||
val rxTime = if (packet.rxTime != 0) packet.rxTime else currentSecond()
|
||||
|
||||
|
|
@ -995,6 +1005,13 @@ class MeshService : Service(), Logging {
|
|||
handleAckNak(false, p.failId)
|
||||
}
|
||||
|
||||
private fun insertPacket(packetToSave: Packet) {
|
||||
serviceScope.handledLaunch {
|
||||
info("insert: ${packetToSave.message_type} = ${packetToSave.raw_message.toOneLineString()}")
|
||||
packetRepo!!.insert(packetToSave)
|
||||
}
|
||||
}
|
||||
|
||||
private fun currentSecond() = (System.currentTimeMillis() / 1000).toInt()
|
||||
|
||||
|
||||
|
|
@ -1229,6 +1246,8 @@ class MeshService : Service(), Logging {
|
|||
|
||||
|
||||
private fun handleRadioConfig(radio: MeshProtos.RadioConfig) {
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "RadioConfig", System.currentTimeMillis(), radio.toString())
|
||||
insertPacket(packetToSave)
|
||||
radioConfig = radio
|
||||
}
|
||||
|
||||
|
|
@ -1257,6 +1276,9 @@ class MeshService : Service(), Logging {
|
|||
private fun handleNodeInfo(info: MeshProtos.NodeInfo) {
|
||||
debug("Received nodeinfo num=${info.num}, hasUser=${info.hasUser()}, hasPosition=${info.hasPosition()}")
|
||||
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "NodeInfo", System.currentTimeMillis(), info.toString())
|
||||
insertPacket(packetToSave)
|
||||
|
||||
logAssert(newNodes.size <= 256) // Sanity check to make sure a device bug can't fill this list forever
|
||||
newNodes.add(info)
|
||||
}
|
||||
|
|
@ -1266,6 +1288,9 @@ class MeshService : Service(), Logging {
|
|||
* Update the nodeinfo (called from either new API version or the old one)
|
||||
*/
|
||||
private fun handleMyInfo(myInfo: MeshProtos.MyNodeInfo) {
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "MyNodeInfo", System.currentTimeMillis(), myInfo.toString())
|
||||
insertPacket(packetToSave)
|
||||
|
||||
setFirmwareUpdateFilename(myInfo)
|
||||
|
||||
val mi = with(myInfo) {
|
||||
|
|
@ -1312,6 +1337,10 @@ class MeshService : Service(), Logging {
|
|||
|
||||
private fun handleConfigComplete(configCompleteId: Int) {
|
||||
if (configCompleteId == configNonce) {
|
||||
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "ConfigComplete", System.currentTimeMillis(), configCompleteId.toString())
|
||||
insertPacket(packetToSave)
|
||||
|
||||
// This was our config request
|
||||
if (newMyNodeInfo == null || newNodes.isEmpty())
|
||||
errormsg("Did not receive a valid config")
|
||||
|
|
|
|||
50
app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt
Normal file
50
app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import kotlinx.android.synthetic.main.debug_fragment.*
|
||||
|
||||
class DebugFragment : Fragment() {
|
||||
|
||||
val model: UIViewModel by viewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
|
||||
return inflater.inflate(R.layout.debug_fragment, container, false)
|
||||
}
|
||||
//Button to clear All log
|
||||
|
||||
//List all log
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.packets_recyclerview)
|
||||
val adapter = PacketListAdapter(requireContext())
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
|
||||
clearButton.setOnClickListener {
|
||||
model.deleteAllPacket()
|
||||
}
|
||||
|
||||
closeButton.setOnClickListener{
|
||||
parentFragmentManager.popBackStack();
|
||||
}
|
||||
model.allPackets.observe(viewLifecycleOwner, Observer {
|
||||
packets -> packets?.let { adapter.setPackets(it) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.content.Context
|
||||
import java.text.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import java.util.*
|
||||
|
||||
class PacketListAdapter internal constructor(
|
||||
context: Context
|
||||
) : RecyclerView.Adapter<PacketListAdapter.PacketViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
private var packets = emptyList<Packet>()
|
||||
|
||||
private val timeFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM)
|
||||
|
||||
inner class PacketViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val packetTypeView: TextView = itemView.findViewById(R.id.type)
|
||||
val packetDateReceivedView: TextView = itemView.findViewById(R.id.dateReceived)
|
||||
val packetRawMessage : TextView = itemView.findViewById(R.id.rawMessage)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PacketViewHolder {
|
||||
val itemView = inflater.inflate(R.layout.adapter_packet_layout, parent, false)
|
||||
return PacketViewHolder(itemView)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PacketViewHolder, position: Int) {
|
||||
val current = packets[position]
|
||||
holder.packetTypeView.text = current.message_type
|
||||
holder.packetRawMessage.text = current.raw_message
|
||||
val date = Date(current.received_date)
|
||||
holder.packetDateReceivedView.text = timeFormat.format(date)
|
||||
}
|
||||
|
||||
internal fun setPackets(packets: List<Packet>) {
|
||||
this.packets = packets
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemCount() = packets.size
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue