fix(ui): give rx_snr real presence semantics end to end (#6523)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
James RichandClaude Opus 5 authored and GitHub committed 2026-07-30 16:40:41 +00:00
1 parent 4846425ff1
commit 2d20cd8a47
46 files changed
+2194 -148

No files matched your search

@@ -130,8 +130,9 @@ import org.meshtastic.core.database.entity.TracerouteNodePositionEntity
AutoMigration(from = 48, to = 49),
AutoMigration(from = 49, to = 50),
AutoMigration(from = 50, to = 51),
AutoMigration(from = 51, to = 52),
],
version = 51,
version = 52,
exportSchema = true,
)
@androidx.room3.ConstructedBy(MeshtasticDatabaseConstructor::class)
@@ -102,7 +102,8 @@ data class Packet(
@ColumnInfo(name = "data") val data: DataPacket,
@ColumnInfo(name = "packet_id", defaultValue = "0") val packetId: Int = 0,
@ColumnInfo(name = "routing_error", defaultValue = "-1") var routingError: Int = -1,
@ColumnInfo(name = "snr", defaultValue = "0") val snr: Float = 0f,
/** Null when the packet carried no snr. Rows written before schema 52 store 0 for both absent and 0 dB. */
@ColumnInfo(name = "snr") val snr: Float? = null,
/** Null when the radio reported no rssi. Rows written before schema 51 store 0 for both absent and 0 dBm. */
@ColumnInfo(name = "rssi") val rssi: Int? = null,
@ColumnInfo(name = "hopsAway", defaultValue = "-1") val hopsAway: Int = -1,
@@ -162,7 +163,8 @@ data class ReactionEntity(
@ColumnInfo(name = "user_id") val userId: String,
val emoji: String,
val timestamp: Long,
@ColumnInfo(name = "snr", defaultValue = "0") val snr: Float = 0f,
/** Null when the packet carried no snr. Rows written before schema 52 store 0 for both absent and 0 dB. */
@ColumnInfo(name = "snr") val snr: Float? = null,
/** Null when the radio reported no rssi. Rows written before schema 51 store 0 for both absent and 0 dBm. */
@ColumnInfo(name = "rssi") val rssi: Int? = null,
@ColumnInfo(name = "hopsAway", defaultValue = "-1") val hopsAway: Int = -1,
@@ -108,6 +108,29 @@ class MeshtasticDatabaseMigrationTest {
}
}
@Test
fun snrColumnsGoNullableWithoutLosingRows() = runTest {
helper.createDatabase(SNR_NULLABLE_FROM_VERSION).use { connection ->
connection.execSQL(
"INSERT INTO packet (uuid, myNodeNum, port_num, contact_key, received_time, read, data, snr, rssi) " +
"VALUES (1, 42, 1, '0^all', 1000, 1, '{}', 0.0, -70)",
)
connection.execSQL(
"INSERT INTO reactions (myNodeNum, reply_id, user_id, emoji, timestamp, snr, rssi) " +
"VALUES (42, 7, '!abc', 'X', 2000, -12.5, -70)",
)
}
helper.runMigrationsAndValidate(SNR_NULLABLE_TO_VERSION, emptyList()).use { connection ->
// A stored 0 dB must survive the recreate as 0, not become NULL: it is a real reading.
assertEquals(listOf("0.0"), queryColumn(connection, "SELECT snr FROM packet"))
assertEquals(listOf("-12.5"), queryColumn(connection, "SELECT snr FROM reactions"))
// A NULL is now storable where the column was previously NOT NULL DEFAULT 0.
connection.execSQL("UPDATE packet SET snr = NULL WHERE uuid = 1")
assertEquals(listOf(null), queryColumn(connection, "SELECT snr FROM packet"))
}
}
/** Reads one column of every row as a string, with SQL NULL surfaced as Kotlin null. */
private fun queryColumn(connection: SQLiteConnection, sql: String): List<String?> =
connection.prepare(sql).use { statement ->
@@ -130,5 +153,7 @@ class MeshtasticDatabaseMigrationTest {
const val EARLIEST_SCHEMA_VERSION = 3
const val RSSI_NULLABLE_FROM_VERSION = 50
const val RSSI_NULLABLE_TO_VERSION = 51
const val SNR_NULLABLE_FROM_VERSION = 51
const val SNR_NULLABLE_TO_VERSION = 52
}
}