From a79470b018c83201db26b3069e66a7992e0bdf9e Mon Sep 17 00:00:00 2001 From: andrekir Date: Sat, 26 Mar 2022 17:44:59 -0300 Subject: [PATCH 1/7] remove batteryLevel from Position --- .../main/java/com/geeksville/mesh/model/NodeDB.kt | 12 +++--------- .../com/geeksville/mesh/service/MockInterface.kt | 1 - 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt index 01ac4e18c..fee2dfa67 100644 --- a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt +++ b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt @@ -10,15 +10,9 @@ import com.geeksville.mesh.Position /// NodeDB lives inside the UIViewModel, but it needs a backpointer to reach the service class NodeDB(private val ui: UIViewModel) { private val testPositions = arrayOf( - Position(32.776665, -96.796989, 35, 123, 40), // dallas - Position(32.960758, -96.733521, 35, 456, 50), // richardson - Position( - 32.912901, - -96.781776, - 35, - 789, - 60 - ) // north dallas + Position(32.776665, -96.796989, 35, 123), // dallas + Position(32.960758, -96.733521, 35, 456), // richardson + Position(32.912901, -96.781776, 35, 789) // north dallas ) val testNodeNoPosition = NodeInfo( diff --git a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt index 9075bf157..93aba7077 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt @@ -159,7 +159,6 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi position = MeshProtos.Position.newBuilder().apply { latitudeI = Position.degI(lat) longitudeI = Position.degI(lon) - batteryLevel = 42 altitude = 35 time = (System.currentTimeMillis() / 1000).toInt() }.build() From 11bbf02bf95c9f02cbbe6a08f172298015101d5f Mon Sep 17 00:00:00 2001 From: andrekir Date: Sat, 26 Mar 2022 18:10:40 -0300 Subject: [PATCH 2/7] add batteryLevel from Telemetry --- .../main/java/com/geeksville/mesh/NodeInfo.kt | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index 6b1f8ad68..7970938e2 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -19,8 +19,7 @@ data class MeshUser( val longName: String, val shortName: String, val hwModel: MeshProtos.HardwareModel -) : - Parcelable { +) : Parcelable { override fun toString(): String { return "MeshUser(id=${id.anonymize}, longName=${longName.anonymize}, shortName=${shortName.anonymize}, hwModel=${hwModelString})" @@ -31,10 +30,8 @@ data class MeshUser( * */ val hwModelString: String? get() = - if (hwModel == MeshProtos.HardwareModel.UNSET) - null - else - hwModel.name.replace('_', '-').replace('p', '.').toLowerCase() + if (hwModel == MeshProtos.HardwareModel.UNSET) null + else hwModel.name.replace('_', '-').replace('p', '.').lowercase() } @Serializable @@ -43,8 +40,7 @@ data class Position( val latitude: Double, val longitude: Double, val altitude: Int, - val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!) - val batteryPctLevel: Int = 0 + val time: Int = currentTime() // default to current time in secs (NOT MILLISECONDS!) ) : Parcelable { companion object { /// Convert to a double representation of degrees @@ -61,8 +57,7 @@ data class Position( degD(p.latitudeI), degD(p.longitudeI), p.altitude, - if (p.time != 0) p.time else defaultTime, - p.batteryLevel + if (p.time != 0) p.time else defaultTime ) /// @return distance in meters to some other node (or null if unknown) @@ -79,7 +74,31 @@ data class Position( } override fun toString(): String { - return "Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=${time}, batteryPctLevel=${batteryPctLevel})" + return "Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=${time})" + } +} + + +@Serializable +@Parcelize +data class Telemetry( + val batteryLevel: Int = 0, + val voltage: Float, + val channelUtilization: Float, + val airUtilTx: Float, +) : Parcelable { + + /** Create our model object from a protobuf. + */ + constructor(t: TelemetryProtos.Telemetry) : this( + t.batteryLevel, + t.voltage, + t.channelUtilization, + t.airUtilTx, + ) + + override fun toString(): String { + return "Telemetry(batteryLevel=${batteryLevel}, voltage=${voltage}, channelUtilization=${channelUtilization}, airUtilTx=${airUtilTx})" } } @@ -95,11 +114,7 @@ data class NodeInfo( var lastHeard: Int = 0 // the last time we've seen this node in secs since 1970 ) : Parcelable { - /** - * Return the last time we've seen this node in secs since 1970 - */ - - val batteryPctLevel get() = position?.batteryPctLevel + val batteryPctLevel get() = telemetry?.batteryLevel /** * true if the device was heard from recently From 472f989b65b56312703ef56933de21b4aec3ad99 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sat, 26 Mar 2022 23:38:58 -0300 Subject: [PATCH 3/7] tag telemetry in NodeInfo --- app/src/main/java/com/geeksville/mesh/NodeInfo.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index 7970938e2..c3f66288c 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -109,6 +109,7 @@ data class NodeInfo( val num: Int, // This is immutable, and used as a key var user: MeshUser? = null, var position: Position? = null, + var telemetry: Telemetry? = null, var snr: Float = Float.MAX_VALUE, var rssi: Int = Int.MAX_VALUE, var lastHeard: Int = 0 // the last time we've seen this node in secs since 1970 From 373500ae8ecff685a5da477d9dc2e5e6a06f16a9 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 27 Mar 2022 18:43:24 -0300 Subject: [PATCH 4/7] point telemetry to new proto reference --- app/src/main/java/com/geeksville/mesh/NodeInfo.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index c3f66288c..981558d07 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -90,7 +90,7 @@ data class Telemetry( /** Create our model object from a protobuf. */ - constructor(t: TelemetryProtos.Telemetry) : this( + constructor(t: TelemetryProtos.DeviceMetrics) : this( t.batteryLevel, t.voltage, t.channelUtilization, From d8a5fff86eee1d513d7ad1ece4c48afec7b77871 Mon Sep 17 00:00:00 2001 From: andrekir Date: Mon, 28 Mar 2022 15:48:27 -0300 Subject: [PATCH 5/7] fix NodeInfo telemetry proto --- .../main/java/com/geeksville/mesh/NodeInfo.kt | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index 981558d07..fb52b6f21 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -82,23 +82,28 @@ data class Position( @Serializable @Parcelize data class Telemetry( + val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!) val batteryLevel: Int = 0, val voltage: Float, val channelUtilization: Float, - val airUtilTx: Float, + val airUtilTx: Float ) : Parcelable { + companion object { + fun currentTime() = (System.currentTimeMillis() / 1000).toInt() + } /** Create our model object from a protobuf. */ - constructor(t: TelemetryProtos.DeviceMetrics) : this( - t.batteryLevel, - t.voltage, - t.channelUtilization, - t.airUtilTx, + constructor(p: TelemetryProtos.Telemetry, defaultTime: Int = currentTime()) : this( + if (p.time != 0) p.time else defaultTime, + p.deviceMetrics.batteryLevel, + p.deviceMetrics.voltage, + p.deviceMetrics.channelUtilization, + p.deviceMetrics.airUtilTx ) override fun toString(): String { - return "Telemetry(batteryLevel=${batteryLevel}, voltage=${voltage}, channelUtilization=${channelUtilization}, airUtilTx=${airUtilTx})" + return "Telemetry(time=${time}, batteryLevel=${batteryLevel}, voltage=${voltage}, channelUtilization=${channelUtilization}, airUtilTx=${airUtilTx})" } } @@ -109,10 +114,10 @@ data class NodeInfo( val num: Int, // This is immutable, and used as a key var user: MeshUser? = null, var position: Position? = null, - var telemetry: Telemetry? = null, var snr: Float = Float.MAX_VALUE, var rssi: Int = Int.MAX_VALUE, - var lastHeard: Int = 0 // the last time we've seen this node in secs since 1970 + var lastHeard: Int = 0, // the last time we've seen this node in secs since 1970 + var telemetry: Telemetry? = null ) : Parcelable { val batteryPctLevel get() = telemetry?.batteryLevel From 0c135c4502d924276ca175b95cf5d21f92cab9a6 Mon Sep 17 00:00:00 2001 From: andrekir Date: Mon, 28 Mar 2022 15:50:33 -0300 Subject: [PATCH 6/7] handle received telemetry portnums --- .../geeksville/mesh/service/MeshService.kt | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) 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 8faf0af33..a52ca08c5 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -780,6 +780,14 @@ class MeshService : Service(), Logging { handleReceivedUser(packet.from, u) } + // Handle new telemetry info + Portnums.PortNum.TELEMETRY_APP_VALUE -> { + var u = TelemetryProtos.Telemetry.parseFrom(data.payload) + if (u.time == 0 && packet.rxTime != 0) + u = u.toBuilder().setTime(packet.rxTime).build() + handleReceivedTelemetry(packet.from, u, dataPacket.time) + } + // Handle new style routing info Portnums.PortNum.ROUTING_APP_VALUE -> { shouldBroadcast = @@ -892,6 +900,17 @@ class MeshService : Service(), Logging { } } + /// Update our DB of users based on someone sending out a User subpacket + private fun handleReceivedTelemetry( + fromNum: Int, + p: TelemetryProtos.Telemetry, + defaultTime: Long = System.currentTimeMillis() + ) { + updateNodeInfo(fromNum) { + it.telemetry = Telemetry(p, (defaultTime / 1000L).toInt()) + } + } + /// If packets arrive before we have our node DB, we delay parsing them until the DB is ready // private val earlyReceivedPackets = mutableListOf() @@ -1269,12 +1288,18 @@ class MeshService : Service(), Logging { it.position = Position(info.position) } + if (info.hasTelemetry()) { + // For the local node, it might not be able to update its times because it doesn't have a valid GPS reading yet + // so if the info is for _our_ node we always assume time is current + it.telemetry = Telemetry(info.telemetry) + } + it.lastHeard = info.lastHeard } } private fun handleNodeInfo(info: MeshProtos.NodeInfo) { - debug("Received nodeinfo num=${info.num}, hasUser=${info.hasUser()}, hasPosition=${info.hasPosition()}") + debug("Received nodeinfo num=${info.num}, hasUser=${info.hasUser()}, hasPosition=${info.hasPosition()}, hasTelemetry=${info.hasTelemetry()}") val packetToSave = Packet( UUID.randomUUID().toString(), From 143bb255d11e8907112b0cf58573d44c55980cf4 Mon Sep 17 00:00:00 2001 From: andrekir Date: Mon, 28 Mar 2022 15:52:32 -0300 Subject: [PATCH 7/7] update nodelist to telemetry deviceMetrics --- .../com/geeksville/mesh/ui/UsersFragment.kt | 93 +++++-------------- 1 file changed, 24 insertions(+), 69 deletions(-) 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 2e93c72e8..2a182643e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -1,7 +1,6 @@ package com.geeksville.mesh.ui import android.os.Bundle -import android.text.Html import android.text.method.LinkMovementMethod import android.view.LayoutInflater import android.view.View @@ -20,7 +19,6 @@ import com.geeksville.mesh.model.UIViewModel import com.geeksville.util.formatAgo import dagger.hilt.android.AndroidEntryPoint import java.net.URLEncoder -import kotlin.math.roundToInt @AndroidEntryPoint class UsersFragment : ScreenFragment("Users"), Logging { @@ -123,7 +121,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { "utf-8" ) }'>${coords}" - holder.coordsView.text = HtmlCompat.fromHtml(html, Html.FROM_HTML_MODE_LEGACY) + holder.coordsView.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY) holder.coordsView.movementMethod = LinkMovementMethod.getInstance() holder.coordsView.visibility = View.VISIBLE } else { @@ -138,32 +136,30 @@ class UsersFragment : ScreenFragment("Users"), Logging { } else { holder.distanceView.visibility = View.INVISIBLE } - renderBattery(n.batteryPctLevel, holder) + renderBattery(n.batteryPctLevel, n.telemetry?.voltage, holder) holder.lastTime.text = formatAgo(n.lastHeard) - 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 - } - if (n.num == ourNodeInfo?.num) { val info = model.myNodeInfo.value if (info != null) { - val channelUtilizationText = String.format("%.1f", info.channelUtilization) - val airUtilTxText = String.format("%.1f", info.airUtilTx) - val combinedText = "ChUtil $channelUtilizationText% AirUtilTX $airUtilTxText%" - holder.signalView.text = combinedText + val text = + String.format( + "ChUtil %.1f%% AirUtilTX %.1f%%", + n.telemetry?.channelUtilization ?: info.channelUtilization, + n.telemetry?.airUtilTx ?: info.airUtilTx + ) + holder.signalView.text = text holder.signalView.visibility = View.VISIBLE } + } else { + if ((n.snr < 100f) && (n.rssi < 0)) { + val text = String.format("rssi:%d snr:%.1f", n.rssi, n.snr) + holder.signalView.text = text + holder.signalView.visibility = View.VISIBLE + } else { + holder.signalView.visibility = View.INVISIBLE + } } } @@ -178,11 +174,15 @@ class UsersFragment : ScreenFragment("Users"), Logging { private fun renderBattery( battery: Int?, + voltage: Float?, holder: ViewHolder ) { val (image, text) = when (battery) { - in 1..100 -> Pair(R.drawable.ic_battery_full_24, "$battery%") + in 1..100 -> Pair( + R.drawable.ic_battery_full_24, + String.format("%d%% %.2fV", battery, voltage ?: 0) + ) 0 -> Pair(R.drawable.ic_power_plug_24, "") else -> Pair(R.drawable.ic_battery_full_24, "?") } @@ -199,7 +199,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = NodelistFragmentBinding.inflate(inflater, container, false) return binding.root } @@ -210,53 +210,8 @@ class UsersFragment : ScreenFragment("Users"), Logging { binding.nodeListView.adapter = nodesAdapter binding.nodeListView.layoutManager = LinearLayoutManager(requireContext()) - model.nodeDB.nodes.observe(viewLifecycleOwner, { + model.nodeDB.nodes.observe(viewLifecycleOwner) { nodesAdapter.onNodesChanged(it.values) - }) - } -} - - -/* - - - if (false) { // hide the firmware update button for now, it is kinda ugly and users don't need it yet - /// Create a software update button - val context = ContextAmbient.current - RadioInterfaceService.getBondedDeviceAddress(context)?.let { macAddress -> - Button( - onClick = { - SoftwareUpdateService.enqueueWork( - context, - SoftwareUpdateService.startUpdateIntent(macAddress) - ) - } - ) { - Text(text = "Update firmware") - } - } - } - } } - - - - /* FIXME - doens't work yet - probably because I'm not using release keys - // If account is null, then show the signin button, otherwise - val context = ambient(ContextAmbient) - val account = GoogleSignIn.getLastSignedInAccount(context) - if (account != null) - Text("We have an account") - else { - Text("No account yet") - if (context is Activity) { - Button("Google sign-in", onClick = { - val signInIntent: Intent = UIState.googleSignInClient.signInIntent - context.startActivityForResult(signInIntent, MainActivity.RC_SIGN_IN) - }) - } - } */ } } - -*/ \ No newline at end of file