Skip to content

feat(dc_measurements): add the battery Measurement — percentage, voltage, current, charge sessions and cycles - #372

Merged
Minipada merged 1 commit into
jazzyfrom
feature/361-add-measurement-battery-percentage-volta
Aug 18, 2026
Merged

feat(dc_measurements): add the battery Measurement — percentage, voltage, current, charge sessions and cycles#372
Minipada merged 1 commit into
jazzyfrom
feature/361-add-measurement-battery-percentage-volta

Conversation

@Minipada

Copy link
Copy Markdown
Owner

Closes #361

DC recorded what the computer was doing and nothing about what powers the robot. dc_measurements/Battery subscribes to sensor_msgs/BatteryState and reports charge percentage, voltage and current on the polling interval, plus one Record when a charging session starts and one when it ends — charging is unavailable time, and that is the fact an operations team schedules around.

Facts, not metrics

Every Record carries an event field naming which of three it is:

event When Carries
sample Every poll, once the topic has published percentage, voltage, current, power_supply_status, completed_cycles, and whatever else the pack reports
charge_session_start The pack starts charging session_id, and the depth and duration of the discharge that preceded it
charge_session_end The pack stops charging session_id, duration_sec, and the percentage points gained

"Charging started after a discharge of 62 %", never "battery availability is 87 %" — aggregation stays in the SQL views, where the window is a query parameter.

dc_common::BatteryCycleAccumulator

Header-only, no rclcpp, no message package, next to record_ring_buffer.hpp. It consumes (percentage, power-supply status, timestamp) triples and returns session boundaries; it owns no clock, so its 10 gtest cases drive whole shifts in microseconds. Two decisions worth reading the code for:

  • Sessions are delimited by power_supply_status, never by a percentage threshold — a percentage bouncing ±5 % inside one session opens and closes nothing, and an unknown status leaves an open session open rather than closing it.
  • Cycles accumulate discharge depth instead of counting full discharges — two half discharges are one cycle, not two, so a robot topped up at every dock still reports the wear it did.

On #361's blocker

#361 declares #360 a blocker and #360 is still open. The Measurement cannot exist without the accumulator, so this PR lands the BatteryCycleAccumulator half of #360 with it. #360 stays open for its other half, StateTransitionDetector (for the intervention/mission Measurements), which is untouched here.

The plugin is a thin adapter

Subscribes, delegates, serialises. sensor_msgs/BatteryState leaves most fields optional and signals "unmeasured" with NaN, so a NaN field is left out of the Record rather than written as null — a pack reporting nothing but a voltage still produces a valid Record. Before the topic publishes at all, no Record is emitted: a gap means no battery data, not a battery at 0 %. power_supply_health is reported when the pack sends one, and health_percentage (capacity against design_capacity) only when it sends both.

Session boundaries are queued when they happen and leave on the next poll, one Record per poll, keeping the timestamp of the moment they happened. So they go through publish() like everything else (Conditions, gate, incident buffering, Group) instead of being published from the subscription callback, which would race the polling timer under the node's MultiThreadedExecutor. Shared state is behind a mutex and the queue is capped.

Parameter Description Default
topic sensor_msgs/BatteryState topic. One Measurement per pack /battery_state
percentage_scale Factor on the message's percentage (the message specifies 0–1; a driver publishing 0–100 sets 1.0) 100.0
full_cycle_percent Accumulated discharge that makes one completed cycle 100.0

Schema

plugins/measurements/json/battery.json, loaded through the existing validateSchema("dc_measurements", …) path: strict where the views look (event always; power_supply_status on a sample; session_id, plus duration_sec on an end, on a boundary), permissive everywhere the hardware decides. percentage is bounded 0–100, which also catches a mis-set percentage_scale.

Verification

Built and run in localhost/dc-workspace:latest:

  • dc_common, 10 cases: half-cycles, status-delimited sessions, unknown-status gaps, monotonic session ids, packs with no percentage at all, first-session-has-no-preceding-discharge, non-negative depth, configurable cycle size.
  • dc_measurements, 5 cases over the live plugin: the happy path (percentage/voltage/current/health/health_percentage, plus schema validation applied in the test so a half-filled Record fails rather than only logging), an input topic that never publishes (no Record at all), an almost-empty BatteryState (fields absent, not null, schema passes), a full charging session (start with a 62 % discharge depth, end with a duration and 66 points gained, then 0.62 completed cycles), and two packs on distinct topics producing distinct Records.
  • colcon test --packages-select dc_common dc_measurements: 217 tests, 0 failures.
  • prek run --all-files --skip build-doc green; mdbook build renders the new page with no new warnings.

The simulation still publishes no BatteryState (#364), so there is no demo showing real charge state — as the issue anticipated, the tests publish synthetic BatteryState themselves.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GEJQDuGUXVwF9sUz5E5M7x

@Minipada
Minipada force-pushed the feature/361-add-measurement-battery-percentage-volta branch from 7d4c016 to dc4f200 Compare August 18, 2026 11:50
…ons and cycles

DC recorded what the computer was doing and nothing about what powers the robot.
`dc_measurements/Battery` subscribes to `sensor_msgs/BatteryState` and reports charge
percentage, voltage and current on the polling interval, plus one Record when a
charging session starts and one when it ends -- charging is unavailable time, and that
is the fact operations schedules around.

Records are facts, not metrics: an `event` field names which of `sample`,
`charge_session_start` (carrying the depth and duration of the discharge before it) and
`charge_session_end` (carrying the session's duration) a Record is. Aggregation stays in
the SQL views, where the window is a query parameter.

The counting lives in `dc_common::BatteryCycleAccumulator` -- header-only, no ROS
dependency, no clock of its own. Sessions are delimited by `power_supply_status`, never
by a percentage threshold, so a noisy percentage cannot open and close them repeatedly,
and cycles accumulate discharge depth rather than counting full discharges: two half
discharges are one cycle. That is the accumulator half of #360, which #361 needs to
exist at all; #360 stays open for its `StateTransitionDetector`.

A NaN field is left out of the Record instead of written as null, so a pack reporting
only a voltage still validates; before the topic publishes at all, no Record is emitted.
`topic` is a parameter, so a robot with two packs runs one Measurement per pack.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GEJQDuGUXVwF9sUz5E5M7x
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
@Minipada
Minipada force-pushed the feature/361-add-measurement-battery-percentage-volta branch from dc4f200 to 3bbd12a Compare August 18, 2026 12:56
@Minipada
Minipada merged commit 293a65b into jazzy Aug 18, 2026
6 checks passed
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.32867% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.64%. Comparing base (9aa2ea7) to head (3bbd12a).
⚠️ Report is 1 commits behind head on jazzy.

Files with missing lines Patch % Lines
dc_measurements/plugins/measurements/battery.cpp 71.33% 41 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            jazzy     #372      +/-   ##
==========================================
+ Coverage   67.55%   67.64%   +0.09%     
==========================================
  Files          98       99       +1     
  Lines        6045     6188     +143     
==========================================
+ Hits         4083     4185     +102     
- Misses       1962     2003      +41     
Flag Coverage Δ
cpp-jazzy 67.64% <71.33%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Minipada
Minipada deleted the feature/361-add-measurement-battery-percentage-volta branch September 2, 2026 12:45
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.

1 participant