diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 7369dd899..bfceab118 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -61,11 +61,9 @@ import com.google.android.material.tabs.TabLayoutMediator import com.google.protobuf.InvalidProtocolBufferException import com.vorlonsoft.android.rate.AppRate import com.vorlonsoft.android.rate.StoreType -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel +import kotlinx.coroutines.* import java.io.FileOutputStream +import java.lang.Runnable import java.nio.charset.Charset import java.text.DateFormat import java.util.* @@ -556,10 +554,16 @@ class MainActivity : AppCompatActivity(), Logging, CREATE_CSV_FILE -> { if (resultCode == Activity.RESULT_OK) { data?.data?.let { file_uri -> + // model.allPackets is a result of a query, so we need to use observer for + // the query to materialize model.allPackets.observe(this, { packets -> if (packets != null) { - saveMessagesCSV(file_uri, packets) + // no need for observer once got non-null list model.allPackets.removeObservers(this) + // execute on the default thread pool to not block the main thread + CoroutineScope(Dispatchers.Default + Job()).handledLaunch { + saveMessagesCSV(file_uri, packets) + } } }) } @@ -1087,7 +1091,7 @@ class MainActivity : AppCompatActivity(), Logging, applicationContext.contentResolver.openFileDescriptor(file_uri, "w")?.use { FileOutputStream(it.fileDescriptor).use { fs -> // Write header - fs.write(("from,snr,time,dist\n").toByteArray()); + fs.write(("from,rssi,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 @@ -1097,13 +1101,9 @@ class MainActivity : AppCompatActivity(), Logging, 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() - ) + val dist = positionToMeter(my_position!!, position).roundToInt() + fs.write("%x,%d,%f,%d,%d\n".format(packet_proto.from,packet_proto.rxRssi, + packet_proto.rxSnr, packet_proto.rxTime, dist).toByteArray()) } } } diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index bd8bacc85..f4864dd91 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -82,7 +82,9 @@ data class Position( data class NodeInfo( val num: Int, // This is immutable, and used as a key var user: MeshUser? = null, - var position: Position? = null + var position: Position? = null, + var snr: Float = Float.MAX_VALUE, + var rssi: Int = Int.MAX_VALUE ) : Parcelable { /// Return the last time we've seen this node in secs since 1970 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 d6841c391..9a0d41a98 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -717,6 +717,7 @@ class MeshService : Service(), Logging { // Handle new style position info Portnums.PortNum.POSITION_APP_VALUE -> { val u = MeshProtos.Position.parseFrom(data.payload) + debug("position_app ${packet.from} ${u.toOneLineString()}") handleReceivedPosition(packet.from, u, dataPacket.time) } @@ -827,6 +828,7 @@ class MeshService : Service(), Logging { defaultTime: Long = System.currentTimeMillis() ) { updateNodeInfo(fromNum) { + debug("update ${it.user?.longName} with ${p.toOneLineString()}") it.position = Position(p) updateNodeInfoTime(it, (defaultTime / 1000).toInt()) } @@ -923,6 +925,8 @@ class MeshService : Service(), Logging { updateNodeInfo(fromNum) { // Update our last seen based on any valid timestamps. If the device didn't provide a timestamp make one updateNodeInfoTime(it, rxTime) + it.snr = packet.rxSnr + it.rssi = packet.rxRssi } handleReceivedData(packet) diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 66bc2c270..0649aa429 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -21,6 +21,7 @@ import com.geeksville.mesh.databinding.NodelistFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.util.formatAgo import java.net.URLEncoder +import kotlin.math.roundToInt class UsersFragment : ScreenFragment("Users"), Logging { @@ -41,6 +42,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { val batteryPctView = itemView.batteryPercentageView val lastTime = itemView.lastConnectionView val powerIcon = itemView.batteryIcon + val signalView = itemView.signalView } private val nodesAdapter = object : RecyclerView.Adapter() { @@ -137,10 +139,22 @@ class UsersFragment : ScreenFragment("Users"), Logging { } else { holder.distanceView.visibility = View.INVISIBLE } - renderBattery(n.batteryPctLevel, holder) holder.lastTime.text = formatAgo(n.lastSeen); + + if ((n.num == ourNodeInfo?.num) || (n.snr > 100f)) { + holder.signalView.visibility = View.INVISIBLE + } else { + val text = if (n.rssi < 0) { + "rssi:${n.rssi} snr:${n.snr.roundToInt()}" + } else { + // Older devices do not send rssi. Remove this branch once upgraded past 1.2.1 + "snr:${n.snr.roundToInt()}" + } + holder.signalView.text = text + holder.signalView.visibility = View.VISIBLE + } } private var nodes = arrayOf() diff --git a/app/src/main/res/layout/adapter_node_layout.xml b/app/src/main/res/layout/adapter_node_layout.xml index f5577aaac..b30bc1394 100644 --- a/app/src/main/res/layout/adapter_node_layout.xml +++ b/app/src/main/res/layout/adapter_node_layout.xml @@ -60,7 +60,7 @@ android:layout_marginBottom="8dp" android:text="@string/sample_coords" android:textAppearance="@style/TextAppearance.AppCompat.Small" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@+id/distance_view" app:layout_constraintTop_toBottomOf="@+id/imageView" app:layout_constraintVertical_bias="0.0" /> @@ -92,7 +92,8 @@ android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:visibility="visible" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/batteryIcon" + app:layout_constraintBottom_toTopOf="@id/signalView" app:layout_constraintEnd_toStartOf="@+id/lastConnectionView" app:srcCompat="@drawable/ic_antenna_24" /> @@ -106,6 +107,17 @@ app:layout_constraintBottom_toBottomOf="@+id/lastCommIcon" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/lastCommIcon" /> + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 93413f1e8..c91839d96 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -90,7 +90,7 @@ Protocol stress test Advanced settings Firmware update required - The radio firmware is too old to talk to this application, please go to the settings pane and choose "Update Firmware". For more information on this see our wiki. + The radio firmware is too old to talk to this application, please go to the settings pane and choose "Update Firmware". For more information on this see our Firmware Installation guide on Github. Okay You must set a region! Region