Skip to content

fix(telemetry): stop emitting -0.001V sentinel when battery unavailable (#7958) - #10217

Merged
thebentern merged 2 commits into
meshtastic:developfrom
jaime-roldan:fix/voltage-sentinel-7958
May 22, 2026
Merged

fix(telemetry): stop emitting -0.001V sentinel when battery unavailable (#7958)#10217
thebentern merged 2 commits into
meshtastic:developfrom
jaime-roldan:fix/voltage-sentinel-7958

Conversation

@jaime-roldan

Copy link
Copy Markdown
Contributor

Summary

Fixes #7958.

Device telemetry packets currently emit voltage = -0.0010000000474974513 for
nodes without a valid battery reading. That oddly-specific number is exactly the
double printout of the float literal -0.001f — a sentinel value that was
leaking onto the wire and being displayed to users as a real (negative) voltage.

Reporters confirmed this on TBEAM, HELTEC V2/V3, XIAO S3, XIAO NRF52, and
RAK4631 — identical bit pattern across wildly different hardware, which rules
out any measurement or hardware-specific cause.

Root cause

In getDeviceTelemetry():

  • has_voltage = true was set unconditionally, on every telemetry packet.
  • voltage = powerStatus->getBatteryVoltageMv() / 1000.0 was assigned
    unconditionally. When getBatteryVoltageMv() returned -1 through any
    signed path, dividing by 1000 produced -0.001f.

Because has_voltage was stamped true regardless, nanopb dutifully shipped
the sentinel over LoRa / BLE / MQTT. Downstream consumers (web client, Python
CLI, iOS/Android apps) had no way to know it meant "no reading" and rendered
it as a real voltage.

Fix

Only set has_voltage = true and assign a value when we actually have a
battery and a non-zero reading. This matches the existing pattern used by
MAX17048Sensor::getMetrics() elsewhere in the tree, and DeviceMetrics.voltage
is already defined as an optional field in the protobuf, so absence is a
valid wire state.

-    t.variant.device_metrics.has_voltage = true;
     t.variant.device_metrics.has_uptime_seconds = true;
     ...
-    t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
+    uint16_t batteryMv = powerStatus->getBatteryVoltageMv();
+    if (powerStatus->getHasBattery() && batteryMv > 0) {
+        t.variant.device_metrics.has_voltage = true;
+        t.variant.device_metrics.voltage = batteryMv / 1000.0f;
+    }

Also switched the divisor to 1000.0f (single-precision) to match the float
destination and avoid a pointless double round-trip.

Related cleanups (not in this PR — deliberately keeping scope small)

  • src/graphics/draw/UIRenderer.cpp:661 has a defensive voltage > 0.001f
    check that's a workaround for this exact bug. Worth leaving as-is for now —
    it defends against older-firmware peers on the mesh still sending -0.001,
    which will persist in the wild for a while. Can be simplified to just
    has_voltage in a later cleanup PR.
  • MeshPacketSerializer.cpp and MeshPacketSerializer_nRF52.cpp currently
    always serialize voltage into JSON/MQTT output regardless of has_voltage.
    Those sites should probably gate on the flag too, so MQTT subscribers stop
    seeing "voltage": 0.0 for nodes with no battery. Separate PR.

Testing

  • Not built locally — submitting for CI verification.
  • Change is mechanical: removes an unconditional has_voltage = true and
    gates the existing assignment behind getHasBattery() && batteryMv > 0,
    matching the pattern already used in MAX17048Sensor::getMetrics().
  • Happy to iterate if a maintainer flags anything on a specific platform.

Backward compatibility

Older clients that don't check has_voltage will see voltage == 0.0 (the
default for an unset float in zero-initialized nanopb structs) instead of
-0.001. This is strictly better: no client rendered -0.001V correctly,
whereas 0.0 is either shown as "0.0V" (acceptable) or filtered out.
Recent first-party clients already read has_voltage correctly.

@CLAassistant

CLAassistant commented Apr 21, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions github-actions Bot added the bugfix Pull request that fixes bugs label Apr 21, 2026
@thebentern
thebentern requested a review from Copilot April 21, 2026 11:11

Copilot AI 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.

Pull request overview

Fixes erroneous negative battery voltage telemetry by only populating DeviceMetrics.voltage when a real battery reading is available, preventing the -0.001V sentinel from leaking onto the wire.

Changes:

  • Stop setting has_voltage unconditionally in device telemetry.
  • Gate voltage population behind getHasBattery() and a positive millivolt reading, and use a float divisor (1000.0f) to match the destination type.

Comment on lines +111 to +115
uint16_t batteryMv = powerStatus->getBatteryVoltageMv();
if (powerStatus->getHasBattery() && batteryMv > 0) {
t.variant.device_metrics.has_voltage = true;
t.variant.device_metrics.voltage = batteryMv / 1000.0f;
}

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

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

powerStatus->getBatteryVoltageMv() returns an int (and can be -1 per PowerStatus ctor default). Storing it in a uint16_t will wrap -1 to 65535, making batteryMv > 0 true and causing has_voltage to be set with an incorrect ~65.5V reading. Use a signed type (e.g., int batteryMv) and gate on batteryMv > 0 (optionally also sanity-check an upper bound) before setting has_voltage/voltage.

Copilot uses AI. Check for mistakes.
@caveman99 caveman99 added the ai-generated Possible AI-generated low-quality content label Apr 21, 2026
@caveman99

Copy link
Copy Markdown
Member

@jaime-roldan can you please address the findings of the review?

@jaime-roldan

jaime-roldan commented Apr 21, 2026

Copy link
Copy Markdown
Contributor Author

Hey @caveman99, sure thing, just checked and the copilot review is actually correct.

PowerStatus::getBatteryVoltageMv() returns int (src/PowerStatus.h:59), so my uint16_t batteryMv would've wrapped -1 to 65535 and made the > 0 check pass with a bogus ~65V reading, perhaps worse than the original bug. Just pushed a fix changing it to int32_t, which handles -1 and 0 correctly.

Left out the upper-bound sanity check copilot mentioned. Feels like if the power layer is returning 20000 mV then something is wrong deeper down and we shouldn't be masking it here.

@cvaldess

Copy link
Copy Markdown
Contributor

Validated on Nordic nRF54L15-DK — a USB-only board with no battery
hardware (the exact case this PR addresses).

Before: outgoing DeviceTelemetry payloads carried voltage = -0.001
(from a -1 mV ADC sentinel / 1000.0), which paired iOS/Android clients
displayed as a real negative voltage.

After this PR: has_voltage = false, voltage field omitted on the wire.
Confirmed by pairing iOS, completing the config stream, and observing
DeviceTelemetry packets (id=0x0de7475f, len=43) going out cleanly. No
regressions to other telemetry fields (battery_level, channel_utilization,
air_util_tx, uptime_seconds all intact).

Builds clean (no footprint change). LGTM.

Tested-by: cvaldess

@thebentern
thebentern merged commit 91f930d into meshtastic:develop May 22, 2026
78 checks passed
Itzdavid01 pushed a commit to Itzdavid01/firmware that referenced this pull request Sep 5, 2026
…le (meshtastic#7958) (meshtastic#10217)

* fix(telemetry): stop emitting -0.001V sentinel when battery unavailable (meshtastic#7958)

* address review: use int32_t for batteryMv to avoid uint16_t signed wrap
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-generated Possible AI-generated low-quality content bugfix Pull request that fixes bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Negative voltage in device metrics

6 participants