Skip to content

chore(deps): bump protobufs to 2.7.26.140 (optional rx_time) - #6510

Merged
jamesarich merged 2 commits into
mainfrom
claude/protobufs-time-edits-f2b7e2
Jul 29, 2026
Merged

chore(deps): bump protobufs to 2.7.26.140 (optional rx_time)#6510
jamesarich merged 2 commits into
mainfrom
claude/protobufs-time-edits-f2b7e2

Conversation

@jamesarich

@jamesarich jamesarich commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Why

Upstream made MeshPacket.rx_time a proto3 optional fixed32 (protobufs #1025), so firmware can now say "I had no trustworthy clock when this packet arrived" instead of emitting a 0 that reads as 1970-01-01. Wire generates that as Int?, so the pin can't move without touching every call site.

The interesting part isn't the null-safety, it's the back-compat: firmware in the field still sends rx_time = 0 for exactly the same state. Absent and 0 both mean unknown, and a 1970 arrival time is never a genuine reading — so the two cases have to collapse into one rule, in one place, rather than each site sprouting a ?: 0 that silently drops the old-firmware half.

This is the same defect class as the RSSI zeros in #6507, but inverted: there, 0 was a legitimate reading being discarded; here, 0 is a sentinel that must not be trusted.

What

🛠️ Bump meshtastic-protobufs 2.7.26.138-g26db1b5-SNAPSHOT2.7.26.140-g6ceceae-SNAPSHOT.

🛠️ Add one helper, MeshPacket.rxTimeOrNull(), that folds "field absent" and "old-firmware 0" into a single null. Every one of the nine rx_time reads now goes through it.

🐛 applySenderPacketUpdate would have written the epoch into node.lastHeard for an unstamped packet (clampTimestampToNow(0)0), aging a live node to 1970. Now it keeps the node's existing lastHeard. Unreachable today — packets are normalized upstream at handleReceivedMeshPacket — but it was a live footgun one refactor away from firing.

🐛 MetricsState.oldestTimestampSeconds() let an unknown timestamp win the min(), which would collapse every metrics chart's time axis to 1970. Now mapNotNulls them out.

🧹 Normalization in handleReceivedMeshPacket collapses to a single expression, and the Message.meshTime KDoc now names both ways the timestamp can be missing.

Everything else — MeshDataMapper, isDirectSignal(), the four SignalMetrics sites, the signal-metrics CSV export — is behaviour-preserving: absent maps to the same 0 those sites already handled.

Notes for reviewers

This pins an untagged snapshot, deliberately. Upstream holds protobufs tags in lockstep with firmware stable releases, so the latest tag is still v2.7.26 (2026-06-20) while master is well ahead. Waiting for a tag here would be an open-ended stall, and the app catalog has been on a snapshot pin for a while already. Renovate may briefly try to "upgrade" us down to the v2.7.26 release, since a bare release numerically out-ranks a -g<sha>-SNAPSHOT qualifier; it self-resolves.

Deliberately out of scope: the new presence bit does not survive to the UI. handleReceivedMeshPacket still overwrites an absent rx_time with now before storing, so nothing downstream can tell "radio had no clock" from "radio was stamped at that second". Preserving it end-to-end means touching DataPacket.time, the Room column, and Message.displayTime — a separate change, not something to smuggle into a version bump.

Testing Performed

New RxTimeExtensionsTest (:core:model) covers stamped / absent / old-firmware-zero for both rxTimeOrNull() and isDirectSignal(). New MeshMessageProcessorImplTest > packets with absent rx_time get current time covers the normalization path alongside the existing zero and non-zero cases.

Both new tests were confirmed to have actually executed rather than replaying from the build cache (checked the test-results XML — a green tick on a FROM-CACHE task proves nothing).

Full baseline green:

./gradlew spotlessApply spotlessCheck detekt assembleDebug test allTests kmpSmokeCompile

Not device-verified — the behavioural guards fire only against firmware that omits rx_time, which needs a clockless node (no GPS, no phone yet connected) to reproduce for real.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Assigns a current receive timestamp for packets missing rx_time (and keeps it consistent throughout processing).
    • Avoids overwriting existing node “last heard” when no valid receive timestamp is available.
    • Improves direct-signal detection for older firmware or packets without receive-time data.
  • Improvements
    • Signal metrics now use the derived receive time consistently across lists, charts, and time-window filtering.
    • CSV exports and displayed message time are more reliable when arrival timestamps are unavailable.

MeshPacket.rx_time became a proto3 optional field upstream, so Wire now
generates it as Int? instead of Int. Old firmware still sends 0 when it has
no clock at reception, so both absent and 0 mean "unknown" — route every
call site through a single rxTimeOrNull() helper rather than scattering ?: 0
and losing the old-firmware case.

Two sites needed more than null-safety: applySenderPacketUpdate would have
written the epoch into lastHeard for an unstamped packet, and
oldestTimestampSeconds let an unknown timestamp win the min() and collapse
every chart's time axis to 1970.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the chore label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c15530a0-f200-4a6b-a333-a25823e71717

📥 Commits

Reviewing files that changed from the base of the PR and between 86b3035 and 7296cdf.

📒 Files selected for processing (2)
  • core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt
  • core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt

📝 Walkthrough

Walkthrough

Changes

Rx-time normalization

Layer / File(s) Summary
Rx-time model contract and consumers
core/model/src/commonMain/..., core/model/src/commonTest/..., gradle/libs.versions.toml
Adds nullable receive-time handling, updates data mapping and documentation, adds coverage, and bumps the protobuf artifact version.
Incoming packet timestamp normalization
core/data/src/commonMain/..., core/data/src/commonTest/...
Stamps packets lacking receive time with the current time and preserves Node.lastHeard when no normalized time is available.
Signal metrics timestamp handling
feature/node/src/commonMain/...
Uses nullable receive-time extraction for CSV export, filtering, charts, cards, and oldest-timestamp calculations.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: dependencies, bugfix

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Tests Prove The Path, Not The End State ⚠️ Warning RxTimeExtensionsTest’s isDirectSignal case checks booleans the old rx_time > 0 logic would also return, so it doesn’t prove the new path. Tighten or replace that test so it distinguishes the helper-based path, or keep only the rxTimeOrNull() cases that fail under the reverted code.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the protobufs version bump and the key behavioral change around optional rx_time handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Sibling Call Sites And Presence Semantics ✅ Passed All non-test rx_time reads now go through rxTimeOrNull(); remaining hits are comments, constructors, or tests, so no sibling call site was left behind.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt (1)

566-585: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a regression assertion for lastHeard preservation.

This test only verifies that the emitted packet receives a positive timestamp; it would still pass while the sender-node lastHeard bug remains. Capture the transform passed to updateNodeAndPersist, apply it to a remote node with an existing lastHeard, and assert that null/zero rx_time leaves it unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt`
around lines 566 - 585, Extend the test `packets with absent rx_time get current
time` to capture the transform supplied to `updateNodeAndPersist`, apply it to a
remote node whose `lastHeard` is already set, and assert that a null or zero
packet `rx_time` preserves the existing `lastHeard`. Keep the current positive
emitted-timestamp assertion, and use the captured transform rather than testing
only the emitted packet.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt`:
- Around line 186-187: Preserve the original nullable receive timestamp before
creating stampedPacket in the packet-processing flow. Update
applySenderPacketUpdate to use that raw timestamp when calculating the sender
node’s lastHeard, so null or zero values retain node.lastHeard, while downstream
consumers continue receiving the normalized preparedPacket.

In
`@feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt`:
- Line 140: Preserve unknown receive times as nullable throughout PacketEntry
and its consumers: in
feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt:140,
keep timeSeconds nullable; at 156 filter via nullable extraction, at 357-363
exclude null timestamps from plotting, and at 559 avoid rendering them as epoch
time. In
feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt:456,
omit unstamped rows or emit blank date/time CSV fields; do not use zero as a
sentinel.

---

Nitpick comments:
In
`@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt`:
- Around line 566-585: Extend the test `packets with absent rx_time get current
time` to capture the transform supplied to `updateNodeAndPersist`, apply it to a
remote node whose `lastHeard` is already set, and assert that a null or zero
packet `rx_time` preserves the existing `lastHeard`. Keep the current positive
emitted-timestamp assertion, and use the captured transform rather than testing
only the emitted packet.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ee20e89f-fca6-4500-a8c5-01cf9cf27892

📥 Commits

Reviewing files that changed from the base of the PR and between 55a7c61 and 86b3035.

📒 Files selected for processing (10)
  • core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt
  • core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt
  • core/model/src/commonMain/kotlin/org/meshtastic/core/model/Message.kt
  • core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/Extensions.kt
  • core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/MeshDataMapper.kt
  • core/model/src/commonTest/kotlin/org/meshtastic/core/model/util/RxTimeExtensionsTest.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/SignalMetrics.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/model/MetricsState.kt
  • gradle/libs.versions.toml

Review follow-up: rather than relying on the threshold comparison to
incidentally exclude them, filter unstamped packets explicitly so the
downstream `?: 0` fallbacks are provably unreachable rather than a
1970 sentinel waiting to render.

Also clarifies why the lastHeard fallback is unreachable by design: a packet
that just arrived SHOULD refresh lastHeard from the phone's clock, so the
fallback guards only against a caller that skips normalization.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jamesarich

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jamesarich
jamesarich added this pull request to the merge queue Jul 29, 2026
Merged via the queue into main with commit d714b22 Jul 29, 2026
15 checks passed
@jamesarich
jamesarich deleted the claude/protobufs-time-edits-f2b7e2 branch July 29, 2026 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant