Skip to content

fix(ui): stop discarding measured-zero sensor and RSSI readings - #6507

Merged
jamesarich merged 2 commits into
mainfrom
fix/presence-vs-sentinel-zero-metrics
Jul 29, 2026
Merged

fix(ui): stop discarding measured-zero sensor and RSSI readings#6507
jamesarich merged 2 commits into
mainfrom
fix/presence-vs-sentinel-zero-metrics

Conversation

@jamesarich

@jamesarich jamesarich commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

A node reporting exactly 0 °C shows no temperature at all. A power channel measuring 0.00 V vanishes from the metrics card. A BLE device we have never heard from renders as having a perfect signal.

All three are the same defect: 0 treated as "not reported" on a scale where 0 is a real value. The enabling fact is that the protobuf models are Wire-generated, so presence IS nullabilityEnvironmentMetrics.temperature and friends are declared Float? = null and there is no hasX() accessor. A (x ?: 0f) != 0f guard collapses "absent" and "measured zero" into one state and silently throws away a real reading, even though the data layer already round-trips it correctly.

NodeItem.kt was fixed for ambient temperature previously. This applies the same treatment to the ten sites that were missed.

🐛 Fixes

Environment metrics — ?.let { } instead of a zero-guard

  • NodeItemCompact temperature. This is the user-visible bug: the fix landed in NodeItem and its compact sibling was missed, so a node at 0 °C showed nothing in the compact row.
  • NodeItem soil temperature, soil moisture, voltage, current.
  • Node.getDisplayStrings temperature, soil temperature, soil moisture, voltage, current.
  • PowerMetrics channel voltage — a 10th site the audit list did not name, and the same "sibling was missed" shape: the current handling two lines below had already been given the presence treatment, but the voltage filter above it still dropped any channel reading exactly 0.00 V.

Soil moisture also no longer requires a soil-temperature reading to render. It now stands on its own presence plus its 0..100 range check, and is formatted via MetricFormatter.percent so the UI and Node.getTelemetryStrings agree — previously a sensor fault reporting 101% rendered in one path but not the other.

RSSI — nullable end-to-end

0 dBm is the strongest value on a signal-strength scale, so defaulting a missing reading to 0 renders an unknown signal as an excellent one. BleDevice.readRssi() now returns Int?, which propagates through MeshtasticBleDevice, DeviceListItem, and CurrentlyConnectedInfo. MetricFormatter.rssi(null) already renders .

CurrentlyConnectedInfo seeded its own state to 0, so it displayed "0 dBm" until the first read landed.

🛠️ Also

  • DeviceListItem and CurrentlyConnectedInfo now key their remembered RSSI state by device address. A recycled list slot previously kept the previous device's reading for up to the 2 s poll interval, and LaunchedEffect(Unit) never restarted on rebind.
  • FakeBleDevice.readRssi() returns its nullable rssi instead of manufacturing -60 from null. No test depended on the fallback.
  • The BLE address in the BleRadioTransport connection-confirmed log is now anonymized, matching the other logs in that file.

🧹 Regression guard

Adds .coderabbit/ast-grep-rules/ with the two rules that found these. Both fire on the pre-fix code and report zero matches across core/ feature/ androidApp/ desktopApp/ now.

for r in .coderabbit/ast-grep-rules/*.yml; do ast-grep scan -r "$r" core/ feature/ androidApp/ desktopApp/; done

Deliberately unchanged

  • Humidity. 0 %RH is not physically reachable, so the relative_humidity / co2_humidity zero-guards are intentional and tested — EnvironmentMetricsForGraphingTest.humidity_zeroFilteredOut asserts it. Same reasoning for barometric_pressure (0 hPa is a vacuum).
  • rx_snr. Still Float = 0f upstream with no proto presence, so its guards genuinely cannot be fixed app-side.
  • packet.rx_rssi. App-level nullability only here; the proto side landed separately in fix(model): rssi explicit presence for protobufs 2.7.26.138 #6498.
  • Reaction.kt:281's rssi == 0. Documented pre-schema-51 migration handling — those rows stored 0 where the column is now nullable, so there a 0 genuinely is indistinguishable from "no reading". The RSSI ast-grep rule deliberately stops at the ?: 0 defaulting form so it does not flag this; the boundary is spelled out in the rule header.

Testing Performed

./gradlew spotlessApply spotlessCheck detekt assembleDebug test allTests — green.

22 new tests across four files. Every changed metric is pinned twice, absent and measured-zero, because either assertion alone lets the two states collapse back into one.

Test Covers
NodeTelemetryStringsTest (7) Node.getTelemetryStrings — zero temp / voltage / current / soil, out-of-range moisture still rejected, humidity zero still filtered
NodeItemZeroMetricsTest (10) NodeItem + NodeItemCompact render zero readings, hide absent ones, reject out-of-range moisture
PowerMetricsZeroVoltageTest (3) 0.00 V channel shown, absent channel hidden, voltage-without-current
MeshtasticBleDeviceRssiTest (2) advertised 0 dBm reported; bonded-only device reports null
DeviceListItemRssiTest (3) 0 dBm renders as a reading; absent renders no reading at all

All confirmed executed via the test-results XML rather than assumed from a green tick.

One structural note: the two Compose UI tests live in jvmTest, not commonTest. Both modules enable withHostTest, and under androidHostTest the Compose Robolectric idling strategy NPEs on a null Build.FINGERPRINT — a commonTest class cannot carry @RunWith(RobolectricTestRunner::class). This matches core/ui, which has host tests disabled and runs its UI tests JVM-only. The reason is commented in both build files.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • RSSI readings now distinguish between a valid 0 dBm signal and an unavailable reading.
    • Sensor displays now show legitimate zero values while hiding unavailable metrics.
    • Soil moisture can be displayed independently when within the valid range.
    • Power metrics display available voltage and current readings more accurately.
  • Bug Fixes

    • Prevented stale or misleading RSSI and telemetry values from appearing in the interface.
  • Tests

    • Added coverage for zero, missing, and invalid RSSI and sensor readings.

The protobuf models are Wire-generated, so presence IS nullability. A
`(x ?: 0f) != 0f` guard collapses "absent" and "measured zero" into one
state and throws away a real reading. Ten live sites did this.

Replace the zero-guards with `?.let { }` presence checks, and keep RSSI
nullable end-to-end so an unknown signal no longer renders as 0 dBm --
the strongest value on that scale.

Humidity keeps its zero-guard: 0 %RH is not physically reachable.

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

coderabbitai Bot commented Jul 28, 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: 55f92e45-5b5d-4fb0-bf9c-f8b2c92e2021

📥 Commits

Reviewing files that changed from the base of the PR and between 844a59c and c6d20c1.

📒 Files selected for processing (5)
  • .coderabbit/ast-grep-rules/no-float-metric-zero-sentinel.yml
  • .coderabbit/ast-grep-rules/no-rssi-zero-default.yml
  • core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioTransport.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/NodeItem.kt
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/NodeItemZeroMetricsTest.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • .coderabbit/ast-grep-rules/no-rssi-zero-default.yml
  • core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioTransport.kt
  • .coderabbit/ast-grep-rules/no-float-metric-zero-sentinel.yml
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/NodeItemZeroMetricsTest.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/NodeItem.kt

📝 Walkthrough

Walkthrough

The PR changes BLE RSSI and environment metric handling to preserve nullable absence separately from valid zero values. It updates APIs, UI state, telemetry and power rendering, tests, Compose test wiring, and adds ast-grep rules for zero-sentinel patterns.

Changes

Nullable RSSI propagation

Layer / File(s) Summary
Nullable RSSI contract and implementations
core/ble/..., core/testing/..., feature/connections/.../ConnectionsPreviews.kt, core/ble/.../*RssiTest.kt
readRssi() now returns Int?; missing advertisements produce null, while an advertised 0 dBm remains 0.
Nullable RSSI UI flow and validation
feature/connections/..., core/network/.../BleRadioTransport.kt, feature/connections/.../*RssiTest.kt, feature/connections/build.gradle.kts
Connection and list-item UI state preserves nullable RSSI, refreshes per device address, logs null-safe values, and tests zero, negative, and absent readings.

Presence-based metric rendering

Layer / File(s) Summary
Metric presence-based rendering
core/model/.../Node.kt, core/ui/.../NodeItem*.kt, feature/node/.../PowerMetrics.kt
Temperature, soil, voltage, and current render when non-null, including zero; humidity zero remains filtered, and soil moisture no longer requires soil temperature.
Metric behavior tests and test wiring
core/model/.../*Test.kt, core/ui/.../*Test.kt, feature/node/.../*Test.kt, feature/{node,connections}/build.gradle.kts
Tests cover absent and zero telemetry, soil moisture independence, power-channel rendering, and Compose JVM test setup.

Zero-sentinel guardrails

Layer / File(s) Summary
Zero-sentinel lint rules
.coderabbit/ast-grep-rules/*
Adds error-level Kotlin ast-grep rules for float metric and RSSI zero-sentinel patterns.

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

Sequence Diagram(s)

sequenceDiagram
  participant BLEDevice
  participant CurrentlyConnectedInfo
  participant DeviceListItem
  BLEDevice->>CurrentlyConnectedInfo: readRssi()
  CurrentlyConnectedInfo->>CurrentlyConnectedInfo: store nullable RSSI
  BLEDevice->>DeviceListItem: provide nullable RSSI
  DeviceListItem->>DeviceListItem: refresh displayed RSSI by device address
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Sibling Call Sites And Presence Semantics ⚠️ Warning feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/PowerMetrics.kt still uses ?: 0f for ch1-8 voltage/current, so a sibling call site remains unfixed. Update that metrics view to render absent channel voltage/current via nullability/presence checks, not zero sentinels, then rerun the zero-metric tests.
Tests Prove The Path, Not The End State ⚠️ Warning Several added tests only echo seeded values (e.g. 0 RSSI/0 dBm) and would still pass under the reverted zero-fallback code. Strengthen those cases to assert a path-specific effect: e.g. RSSI rebind/reset via device.address or a readRssi call count, not just returned/displayed 0.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main UI behavior change: preserving measured-zero sensor and RSSI readings instead of discarding them.
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.

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

🤖 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 @.coderabbit/ast-grep-rules/no-float-metric-zero-sentinel.yml:
- Around line 14-20: Extend the rule patterns under rule.any in
no-float-metric-zero-sentinel.yml to recognize equivalent zero-float literal
spellings, including 0F and 0.0f, for both equality and inequality checks;
alternatively, add invalid fixtures covering each spelling so formatting changes
cannot bypass the rule.

In @.coderabbit/ast-grep-rules/no-rssi-zero-default.yml:
- Around line 9-15: Update the rule anchored by the `X` RSSI constraint to cover
nullable zero checks such as `reaction.rssi == null || reaction.rssi == 0`, or
explicitly narrow and document the legacy exception if it must remain allowed.
Ensure the guard’s contract consistently detects the intended RSSI-zero
anti-pattern beyond the existing `?: 0` patterns.

In
`@core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioTransport.kt`:
- Line 509: Update the connection-confirmation log in BleRadioTransport to
replace the raw address interpolation with address.anonymize(), matching the
surrounding transport logs while preserving the existing RSSI message.

In `@core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/NodeItem.kt`:
- Around line 397-399: Update the soilMoisture handling in NodeItem to discard
values outside 0..100 using takeIf, then format valid readings with the shared
MetricFormatter.percent instead of interpolating a percent sign. Add a UI
regression test covering an out-of-range soil-moisture value and confirming it
is not displayed.
🪄 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: ecb9af64-6463-4abc-a192-2855d15b4a2e

📥 Commits

Reviewing files that changed from the base of the PR and between dce2d89 and 844a59c.

📒 Files selected for processing (20)
  • .coderabbit/ast-grep-rules/no-float-metric-zero-sentinel.yml
  • .coderabbit/ast-grep-rules/no-rssi-zero-default.yml
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleDevice.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/MeshtasticBleDevice.kt
  • core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/MeshtasticBleDeviceRssiTest.kt
  • core/model/src/commonMain/kotlin/org/meshtastic/core/model/Node.kt
  • core/model/src/commonTest/kotlin/org/meshtastic/core/model/NodeTelemetryStringsTest.kt
  • core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioTransport.kt
  • core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeBle.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/NodeItem.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/NodeItemCompact.kt
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/component/NodeItemZeroMetricsTest.kt
  • feature/connections/build.gradle.kts
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/component/ConnectionsPreviews.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/CurrentlyConnectedInfo.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/components/DeviceListItem.kt
  • feature/connections/src/jvmTest/kotlin/org/meshtastic/feature/connections/ui/components/DeviceListItemRssiTest.kt
  • feature/node/build.gradle.kts
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt
  • feature/node/src/jvmTest/kotlin/org/meshtastic/feature/node/component/PowerMetricsZeroVoltageTest.kt

Comment thread .coderabbit/ast-grep-rules/no-float-metric-zero-sentinel.yml
Comment thread .coderabbit/ast-grep-rules/no-rssi-zero-default.yml
Comment thread core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/NodeItem.kt Outdated
…dress

- NodeItem soil moisture now range-checks 0..100 and formats via
  MetricFormatter, matching Node.getTelemetryStrings. Previously a sensor
  fault reporting 101% rendered in the UI but not in the telemetry strings.
- Anonymize the address in the BleRadioTransport connection-confirmed log.
- ast-grep float rule now covers the 0F / 0.0f / 0.0F literal spellings so a
  reformat cannot bypass it.
- Document why the RSSI rule stops at `?: 0` and deliberately does not match
  `== 0` against stored rows (Reaction.kt reads pre-schema-51 data where 0
  really is indistinguishable from "no reading").

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

Copy link
Copy Markdown
Collaborator Author

Batched all four review findings into one push (c6d20c1) rather than fix-at-a-time. Baseline re-run green: spotlessApply spotlessCheck detekt assembleDebug test allTests.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@jamesarich: Thanks—reviewing the consolidated update and its regression coverage now.

✅ 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 merged commit c7ca45f into main Jul 29, 2026
5 checks passed
@jamesarich
jamesarich deleted the fix/presence-vs-sentinel-zero-metrics branch July 29, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant