From 6ec16073c1e26ee6bc73475f82f27b3bb1b90f71 Mon Sep 17 00:00:00 2001 From: Ludovic Goix Date: Wed, 23 Sep 2020 22:47:45 -0400 Subject: [PATCH] Added a debug panel final --- app/build.gradle | 13 ++ .../java/com/geeksville/mesh/MainActivity.kt | 11 ++ .../mesh/database/MeshtasticDatabase.kt | 36 +++++ .../mesh/database/PacketRepository.kt | 17 ++ .../geeksville/mesh/database/dao/PacketDao.kt | 21 +++ .../geeksville/mesh/database/entity/Packet.kt | 17 ++ .../java/com/geeksville/mesh/model/UIState.kt | 24 +++ .../geeksville/mesh/service/MeshService.kt | 29 ++++ .../com/geeksville/mesh/ui/DebugFragment.kt | 50 ++++++ .../geeksville/mesh/ui/PacketListAdapter.kt | 49 ++++++ .../drawable/cloud_download_outline_24.xml | 7 + app/src/main/res/layout/activity_main.xml | 146 ++++++++++-------- .../main/res/layout/adapter_packet_layout.xml | 68 ++++++++ app/src/main/res/layout/debug_fragment.xml | 60 +++++++ app/src/main/res/menu/menu_main.xml | 4 + 15 files changed, 484 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt create mode 100644 app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt create mode 100644 app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt create mode 100644 app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt create mode 100644 app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt create mode 100644 app/src/main/java/com/geeksville/mesh/ui/PacketListAdapter.kt create mode 100644 app/src/main/res/drawable/cloud_download_outline_24.xml create mode 100644 app/src/main/res/layout/adapter_packet_layout.xml create mode 100644 app/src/main/res/layout/debug_fragment.xml diff --git a/app/build.gradle b/app/build.gradle index ec5815b9..99cc0057 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,6 +12,8 @@ apply plugin: 'com.google.firebase.crashlytics' // protobuf apply plugin: 'com.google.protobuf' +apply plugin: 'kotlin-kapt' + android { /* signingConfigs { @@ -105,6 +107,9 @@ protobuf { } dependencies { + + def room_version = "2.2.5" + implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' @@ -117,7 +122,15 @@ dependencies { implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' + implementation "androidx.room:room-runtime:$room_version" + kapt "androidx.room:room-compiler:$room_version" + + // optional - Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" + + // optional - Test helpers + testImplementation "androidx.room:room-testing:$room_version" testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 2c774ddc..b8da48e2 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -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) } } diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt new file mode 100644 index 00000000..a384ca0e --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt @@ -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 + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt new file mode 100644 index 00000000..f82240b2 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt @@ -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> = packetDao.getAllPacket(500) + + suspend fun insert(packet: Packet) { + packetDao.insert(packet) + } + + suspend fun deleteAll() { + packetDao.deleteAll() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt new file mode 100644 index 00000000..3eb4e403 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt @@ -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> + + @Insert + fun insert(packet: Packet) + + @Query("DELETE from packet") + fun deleteAll() + +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt new file mode 100644 index 00000000..0c4d37e1 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt @@ -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 +) { + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index a673dd6e..e7c9af33 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -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> + 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 diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 24a732b9..2e1cd5eb 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -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") diff --git a/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt new file mode 100644 index 00000000..6257f6b5 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt @@ -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(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) } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/PacketListAdapter.kt b/app/src/main/java/com/geeksville/mesh/ui/PacketListAdapter.kt new file mode 100644 index 00000000..52ec3589 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/PacketListAdapter.kt @@ -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() { + + private val inflater: LayoutInflater = LayoutInflater.from(context) + private var packets = emptyList() + + 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) { + this.packets = packets + notifyDataSetChanged() + } + + override fun getItemCount() = packets.size + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/cloud_download_outline_24.xml b/app/src/main/res/drawable/cloud_download_outline_24.xml new file mode 100644 index 00000000..4301600f --- /dev/null +++ b/app/src/main/res/drawable/cloud_download_outline_24.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 62f4e2fd..55c6134b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,90 +1,100 @@ - + tools:context=".MainActivity" + android:id="@+id/mainActivityLayout"> - + android:layout_height="match_parent"> - + android:layout_height="wrap_content" + android:theme="@style/AppTheme.AppBarOverlay"> - + - + android:background="?attr/colorPrimary" + android:minHeight="?attr/actionBarSize" + android:visibility="visible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - + + + + + + + + + - + + + + + android:layout_height="match_parent" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> - - - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_packet_layout.xml b/app/src/main/res/layout/adapter_packet_layout.xml new file mode 100644 index 00000000..f1e8beda --- /dev/null +++ b/app/src/main/res/layout/adapter_packet_layout.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/debug_fragment.xml b/app/src/main/res/layout/debug_fragment.xml new file mode 100644 index 00000000..6aef5a35 --- /dev/null +++ b/app/src/main/res/layout/debug_fragment.xml @@ -0,0 +1,60 @@ + + + + + +