@@ -58,6 +58,7 @@ import org.meshtastic.core.common.util.MetricFormatter
5858import org.meshtastic.core.model.TelemetryType
5959import org.meshtastic.core.model.util.TimeConstants.MS_PER_SEC
6060import org.meshtastic.core.model.util.formatUptime
61+ import org.meshtastic.core.model.util.rxTimeOrNull
6162import org.meshtastic.core.resources.Res
6263import org.meshtastic.core.resources.busy_noise_floor
6364import org.meshtastic.core.resources.clear
@@ -136,7 +137,7 @@ internal sealed interface SignalLogEntry {
136137 }
137138
138139 data class PacketEntry (val meshPacket : MeshPacket , val index : Int ) : SignalLogEntry {
139- override val timeSeconds: Int = meshPacket.rx_time
140+ override val timeSeconds: Int = meshPacket.rxTimeOrNull() ? : 0
140141
141142 // MeshPacket.id repeats: it is unique only per originating node, and retransmissions are stored per reception.
142143 // The source-list index disambiguates, as it does for local stats.
@@ -152,7 +153,13 @@ fun SignalMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit, m
152153 val timeFrame by viewModel.timeFrame.collectAsStateWithLifecycle()
153154 val availableTimeFrames by viewModel.availableTimeFrames.collectAsStateWithLifecycle()
154155 val threshold = timeFrame.timeThreshold()
155- val signalData = state.signalMetrics.filter { it.rx_time.toLong() >= threshold }
156+ // An unstamped packet has no place on a time axis. Dropping it here is the invariant every downstream
157+ // `rxTimeOrNull() ?: 0` in this file relies on, so none of them can render or plot 1970.
158+ val signalData =
159+ state.signalMetrics.filter { packet ->
160+ val rxTime = packet.rxTimeOrNull() ? : return @filter false
161+ rxTime.toLong() >= threshold
162+ }
156163 val localStatsData = state.localStats.filter { it.time.toLong() >= threshold && it.local_stats != null }
157164 val data = remember(signalData, localStatsData) { buildSignalLog(signalData, localStatsData) }
158165 val hasNoiseFloor = remember(localStatsData) { localStatsData.any { it.local_stats?.noise_floor != 0 } }
@@ -353,11 +360,13 @@ private fun SignalMetricsChart(
353360 lineModel { series(x = busyFloorData.map { it.time }, y = busyFloorData.map { BUSY_FLOOR_DBM }) }
354361 }
355362 if (rssiData.isNotEmpty()) {
356- lineModel { series(x = rssiData.map { it.rx_time }, y = rssiData.mapNotNull { it.rx_rssi }) }
363+ lineModel {
364+ series(x = rssiData.map { it.rxTimeOrNull() ? : 0 }, y = rssiData.mapNotNull { it.rx_rssi })
365+ }
357366 }
358367 if (snrData.isNotEmpty()) {
359368 /* Use a separate lineModel call to associate SNR with the right axis. */
360- lineModel { series(x = snrData.map { it.rx_time }, y = snrData.map { it.rx_snr }) }
369+ lineModel { series(x = snrData.map { it.rxTimeOrNull() ? : 0 }, y = snrData.map { it.rx_snr }) }
361370 }
362371 }
363372 }
@@ -553,7 +562,7 @@ private fun LocalStatsCard(telemetry: Telemetry, isSelected: Boolean, onClick: (
553562
554563@Composable
555564private fun SignalMetricsCard (meshPacket : MeshPacket , isSelected : Boolean , onClick : () -> Unit ) {
556- val time = meshPacket.rx_time .toLong() * MS_PER_SEC
565+ val time = ( meshPacket.rxTimeOrNull() ? : 0 ) .toLong() * MS_PER_SEC
557566 SelectableMetricCard (isSelected = isSelected, onClick = onClick) {
558567 Row (modifier = Modifier .fillMaxWidth(), verticalAlignment = Alignment .CenterVertically ) {
559568 /* Data */
0 commit comments