Merge pull request #396 from meshtastic/refactor-protos

add telemetry.protos
This commit is contained in:
Andre Kirchhoff
2022-03-28 15:59:15 -03:00
committed by GitHub
5 changed files with 91 additions and 97 deletions

View File

@@ -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,36 @@ 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 time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!)
val batteryLevel: Int = 0,
val voltage: Float,
val channelUtilization: Float,
val airUtilTx: Float
) : Parcelable {
companion object {
fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
}
/** Create our model object from a protobuf.
*/
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(time=${time}, batteryLevel=${batteryLevel}, voltage=${voltage}, channelUtilization=${channelUtilization}, airUtilTx=${airUtilTx})"
}
}
@@ -92,14 +116,11 @@ data class NodeInfo(
var position: Position? = 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 {
/**
* 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

View File

@@ -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(

View File

@@ -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<MeshPacket>()
@@ -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(),

View File

@@ -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()

View File

@@ -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}</a>"
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)
})
}
} */
}
}
*/