mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: show per-message SNR, RSSI and hop count (#2040)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
parent
639213145b
commit
9a371ee9cd
9 changed files with 794 additions and 8 deletions
|
|
@ -59,6 +59,9 @@ data class DataPacket(
|
|||
var hopLimit: Int = 0,
|
||||
var channel: Int = 0, // channel index
|
||||
var wantAck: Boolean = true, // If true, the receiver should send an ack back
|
||||
var hopStart: Int = 0,
|
||||
var snr: Float = 0f,
|
||||
var rssi: Int = 0,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
|
|
@ -107,6 +110,9 @@ data class DataPacket(
|
|||
null
|
||||
}
|
||||
|
||||
val hopsAway: Int
|
||||
get() = if (hopStart == 0 || hopLimit > hopStart) -1 else hopStart - hopLimit
|
||||
|
||||
// Autogenerated comparision, because we have a byte array
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
|
|
@ -120,8 +126,12 @@ data class DataPacket(
|
|||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt() == 1,
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readInt(),
|
||||
)
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
|
@ -138,6 +148,9 @@ data class DataPacket(
|
|||
if (status != other.status) return false
|
||||
if (hopLimit != other.hopLimit) return false
|
||||
if (wantAck != other.wantAck) return false
|
||||
if (hopStart != other.hopStart) return false
|
||||
if (snr != other.snr) return false
|
||||
if (rssi != other.rssi) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -153,6 +166,9 @@ data class DataPacket(
|
|||
result = 31 * result + hopLimit
|
||||
result = 31 * result + channel
|
||||
result = 31 * result + wantAck.hashCode()
|
||||
result = 31 * result + hopStart
|
||||
result = 31 * result + snr.hashCode()
|
||||
result = 31 * result + rssi
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -167,6 +183,9 @@ data class DataPacket(
|
|||
parcel.writeInt(hopLimit)
|
||||
parcel.writeInt(channel)
|
||||
parcel.writeInt(if (wantAck) 1 else 0)
|
||||
parcel.writeInt(hopStart)
|
||||
parcel.writeFloat(snr)
|
||||
parcel.writeInt(rssi)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
|
|
@ -185,6 +204,9 @@ data class DataPacket(
|
|||
hopLimit = parcel.readInt()
|
||||
channel = parcel.readInt()
|
||||
wantAck = parcel.readInt() == 1
|
||||
hopStart = parcel.readInt()
|
||||
snr = parcel.readFloat()
|
||||
rssi = parcel.readInt()
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<DataPacket> {
|
||||
|
|
|
|||
|
|
@ -70,8 +70,9 @@ import com.geeksville.mesh.database.entity.ReactionEntity
|
|||
AutoMigration(from = 14, to = 15),
|
||||
AutoMigration(from = 15, to = 16),
|
||||
AutoMigration(from = 16, to = 17),
|
||||
AutoMigration(from = 17, to = 18),
|
||||
],
|
||||
version = 17,
|
||||
version = 18,
|
||||
exportSchema = true,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ data class PacketEntity(
|
|||
node = getNode(data.from),
|
||||
text = data.text.orEmpty(),
|
||||
time = getShortDateTime(data.time),
|
||||
snr = snr,
|
||||
rssi = rssi,
|
||||
hopsAway = hopsAway,
|
||||
read = read,
|
||||
status = data.status,
|
||||
routingError = routingError,
|
||||
|
|
@ -70,6 +73,9 @@ data class Packet(
|
|||
@ColumnInfo(name = "packet_id", defaultValue = "0") val packetId: Int = 0,
|
||||
@ColumnInfo(name = "routing_error", defaultValue = "-1") var routingError: Int = -1,
|
||||
@ColumnInfo(name = "reply_id", defaultValue = "0") val replyId: Int = 0,
|
||||
@ColumnInfo(name = "snr", defaultValue = "0") val snr: Float = 0f,
|
||||
@ColumnInfo(name = "rssi", defaultValue = "0") val rssi: Int = 0,
|
||||
@ColumnInfo(name = "hopsAway", defaultValue = "-1") val hopsAway: Int = -1,
|
||||
)
|
||||
|
||||
@Entity(tableName = "contact_settings")
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ data class Message(
|
|||
val routingError: Int,
|
||||
val packetId: Int,
|
||||
val emojis: List<Reaction>,
|
||||
val snr: Float,
|
||||
val rssi: Int,
|
||||
val hopsAway: Int,
|
||||
) {
|
||||
fun getStatusStringRes(): Pair<Int, Int> {
|
||||
val title = if (routingError > 0) R.string.error else R.string.message_delivery_status
|
||||
|
|
|
|||
|
|
@ -673,6 +673,9 @@ class MeshService : Service(), Logging {
|
|||
hopLimit = packet.hopLimit,
|
||||
channel = if (packet.pkiEncrypted) DataPacket.PKC_CHANNEL_INDEX else packet.channel,
|
||||
wantAck = packet.wantAck,
|
||||
hopStart = packet.hopStart,
|
||||
snr = packet.rxSnr,
|
||||
rssi = packet.rxRssi
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -705,7 +708,10 @@ class MeshService : Service(), Logging {
|
|||
packetRepository.get().insertReaction(reaction)
|
||||
}
|
||||
|
||||
private fun rememberDataPacket(dataPacket: DataPacket, updateNotification: Boolean = true) {
|
||||
private fun rememberDataPacket(
|
||||
dataPacket: DataPacket,
|
||||
updateNotification: Boolean = true,
|
||||
) {
|
||||
if (dataPacket.dataType !in rememberDataType) return
|
||||
val fromLocal = dataPacket.from == DataPacket.ID_LOCAL
|
||||
val toBroadcast = dataPacket.to == DataPacket.ID_BROADCAST
|
||||
|
|
@ -722,7 +728,10 @@ class MeshService : Service(), Logging {
|
|||
contact_key = contactKey,
|
||||
received_time = System.currentTimeMillis(),
|
||||
read = fromLocal,
|
||||
data = dataPacket
|
||||
data = dataPacket,
|
||||
snr = dataPacket.snr,
|
||||
rssi = dataPacket.rssi,
|
||||
hopsAway = dataPacket.hopsAway
|
||||
)
|
||||
serviceScope.handledLaunch {
|
||||
packetRepository.get().apply {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import com.geeksville.mesh.R
|
||||
|
||||
private const val SNR_GOOD_THRESHOLD = -7f
|
||||
|
|
@ -132,7 +133,7 @@ fun LoraSignalIndicator(snr: Float, rssi: Int) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Snr(snr: Float) {
|
||||
fun Snr(snr: Float, fontSize: TextUnit = MaterialTheme.typography.labelLarge.fontSize) {
|
||||
val color: Color = if (snr > SNR_GOOD_THRESHOLD) {
|
||||
Quality.GOOD.color
|
||||
} else if (snr > SNR_FAIR_THRESHOLD) {
|
||||
|
|
@ -144,12 +145,12 @@ private fun Snr(snr: Float) {
|
|||
Text(
|
||||
text = "%s %.2fdB".format(stringResource(id = R.string.snr), snr),
|
||||
color = color,
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize
|
||||
fontSize = fontSize
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Rssi(rssi: Int) {
|
||||
fun Rssi(rssi: Int, fontSize: TextUnit = MaterialTheme.typography.labelLarge.fontSize) {
|
||||
val color: Color = if (rssi > RSSI_GOOD_THRESHOLD) {
|
||||
Quality.GOOD.color
|
||||
} else if (rssi > RSSI_FAIR_THRESHOLD) {
|
||||
|
|
@ -160,7 +161,7 @@ private fun Rssi(rssi: Int) {
|
|||
Text(
|
||||
text = "%s %ddBm".format(stringResource(id = R.string.rssi), rssi),
|
||||
color = color,
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize
|
||||
fontSize = fontSize
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -191,7 +191,15 @@ internal fun MessageList(
|
|||
onAction = onNodeMenuAction,
|
||||
onStatusClick = { showStatusDialog = msg },
|
||||
onSendReaction = { onSendReaction(it, msg.packetId) },
|
||||
isConnected = isConnected
|
||||
isConnected = isConnected,
|
||||
snr = msg.snr,
|
||||
rssi = msg.rssi,
|
||||
hopsAway = if (msg.hopsAway > 0) { "%s: %d".format(
|
||||
stringResource(id = R.string.hops_away),
|
||||
msg.hopsAway
|
||||
) } else {
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import androidx.compose.foundation.combinedClickable
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -53,6 +54,8 @@ import com.geeksville.mesh.MessageStatus
|
|||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.ui.common.components.AutoLinkText
|
||||
import com.geeksville.mesh.ui.common.components.Rssi
|
||||
import com.geeksville.mesh.ui.common.components.Snr
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
|
|
@ -74,6 +77,9 @@ internal fun MessageItem(
|
|||
onStatusClick: () -> Unit = {},
|
||||
onSendReaction: (String) -> Unit = {},
|
||||
isConnected: Boolean,
|
||||
snr: Float,
|
||||
rssi: Int,
|
||||
hopsAway: String?,
|
||||
) = Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -141,6 +147,17 @@ internal fun MessageItem(
|
|||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (!fromLocal) {
|
||||
if (hopsAway == null) {
|
||||
Snr(snr, fontSize = MaterialTheme.typography.bodySmall.fontSize)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Rssi(rssi, fontSize = MaterialTheme.typography.bodySmall.fontSize)
|
||||
} else { Text(
|
||||
text = hopsAway,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
) }
|
||||
Spacer(Modifier.weight(1f))
|
||||
}
|
||||
Text(
|
||||
text = messageTime,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
|
|
@ -181,6 +198,9 @@ private fun MessageItemPreview() {
|
|||
messageStatus = MessageStatus.DELIVERED,
|
||||
selected = false,
|
||||
isConnected = true,
|
||||
snr = 20.5f,
|
||||
rssi = 90,
|
||||
hopsAway = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue