Skip to content

Use real noise floor for signal rating; unify with SNR color#2078

Open
bruschill wants to merge 6 commits into
meshtastic:mainfrom
bruschill:feat/snr-noise-floor-signal-2042
Open

Use real noise floor for signal rating; unify with SNR color#2078
bruschill wants to merge 6 commits into
meshtastic:mainfrom
bruschill:feat/snr-noise-floor-signal-2042

Conversation

@bruschill

@bruschill bruschill commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What changed?

Reworked the LoRa signal-quality rating in LoRaSignalStrengthIndicator.swift around a single signalQuality(snrMargin:) helper that both the bar indicator and the SNR text color derive from, so they can never disagree on the same reading.

  • getLoRaSignalStrength(...) now takes an optional noiseFloor:. When the receiving node has a recent noise-floor reading (DeviceMetrics.noise_floor, delivered via Local Stats telemetry), it computes the real link margin rssi − noiseFloor, rates it against the same preset floor, and returns the more conservative of the SNR-based and RSSI-based tiers. When no noise floor is available it falls back to SNR-only — the old guessed fixed RSSI thresholds (−115/−120/−126) are gone.
  • New NodeInfoEntity.recentNoiseFloor (present, non-zero, newer than 2 h — matching the existing isOnline convention).
  • NodeDetail passes the connected node's recentNoiseFloor into the rating and colors the SNR text from the same signalStrength.color as the bar.
  • Fixes a latent bug where LoRaSignalStrengthMeter colored its SNR text with a hardcoded .longFast preset instead of the actual preset.
  • Updated docs/user/signal-meter.md (and regenerated the bundled HTML) to describe the new algorithm.

Why did it change?

Per meshtastic/design#15, the RSSI thresholds in the rating were guessed values that can't distinguish signal from noise without knowing the noise floor. Android resolved this by going SNR-only (Meshtastic-Android#5903). iOS can do better: DeviceMetrics.noise_floor gives us the receiving radio's actual noise floor, so we can compute a real link margin instead of guessing. Separately, getLoRaSignalStrength() and getSnrColor() used different algorithms and could disagree on the same reading — now unified.

Closes #2042

How is this tested?

New LoRaSignalStrengthRatingTests (10 tests, all passing on simulator):

  • signalQuality tier boundaries.
  • SNR-only fallback ignores RSSI (no more fixed thresholds): a good-SNR link with weak RSSI stays Good.
  • Noise-floor combine: a poor rssi − noiseFloor margin downgrades an otherwise-good SNR; a strong RSSI margin does not upgrade a poor SNR (conservative combine); a zero noise floor is treated as unavailable.
  • getSnrColor matches the bar tier for the same reading.
  • recentNoiseFloor freshness gate (fresh / stale >2 h / zero / absent) via SwiftData.

SourceKit-LSP reports no diagnostics; SwiftLint clean; full test build succeeds. Reviewed by an internal Swift reviewer (approve-with-nits; the actionable lint nit is fixed).

Screenshots/Videos (when applicable)

N/A — the meter's visual layout is unchanged; only the tier computation changed.

Checklist

  • My code adheres to the project's coding and style guidelines.
  • I have conducted a self-review of my code.
  • I have commented my code, particularly in complex areas.
  • I have verified whether these changes require updates to the in-app documentation under docs/user/ or docs/developer/, and updated accordingly (see copilot-instructions.md for the view → doc page mapping). If no doc update is needed, add the skip-docs-check label.
  • I have tested the change to ensure that it works as intended.

Summary by CodeRabbit

  • New Features
    • Updated LoRa Signal Meter quality ratings to be based on SNR relative to the active preset’s SNR limit.
    • When recent noise-floor telemetry is available, the meter also evaluates RSSI-to-noise margin and uses the more conservative rating.
    • Strength and SNR indicators now consistently reflect the same computed quality.
  • Documentation
    • Updated Signal Meter and node guidance to describe the new SNR- and noise-aware behavior.
  • Tests
    • Added coverage for SNR tiering, conservative noise-floor behavior, and recent noise-floor extraction.
  • Chores
    • Improved docs keyword extraction to handle HTML entities correctly.

getLoRaSignalStrength() blended guessed fixed RSSI thresholds
(-115/-120/-126) with the SNR-vs-preset-floor comparison to pick the
signal-quality tier, while getSnrColor() was already SNR-only — so the
bar indicator and the SNR text color could disagree on the same reading.

