mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-15 07:51:19 +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:
@@ -12,6 +12,7 @@
|
||||
#include "Power.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "UptimeClock.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/draw/MessageRenderer.h"
|
||||
#include "main.h"
|
||||
@@ -180,6 +181,36 @@ NodeNum MeshService::getNodenumFromRequestId(uint32_t request_id)
|
||||
return nodenum;
|
||||
}
|
||||
|
||||
// Back-calculate the real epoch for any queued packet still carrying a millis() rx_time
|
||||
// placeholder, now that the clock is trustworthy.
|
||||
void MeshService::reconcilePendingRxTimes()
|
||||
{
|
||||
const uint32_t nowEpoch = getValidTime(RTCQualityFromNet);
|
||||
if (nowEpoch == 0) // called before the clock was actually valid - nothing to reconcile against
|
||||
return;
|
||||
const uint32_t nowMillis = Time::getMillis();
|
||||
|
||||
// Rotate the queue once. TypedQueue is strictly FIFO on both backends, so dequeueing and
|
||||
// re-enqueueing every element in turn leaves the delivery order unchanged.
|
||||
for (int remaining = toPhoneQueue.numUsed(); remaining > 0; remaining--) {
|
||||
meshtastic_MeshPacket *p = toPhoneQueue.dequeuePtr(0);
|
||||
if (!p) // drained from under us - nothing left to rotate
|
||||
break;
|
||||
if (!p->has_rx_time) {
|
||||
// Unsigned subtraction is wraparound-safe; rx_time is a 32-bit wire field, so the
|
||||
// placeholder was never wider than 32 bits to begin with.
|
||||
const uint32_t elapsedMs = nowMillis - p->rx_time;
|
||||
p->rx_time = nowEpoch - (elapsedMs / 1000);
|
||||
p->has_rx_time = true;
|
||||
}
|
||||
if (!toPhoneQueue.enqueue(p, 0)) { // mirrors sendToPhone()'s degrade-on-failure path
|
||||
LOG_CRIT("Failed to requeue a packet into toPhoneQueue!");
|
||||
releaseToPool(p);
|
||||
fromNum++; // notify observers so the phone can resync
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if MESHTASTIC_ENABLE_FRAME_INJECTION
|
||||
// Deliver a client-supplied frame into the receive pipeline as if it arrived off the LoRa chip. Mirrors
|
||||
// the portduino SimRadio SIMULATOR_APP unwrap so the same host wire format works on real hardware: the
|
||||
@@ -220,7 +251,9 @@ void MeshService::injectAsReceived(meshtastic_MeshPacket &p)
|
||||
mp->rx_rssi = -40;
|
||||
mp->has_rx_rssi = true;
|
||||
}
|
||||
mp->rx_time = getValidTime(RTCQualityFromNet);
|
||||
// dispatchReceived() restamps this when the packet re-enters the pipeline below; stamp it
|
||||
// here anyway so the packet is never observable with an unset arrival time.
|
||||
stampRxTime(mp);
|
||||
LOG_INFO("inject: RX from=0x%08x to=0x%08x id=0x%08x ch=%d %s", mp->from, mp->to, mp->id, mp->channel,
|
||||
mp->which_payload_variant == meshtastic_MeshPacket_encrypted_tag ? "encrypted" : "decoded");
|
||||
router->enqueueReceivedMessage(mp);
|
||||
@@ -258,7 +291,8 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
||||
if (p.id == 0)
|
||||
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
||||
|
||||
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
|
||||
// Record the time the packet arrived from the phone.
|
||||
stampRxTime(&p);
|
||||
|
||||
IF_SCREEN(if (p.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && p.decoded.payload.size > 0 &&
|
||||
p.to != NODENUM_BROADCAST && p.to != 0) // DM only
|
||||
@@ -595,6 +629,11 @@ bool MeshService::isToPhoneQueueEmpty()
|
||||
|
||||
uint32_t MeshService::GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp)
|
||||
{
|
||||
// rx_time may be a millis() placeholder while has_rx_time is false - don't age it as
|
||||
// wall-clock, and don't pass it off as "just now" either.
|
||||
if (!mp->has_rx_time)
|
||||
return SINCE_UNKNOWN;
|
||||
|
||||
uint32_t now = getTime();
|
||||
|
||||
uint32_t last_seen = mp->rx_time;
|
||||
|
||||
Reference in New Issue
Block a user