From 78a08898fe6097f7f7198282d9e2a26afc5c2d30 Mon Sep 17 00:00:00 2001 From: Vadim Furman Date: Wed, 17 Mar 2021 21:00:01 -0700 Subject: [PATCH] Save messages in CSV and fix position broadcast --- .../java/com/geeksville/mesh/MainActivity.kt | 67 +++++++++++++++++-- .../geeksville/mesh/database/dao/PacketDao.kt | 2 +- .../geeksville/mesh/database/entity/Packet.kt | 27 +++++++- .../geeksville/mesh/service/MeshService.kt | 41 +++++++----- .../com/geeksville/mesh/ui/LocationUtils.kt | 11 ++- app/src/main/res/menu/menu_main.xml | 4 ++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 126 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 136169f67..2d2320950 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -20,9 +20,7 @@ import android.os.Build import android.os.Bundle import android.os.Handler import android.os.RemoteException -import android.text.SpannableString import android.text.method.LinkMovementMethod -import android.text.util.Linkify import android.view.Menu import android.view.MenuItem import android.view.MotionEvent @@ -43,8 +41,8 @@ import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.android.ServiceClient import com.geeksville.concurrent.handledLaunch +import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.databinding.ActivityMainBinding -import com.geeksville.mesh.model.Channel import com.geeksville.mesh.model.ChannelSet import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.model.UIViewModel @@ -66,9 +64,11 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import java.io.FileOutputStream import java.nio.charset.Charset import java.text.DateFormat import java.util.* +import kotlin.math.roundToInt /* @@ -132,6 +132,7 @@ class MainActivity : AppCompatActivity(), Logging, const val RC_SIGN_IN = 12 // google signin completed const val RC_SELECT_DEVICE = 13 // seems to be hardwired in CompanionDeviceManager to add 65536 + const val CREATE_CSV_FILE = 14 } private lateinit var binding: ActivityMainBinding @@ -555,6 +556,18 @@ class MainActivity : AppCompatActivity(), Logging, else -> warn("BLE device select intent failed") } + CREATE_CSV_FILE -> { + if (resultCode == Activity.RESULT_OK) { + data?.data?.let { file_uri -> + model.allPackets.observe(this, { packets -> + if (packets != null) { + saveMessagesCSV(file_uri, packets) + model.allPackets.removeObservers(this) + } + }) + } + } + } } } @@ -659,7 +672,7 @@ class MainActivity : AppCompatActivity(), Logging, val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0") val minVer = DeviceVersion("1.2.0") - if(curVer < minVer) + if (curVer < minVer) showAlert(R.string.firmware_too_old, R.string.firmware_old) else { // If our app is too old/new, we probably don't understand the new radioconfig messages, so we don't read them until here @@ -869,8 +882,7 @@ class MainActivity : AppCompatActivity(), Logging, errormsg("Device error during init ${ex.message}") model.isConnected.value = MeshService.ConnectionState.valueOf(service.connectionState()) - } - finally { + } finally { connectionJob = null } @@ -1029,6 +1041,15 @@ class MainActivity : AppCompatActivity(), Logging, fragmentTransaction.commit() return true } + R.id.save_messages_csv -> { + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/csv" + putExtra(Intent.EXTRA_TITLE, "messages.csv") + } + startActivityForResult(intent, CREATE_CSV_FILE) + return true + } else -> super.onOptionsItemSelected(item) } } @@ -1042,4 +1063,38 @@ class MainActivity : AppCompatActivity(), Logging, errormsg("Can not find the version: ${e.message}") } } + + private fun saveMessagesCSV(file_uri: Uri, packets: List) { + // Extract distances to this device from position messages and put (node,SNR,distance) in + // the file_uri + val myNodeNum = model.myNodeInfo.value?.myNodeNum ?: return + + applicationContext.contentResolver.openFileDescriptor(file_uri, "w")?.use { + FileOutputStream(it.fileDescriptor).use { fs -> + // Write header + fs.write(("from,snr,time,dist\n").toByteArray()); + // Packets are ordered by time, we keep most recent position of + // our device in my_position. + var my_position: MeshProtos.Position? = null + packets.forEach { + it.proto?.let { packet_proto -> + it.position?.let { position -> + if (packet_proto.from == myNodeNum) { + my_position = position + } else if (my_position != null) { + val dist: Int = + positionToMeter(my_position!!, position).roundToInt() + fs.write( + ("${packet_proto.from.toUInt().toString(16)}," + + "${packet_proto.rxSnr},${packet_proto.rxTime},$dist\n") + .toByteArray() + ) + } + } + } + } + } + } + } } + 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 index 3eb4e4031..a02016a94 100644 --- a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt +++ b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt @@ -9,7 +9,7 @@ import com.geeksville.mesh.database.entity.Packet @Dao interface PacketDao { - @Query("Select * from packet order by rowid desc limit 0,:maxItem") + @Query("Select * from packet order by received_date desc limit 0,:maxItem") fun getAllPacket(maxItem: Int): LiveData> @Insert 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 index 0c4d37e17..ad516e1ec 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt @@ -3,6 +3,10 @@ package com.geeksville.mesh.database.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import com.geeksville.mesh.MeshProtos +import com.geeksville.mesh.Portnums +import com.google.protobuf.TextFormat +import java.io.IOException @Entity(tableName = "packet") @@ -12,6 +16,25 @@ data class Packet(@PrimaryKey val uuid: String, @ColumnInfo(name = "message") val raw_message: String ) { - - + val proto: MeshProtos.MeshPacket? + get() { + if (message_type == "packet") { + val builder = MeshProtos.MeshPacket.newBuilder() + try { + TextFormat.getParser().merge(raw_message, builder) + return builder.build() + } catch (e: IOException) { + } + } + return null + } + val position: MeshProtos.Position? + get() { + return proto?.run { + if (hasDecoded() && decoded.portnumValue == Portnums.PortNum.POSITION_APP_VALUE) { + return MeshProtos.Position.parseFrom(decoded.payload) + } + return null + } + } } \ No newline at end of file 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 217ee0f29..236c352e9 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -163,15 +163,13 @@ class MeshService : Service(), Logging { */ @SuppressLint("MissingPermission") @UiThread - private fun startLocationRequests() { + private fun startLocationRequests(requestInterval: Long) { // FIXME - currently we don't support location reading without google play if (fusedLocationClient == null && isGooglePlayAvailable(this)) { GeeksvilleApplication.analytics.track("location_start") // Figure out how many users needed to use the phone GPS - + val request = LocationRequest.create().apply { - interval = - 5 * 60 * 1000 // FIXME, do more like once every 5 mins while we are connected to our radio _and_ someone else is in the mesh - + interval = requestInterval priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val builder = LocationSettingsRequest.Builder().addLocationRequest(request) @@ -938,19 +936,28 @@ class MeshService : Service(), Logging { private fun onNodeDBChanged() { maybeUpdateServiceStatusNotification() - // we don't ask for GPS locations from android if our device has a built in GPS - // Note: myNodeInfo can go away if we lose connections, so it might be null - if (myNodeInfo?.hasGPS != true) { - // If we have at least one other person in the mesh, send our GPS position otherwise stop listening to GPS + serviceScope.handledLaunch(Dispatchers.Main) { + setupLocationRequest() + } + } - serviceScope.handledLaunch(Dispatchers.Main) { - if (numOnlineNodes >= 2) - startLocationRequests() - else - stopLocationRequests() - } - } else - debug("Our radio has a built in GPS, so not reading GPS in phone") + private var locationRequestInterval: Long = 0; + private fun setupLocationRequest () { + val desiredInterval: Long = if (myNodeInfo?.hasGPS == true) { + 0L // no requests when device has GPS + } else if (numOnlineNodes < 2) { + 5 * 60 * 1000L // send infrequently, device needs these requests to set its clock + } else { + radioConfig?.preferences?.positionBroadcastSecs?.times( 1000L) ?: 5 * 60 * 1000L + } + + debug("desired location request $desiredInterval, current $locationRequestInterval") + + if (desiredInterval != locationRequestInterval) { + if (locationRequestInterval > 0) stopLocationRequests() + if (desiredInterval > 0) startLocationRequests(desiredInterval) + locationRequestInterval = desiredInterval + } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/LocationUtils.kt b/app/src/main/java/com/geeksville/mesh/ui/LocationUtils.kt index 10df4cacc..eed32c702 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/LocationUtils.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/LocationUtils.kt @@ -1,5 +1,6 @@ package com.geeksville.mesh.ui +import com.geeksville.mesh.MeshProtos import kotlin.math.cos import kotlin.math.sin @@ -124,6 +125,14 @@ fun latLongToMeter( return 6366000 * tt } +// Same as above, but takes Mesh Position proto. +fun positionToMeter(a: MeshProtos.Position, b: MeshProtos.Position): Double { + return latLongToMeter( + a.latitudeI * 1e-7, + a.longitudeI * 1e-7, + b.latitudeI * 1e-7, + b.longitudeI * 1e-7) +} /** * Convert degrees/mins/secs to a single double * @@ -186,4 +195,4 @@ fun bearing( */ fun radToBearing(rad: Double): Double { return (Math.toDegrees(rad) + 360) % 360 -} \ No newline at end of file +} diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index db3e1a6da..45f8e5cf0 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -20,6 +20,10 @@ android:id="@+id/advanced_settings" app:showAsAction="withText" android:title="@string/advanced_settings" /> + Okay You must set a region! Region + Save messages as csv...