Unify both around a single signalQuality(snrMargin:) helper. When the
receiving node has a recent noise floor (DeviceMetrics.noise_floor from
Local Stats telemetry), compute the real link margin (rssi - noiseFloor)
and combine it with the SNR margin, taking the more conservative tier —
more accurate than Android's SNR-only model. Fall back to SNR-only when
no noise floor is available, dropping the guessed RSSI thresholds.

Adds NodeInfoEntity.recentNoiseFloor (2h freshness, matching isOnline),
ties the SNR text color to the same rating as the bar in the signal
meters, and fixes a latent bug where LoRaSignalStrengthMeter colored the
SNR text with a hardcoded .longFast preset.

Adds LoRaSignalStrengthRatingTests (10 tests): rating bands, SNR-only
fallback, noise-floor combine, bar/text color agreement, and the
recentNoiseFloor freshness gate.

Fixes meshtastic#2042

Docs: updated signal-meter.md for the new rating algorithm
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The signal meter now rates quality from preset-relative SNR, optionally constrained by RSSI-to-noise-floor margin from recent local stats. UI consumers, tests, project wiring, documentation, and search metadata were updated accordingly.

Changes

Signal quality rating

Layer / File(s) Summary
Signal rating model
Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift
Adds ordered signal tiers and shared color mapping, replaces fixed RSSI thresholds with preset-relative SNR margins, and conservatively combines recent noise-floor margins when available.
Telemetry and UI integration
Meshtastic/Extensions/SwiftData/NodeInfoEntityExtension.swift, Meshtastic/Views/Helpers/LoRaSignalStrength.swift, Meshtastic/Views/Nodes/Helpers/NodeDetail.swift, MeshtasticTests/*, Meshtastic.xcodeproj/project.pbxproj
Adds fresh-noise-floor lookup, passes it into signal calculations, aligns displayed colors with the computed tier, and adds and registers coverage for rating and freshness behavior.
Signal meter documentation and indexing
Meshtastic/Resources/docs/markdown/user/signal-meter.md, Meshtastic/Resources/docs/user/signal-meter.html, docs/user/signal-meter.md, Meshtastic/Resources/docs/markdown/user/nodes.md, Meshtastic/Resources/docs/user/nodes.html, docs/user/nodes.md, Meshtastic/Resources/docs/index.json, scripts/build-docs.sh
Documents SNR-limit rating and noise-floor-aware margins, updates node-meter explanations and search metadata, and normalizes HTML entities during keyword extraction.

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

Possibly related issues

  • meshtastic/web#1241 — Covers a similar preset-relative signal-quality model with noise-floor-aware RSSI blending.

Suggested reviewers: garthvh

Sequence Diagram(s)

sequenceDiagram
  participant NodeDetail
  participant NodeInfoEntity
  participant SignalRating
  NodeDetail->>NodeInfoEntity: obtain recentNoiseFloor
  NodeDetail->>SignalRating: provide SNR, RSSI, preset, noise floor
  SignalRating-->>NodeDetail: return signal tier and color
Loading

Poem

A rabbit checks the signal bars,
With moonlit SNR stars.
Noise floors tucked beneath the ground,
Make clearer margins hop around.
Good, fair, bad—the tiers now play,
And tests keep carrots safe each day.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Implementation matches #2042: recent noise floor is used when available, falls back to SNR-only when absent or stale, and bar/color share one algorithm.
Out of Scope Changes check ✅ Passed The changes stay focused on the signal-rating fix, tests, and supporting docs; no unrelated feature work is evident.
Title check ✅ Passed The title accurately summarizes the main change: using real noise-floor data for signal rating and unifying the SNR color path.
Description check ✅ Passed The description follows the required template and includes what changed, why, testing, screenshots, and checklist details.

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: 3

🧹 Nitpick comments (2)
Meshtastic/Resources/docs/markdown/user/signal-meter.md (1)

67-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Warning callout doesn't follow the two-line format.

The title and body are on the same blockquote line, separated by :, which the repo's callout guideline explicitly disallows.

📝 Suggested reformat
-> **Warning — Watch out for local noise:** If you hook up a massive antenna and see a great RSSI (e.g., `-90 dBm`) but your signal meter is only showing **1 Bar (Bad)**, you have a problem. It means you have local interference—perhaps a cheap power supply, a noisy computer, or a nearby radio tower—creating so much static that it is drowning out your mesh. When your radio knows its own noise floor, the meter accounts for exactly this by measuring your real margin above the noise.
+> **Warning — Local noise**
+>
+> If you hook up a massive antenna and see a great RSSI (e.g., `-90 dBm`) but your signal meter is only showing **1 Bar (Bad)**, you have a problem. It means you have local interference—perhaps a cheap power supply, a noisy computer, or a nearby radio tower—creating so much static that it is drowning out your mesh. When your radio knows its own noise floor, the meter accounts for this by measuring your real margin above the noise.

If this line originates from docs/user/signal-meter.md, apply the same fix there and regenerate the bundled HTML/snapshots per the doc build guidelines.
As per coding guidelines, "Use the two-line callout format for Tip and Warning blocks in markdown, with the title on one blockquote line and the body on following blockquote lines" and "Do not place callout body text on the same line as the title, and do not use a : separator in Tip/Warning callouts."

🤖 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 `@Meshtastic/Resources/docs/markdown/user/signal-meter.md` at line 67, Reformat
the “Warning — Watch out for local noise” callout in the signal-meter
documentation so the title occupies its own blockquote line and the explanatory
text follows on subsequent blockquote lines, without a colon separator. If the
source exists under docs/user, apply the same change there and regenerate the
bundled HTML and snapshots using the documentation build process.

Source: Coding guidelines

Meshtastic/Extensions/SwiftData/NodeInfoEntityExtension.swift (1)

206-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared "2-hour freshness" check.

This duplicates the "now minus 120 minutes" comparison already used by isOnline (same file, unrelated diff). Both encode the same 2-hour freshness window independently; if the online/staleness window ever changes, it's easy to update one and miss the other.

♻️ Suggested helper
+extension Date {
+	func isMoreRecentThan(minutesAgo: Int) -> Bool {
+		guard let threshold = Calendar.current.date(byAdding: .minute, value: -minutesAgo, to: Date()) else { return false }
+		return self.compare(threshold) == .orderedDescending
+	}
+}
+
 var recentNoiseFloor: Int32? {
 	guard let stats = latestLocalStats,
 		  let time = stats.time,
 		  let noiseFloor = stats.noiseFloor,
 		  noiseFloor != 0,
-		  let twoHoursAgo = Calendar.current.date(byAdding: .minute, value: -120, to: Date()),
-		  time.compare(twoHoursAgo) == .orderedDescending
+		  time.isMoreRecentThan(minutesAgo: 120)
 	else { return nil }
 	return noiseFloor
 }
🤖 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 `@Meshtastic/Extensions/SwiftData/NodeInfoEntityExtension.swift` around lines
206 - 219, Extract the shared two-hour freshness calculation or predicate used
by recentNoiseFloor and isOnline into a common helper in
NodeInfoEntityExtension, then have both checks reuse it. Preserve the existing
behavior: timestamps must be newer than the current 120-minute threshold.
🤖 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 `@docs/user/signal-meter.md`:
- Line 67: Reformat the Warning callout in the signal-meter documentation so its
bold title occupies its own blockquote line, with the closing ** directly after
“noise” and no colon separator; move the explanatory text to subsequent
blockquote line(s). Apply the same two-line format to the nearby Tip callout for
consistency.

In `@Meshtastic/Resources/docs/index.json`:
- Around line 437-468: Update scripts/build-docs.sh to decode HTML entities in
rendered documentation before extracting search keywords, ensuring entities such
as " become their actual characters rather than standalone terms like quot.
Regenerate Meshtastic/Resources/docs/index.json and verify entity names are no
longer included as keywords.

In `@Meshtastic/Views/Nodes/Helpers/NodeDetail.swift`:
- Around line 142-146: Update the Node Detail documentation in
docs/user/nodes.md with a brief user-facing note describing the shared
signal-strength meter and its use of the connected node’s recent noise floor
when available. Keep the existing high-level documentation intact and limit the
change to this behavior.

---

Nitpick comments:
In `@Meshtastic/Extensions/SwiftData/NodeInfoEntityExtension.swift`:
- Around line 206-219: Extract the shared two-hour freshness calculation or
predicate used by recentNoiseFloor and isOnline into a common helper in
NodeInfoEntityExtension, then have both checks reuse it. Preserve the existing
behavior: timestamps must be newer than the current 120-minute threshold.

In `@Meshtastic/Resources/docs/markdown/user/signal-meter.md`:
- Line 67: Reformat the “Warning — Watch out for local noise” callout in the
signal-meter documentation so the title occupies its own blockquote line and the
explanatory text follows on subsequent blockquote lines, without a colon
separator. If the source exists under docs/user, apply the same change there and
regenerate the bundled HTML and snapshots using the documentation build process.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 22c6ecab-8846-4503-850f-73958f3d3592

📥 Commits

Reviewing files that changed from the base of the PR and between 8e0eaaf and 05e4ad8.

📒 Files selected for processing (10)
  • Meshtastic.xcodeproj/project.pbxproj
  • Meshtastic/Extensions/SwiftData/NodeInfoEntityExtension.swift
  • Meshtastic/Resources/docs/index.json
  • Meshtastic/Resources/docs/markdown/user/signal-meter.md
  • Meshtastic/Resources/docs/user/signal-meter.html
  • Meshtastic/Views/Helpers/LoRaSignalStrength.swift
  • Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift
  • Meshtastic/Views/Nodes/Helpers/NodeDetail.swift
  • MeshtasticTests/LoRaSignalStrengthRatingTests.swift
  • docs/user/signal-meter.md

Comment thread docs/user/signal-meter.md Outdated
Comment thread Meshtastic/Resources/docs/index.json Outdated
Comment thread Meshtastic/Views/Nodes/Helpers/NodeDetail.swift
- Reformat the signal-meter Tip and Warning callouts to the required
  two-line format (title on its own blockquote line, no colon separator,
  closing ** adjacent to the last word).
- build-docs.sh: strip HTML entities before keyword extraction so
  rendered "/& no longer yield bogus 'quot'/'amp' search
  keywords in index.json (removed repo-wide).

@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: 1

🤖 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 `@Meshtastic/Resources/docs/user/signal-meter.html`:
- Around line 74-77: Fix the malformed callout markup generated from
docs/user/signal-meter.md by ensuring each callout uses valid paragraph
structure: the title remains inside the div, the body is wrapped in an opening
and closing p, and the div closes afterward. Regenerate
Meshtastic/Resources/docs with bash scripts/build-docs.sh --output
Meshtastic/Resources/docs so the bundled documentation stays synchronized.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 85333165-9fac-41c6-a568-81b8c2b412a2

📥 Commits

Reviewing files that changed from the base of the PR and between 05e4ad8 and a7d82c5.

📒 Files selected for processing (5)
  • Meshtastic/Resources/docs/index.json
  • Meshtastic/Resources/docs/markdown/user/signal-meter.md
  • Meshtastic/Resources/docs/user/signal-meter.html
  • docs/user/signal-meter.md
  • scripts/build-docs.sh

Comment thread Meshtastic/Resources/docs/user/signal-meter.html Outdated
Node Detail is where the noise-floor-aware signal meter appears, so point
docs/user/nodes.md at the Signal Meter page with a one-line summary of the
unified SNR/noise-floor rating. (CodeRabbit)
The two-line callout format must be consecutive blockquote lines with no
blank '>' line between title and body — a blank line produces two <p>
elements, which the docs build's callout converter mangles into invalid
HTML. Match the working format used elsewhere (e.g. messages.md) so the
Tip/Warning callouts render as valid <div> callouts. (CodeRabbit)

@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: 1

🤖 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 `@docs/user/nodes.md`:
- Around line 52-53: Update the signal meter description near the node detail
documentation to specify that the radio’s reported noise floor affects
link-quality ratings only when the data is recent, within two hours; otherwise
it is not used. Then regenerate the bundled Markdown and HTML documentation
outputs so they remain synchronized with the updated source under docs/.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f206d67a-5c61-4545-8381-67167ad37465

📥 Commits

Reviewing files that changed from the base of the PR and between a7d82c5 and b0f148f.

📒 Files selected for processing (4)
  • Meshtastic/Resources/docs/index.json
  • Meshtastic/Resources/docs/markdown/user/nodes.md
  • Meshtastic/Resources/docs/user/nodes.html
  • docs/user/nodes.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • Meshtastic/Resources/docs/index.json

Comment thread docs/user/nodes.md Outdated
The rating only uses noise-floor data reported within the last ~2h, so
say 'recently reported ... when available' rather than implying any
noise-floor reading qualifies. (CodeRabbit)
@garthvh
garthvh requested a review from GUVWAF July 16, 2026 22:34
…r-signal-2042

# Conflicts:
#	Meshtastic.xcodeproj/project.pbxproj
#	Meshtastic/Resources/docs/index.json
#	Meshtastic/Views/Helpers/LoRaSignalStrength.swift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use noise floor (Local Stats) for signal-quality rating instead of fixed RSSI thresholds

1 participant