mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-16 00:21:35 +10:00
Arrival time fix perhaps (#11274)
* Add explicit presence for MeshPacket.rx_time (arrival time) rx_time is now proto3 optional with a has_rx_time presence bit, matching the rx_rssi treatment. A node with no GPS and no phone connected yet has no time source at all, so a bare 0 was indistinguishable from a genuine 1970-01-01 reading; downstream consumers (replay packets, JSON serialization) now check has_rx_time instead of the value. * Dedupe rx_time stamping into a shared helper; trim a debug log string Extract the repeated haveTime/rx_time/has_rx_time stamp logic (5 call sites across Router.cpp, MeshBeaconModule.cpp, MeshService.cpp) into Router::computeRxTimeStamp()/stampRxTime(). Also shorten the new RTC.cpp LOG_DEBUG string. Saves 48 bytes of flash on rak4631 (measured), no behavior change. * Fix has_rx_rssi presence carried unconditionally through StoreForward replay preparePayload() set has_rx_rssi = true unconditionally on replay, regardless of whether the packet's rx_rssi at store time was a genuine measurement (e.g. MQTT-relayed packets carry no real RSSI). Store the presence bit alongside rx_rssi in PacketHistoryStruct and restore it on replay instead. Flagged by Copilot on #11271 (same root cause the has_rx_time explicit presence work fixes) but never addressed before that PR merged. * Trim comment blocks to the repo's 1-2 line guideline .github/copilot-instructions.md:338 caps code comments at 1-2 lines; several blocks added across the rx_time explicit-presence work ran well past that. Also consolidates Time.cpp's file-level doc comment into Time.h, where the rest of the Time:: API contract already lives. No behavior change. * Add rx_time explicit-presence test coverage - test_meshpacket_serializer: has_rx_time=false fixture plus tests asserting JsonSerialize/JsonSerializeEncrypted emit 0 rather than leaking the millis() placeholder, alongside the has_rx_time=true baseline. - test_stream_api: two tests driving a real PhoneAPI handshake (want_config_id through STATE_SEND_PACKETS) that simulate a phone time-giving transaction arriving before vs. after a queued packet is drained - covering both the reconciled and the ships-with-placeholder-absent paths of MeshService::reconcilePendingRxTimes(). * Fix three correctness issues flagged in review - Time.h: drop the reserved-identifier include guard (_MT_TIME_H); pragma once already covers it, matching convention elsewhere (e.g. RTC.h). - Time.cpp: rebase getMillis64()'s wrap accumulator when the test seam swaps clock sources, so a real<->injected clock jump isn't miscounted as a genuine 32-bit wrap. - NodeInfoModule: the 12h reply-suppression window is a local dedup duration, not a wall-clock reading - switch it to Time::getMillis64() so RTC-quality jumps and replayed packets' stale rx_time can't perturb it. - StoreForwardModule: has_rx_time was derived from *current* RTC quality at replay time rather than stored at capture time, so a history entry saved while time-blind could be misreported as a valid epoch once the clock later improved. Persist the presence bit in PacketHistoryStruct instead. * tryfix CI * post review fixes * more test fixes
This commit is contained in:
@@ -342,7 +342,7 @@ void MeshBeaconBroadcastModule::sendBeacon()
|
||||
p->hop_limit = 0; // all beacon packets are zero hopped to limit spamming.
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
p->want_ack = false;
|
||||
p->rx_time = getValidTime(RTCQualityFromNet);
|
||||
stampRxTime(p);
|
||||
};
|
||||
|
||||
// ── Packet type decisions ────────────────────────────────────────────────
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "NodeStatus.h"
|
||||
#include "Router.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "UptimeClock.h"
|
||||
#include "configuration.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "main.h"
|
||||
@@ -33,7 +34,9 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
|
||||
// Suppress replies to senders we've replied to recently (12H window)
|
||||
if (mp.decoded.want_response && !isFromUs(&mp)) {
|
||||
const NodeNum sender = getFrom(&mp);
|
||||
const uint32_t now = mp.rx_time ? mp.rx_time : getTime();
|
||||
// A local dedup window, not a wall-clock reading - uptime avoids RTC-quality jumps and
|
||||
// replayed packets' stale rx_time perturbing it.
|
||||
const uint32_t now = (uint32_t)(Time::getMillis64() / 1000);
|
||||
auto it = lastNodeInfoSeen.find(sender);
|
||||
if (it != lastNodeInfoSeen.end()) {
|
||||
uint32_t sinceLast = now >= it->second ? now - it->second : 0;
|
||||
|
||||
@@ -193,6 +193,9 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
|
||||
}
|
||||
|
||||
this->packetHistory[this->packetHistoryTotalCount].time = getTime();
|
||||
// getTime() silently falls back to a boot-relative count with no RTC source at all; record
|
||||
// whether it was actually trustworthy so replay doesn't have to guess from current quality.
|
||||
this->packetHistory[this->packetHistoryTotalCount].has_rx_time = (getRTCQuality() >= RTCQualityFromNet);
|
||||
this->packetHistory[this->packetHistoryTotalCount].to = mp.to;
|
||||
this->packetHistory[this->packetHistoryTotalCount].channel = mp.channel;
|
||||
this->packetHistory[this->packetHistoryTotalCount].from = getFrom(&mp);
|
||||
@@ -201,6 +204,7 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
|
||||
this->packetHistory[this->packetHistoryTotalCount].emoji = (bool)p.emoji;
|
||||
this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
|
||||
this->packetHistory[this->packetHistoryTotalCount].rx_rssi = mp.rx_rssi;
|
||||
this->packetHistory[this->packetHistoryTotalCount].has_rx_rssi = mp.has_rx_rssi;
|
||||
this->packetHistory[this->packetHistoryTotalCount].rx_snr = mp.rx_snr;
|
||||
this->packetHistory[this->packetHistoryTotalCount].hop_start = mp.hop_start;
|
||||
this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
|
||||
@@ -257,9 +261,10 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t
|
||||
p->channel = this->packetHistory[i].channel;
|
||||
p->decoded.reply_id = this->packetHistory[i].reply_id;
|
||||
p->rx_time = this->packetHistory[i].time;
|
||||
p->has_rx_time = this->packetHistory[i].has_rx_time; // presence captured at store time, not replay time
|
||||
p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
|
||||
p->rx_rssi = this->packetHistory[i].rx_rssi;
|
||||
p->has_rx_rssi = true; // rx_rssi has explicit presence; the stored value was a genuine measurement
|
||||
p->has_rx_rssi = this->packetHistory[i].has_rx_rssi; // presence captured at store time, not replay time
|
||||
p->rx_snr = this->packetHistory[i].rx_snr;
|
||||
p->hop_start = this->packetHistory[i].hop_start;
|
||||
p->hop_limit = this->packetHistory[i].hop_limit;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
struct PacketHistoryStruct {
|
||||
uint32_t time;
|
||||
bool has_rx_time; // whether `time` was a trustworthy epoch when captured, not a getTime() boot-relative fallback
|
||||
uint32_t to;
|
||||
uint32_t from;
|
||||
uint32_t id;
|
||||
@@ -21,6 +22,7 @@ struct PacketHistoryStruct {
|
||||
uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN];
|
||||
pb_size_t payload_size;
|
||||
int32_t rx_rssi;
|
||||
bool has_rx_rssi; // whether rx_rssi was a genuine measurement (e.g. not MQTT-relayed) when captured
|
||||
float rx_snr;
|
||||
uint8_t hop_start;
|
||||
uint8_t hop_limit;
|
||||
|
||||
@@ -132,8 +132,13 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *
|
||||
}
|
||||
|
||||
// Display "Health From: ..." on its own
|
||||
char agoStr[16];
|
||||
if (agoSecs == SINCE_UNKNOWN)
|
||||
snprintf(agoStr, sizeof(agoStr), "?"); // no trustworthy arrival time to age against
|
||||
else
|
||||
snprintf(agoStr, sizeof(agoStr), "%us", (unsigned)agoSecs);
|
||||
char headerStr[64];
|
||||
snprintf(headerStr, sizeof(headerStr), "Health From: %s(%ds)", lastSender, (int)agoSecs);
|
||||
snprintf(headerStr, sizeof(headerStr), "Health From: %s(%s)", lastSender, agoStr);
|
||||
display->drawString(x, y, headerStr);
|
||||
|
||||
char last_temp[16];
|
||||
|
||||
@@ -154,8 +154,13 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
}
|
||||
|
||||
// Display "Pow. From: ..."
|
||||
char agoStr[16];
|
||||
if (agoSecs == SINCE_UNKNOWN)
|
||||
snprintf(agoStr, sizeof(agoStr), "?"); // no trustworthy arrival time to age against
|
||||
else
|
||||
snprintf(agoStr, sizeof(agoStr), "%us", (unsigned)agoSecs);
|
||||
char fromStr[64];
|
||||
snprintf(fromStr, sizeof(fromStr), "Pow. From: %s (%us)", lastSender, agoSecs);
|
||||
snprintf(fromStr, sizeof(fromStr), "Pow. From: %s (%s)", lastSender, agoStr);
|
||||
display->drawString(x, graphics::getTextPositions(display)[line++], fromStr);
|
||||
|
||||
// Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags
|
||||
|
||||
Reference in New Issue
Block a user