Skip to content

Add Battery State Broadcaster controller - #1888

Merged
christophfroehlich merged 52 commits into
ros-controls:feature/battery_state_broadcasterfrom
b-robotized-forks:add-battery-state-broadcaster
Jul 24, 2026
Merged

Add Battery State Broadcaster controller#1888
christophfroehlich merged 52 commits into
ros-controls:feature/battery_state_broadcasterfrom
b-robotized-forks:add-battery-state-broadcaster

Conversation

@YaraShahin

@YaraShahin YaraShahin commented Sep 5, 2025

Copy link
Copy Markdown

This Pull Request introduces the Battery State Broadcaster, a controller for publishing battery status information in ROS2. The broadcaster reads battery-related state interfaces from hardware and exposes them in standardized ROS messages for easy integration with monitoring tools, logging systems, and higher-level decision-making nodes.

Dependency: This PR depends on control_msgs#250 which introduces the BatteryStates message.

Features

  • Aggregated and Raw Outputs:

    • Publishes a combined sensor_msgs::msg::BatteryState message representing the overall system status.

    • Publishes per-joint control_msgs::msg::BatteryStates messages containing raw values.

  • Flexible Interface Support: Reads from interfaces such as battery_voltage, battery_current, battery_temperature, battery_charge, battery_percentage, and others.

  • Parameterization: Configurable through YAML using generate_parameter_library.

Interfaces

Published Topics

  • ~/battery_state (sensor_msgs::msg::BatteryState) — aggregated battery status across all configured joints.

  • ~/raw_battery_states (control_msgs::msg::BatteryStates) — raw per-joint battery state values.

Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:

  1. Limited scope. Your PR should do one thing or one set of things. Avoid adding “random fixes” to PRs. Put those on separate PRs.
  2. Give your PR a descriptive title. Add a short summary, if required.
  3. Make sure the pipeline is green.
  4. Don’t be afraid to request reviews from maintainers.
  5. New code = new tests. If you are adding new functionality, always make sure to add some tests exercising the code and serving as live documentation of your original intention.

To send us a pull request, please:

  • Fork the repository.
  • Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
  • Ensure local tests pass. (colcon test and pre-commit run (requires you to install pre-commit by pip3 install pre-commit)
  • Commit to your fork using clear commit messages.
  • Send a pull request, answering any default questions in the pull request interface.
  • Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.

Comment thread battery_state_broadcaster/doc/userdoc.rst Outdated

@christophfroehlich christophfroehlich left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for your contribution.
Unfortunately, there is already an existing package with this name.
https://index.ros.org/p/battery_state_broadcaster/#rolling

Can you please highlight the difference between yours and the existing one? If there is a benefit, we could either rename yours or ask the maintainers/authors of the original one to move it to our repository and merge your additions on top. As an alternative, you could also open a PR there.

Comment thread battery_state_broadcaster/package.xml Outdated
Comment thread battery_state_broadcaster/package.xml Outdated
Comment thread battery_state_broadcaster/package.xml Outdated
Comment thread battery_state_broadcaster/src/battery_state_broadcaster.cpp Outdated

std::vector<bool> battery_presence_;

private:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I bet some of the above could be moved to the private section ;)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I just moved what I could to private; unfortunately I am using some of them in test so they have to stay protected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@YaraShahin you can use friending for this to enable testing. We should be using this pattern anyways.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Oh, thanks for the tip. I made FriendBatteryStateBroadcaster to be a frined of BatteryStateBroadcaster. Then I exposed in FriendBatteryStateBroadcaster the private variables that each of the TEST_F classes need, since they are also friends of FriendBatteryStateBroadcaster but not direct friends of BatteryStateBroadcaster.

Comment thread battery_state_broadcaster/src/battery_state_broadcaster.cpp Outdated
Comment thread battery_state_broadcaster/src/battery_state_broadcaster.cpp Outdated
Comment thread battery_state_broadcaster/src/battery_state_broadcaster.cpp Outdated

@bmagyar bmagyar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A few small cleanup steps, otherwise looks good!

@YaraShahin

YaraShahin commented Sep 9, 2025

Copy link
Copy Markdown
Author

Thank you for your contribution. Unfortunately, there is already an existing package with this name. https://index.ros.org/p/battery_state_broadcaster/#rolling

Can you please highlight the difference between yours and the existing one? If there is a benefit, we could either rename yours or ask the maintainers/authors of the original one to move it to our repository and merge your additions on top. As an alternative, you could also open a PR there.

Thank you @christophfroehlich for your feedback. I’ve reached out to the maintainer of the original battery_state_broadcaster package by email to discuss whether they intend to release it into ros2_controllers and how we could best align efforts.

In the meantime, I’ve prepared a summary of the differences and benefits of this implementation, as follows:

  • Full sensor_msgs::msg::BatteryState coverage:
    The existing broadcaster is currently focused on publishing the voltage interface with parameters for power_supply_technology and design_capacity. This version extends by also broadcasting additional BatteryState interfaces (current, charge, temperature, percentage, power_supply_status, power_supply_health, and presence), while filling the remaining fields via parameters.

  • Flexible parameterization: Provides per-joint parameters to enable or disable specific battery interfaces, making it adaptable to different hardware setups. It also supports parameters for fixed battery properties.

  • Multi-battery support: While the existing package is designed around a single battery, this version broadens usage to multi-battery systems, publishing both per-battery states and an aggregated battery state topic (control_msgs::msg::BatteryStates).

  • Battery analytics:
    Adds analytics features on top of the basic fields, including:

    • Percentage calculation (if no direct percentage interface is available) using voltage and min/max parameters.
    • Presence detection (via battery_present interface or inferred from voltage).
    • Aggregation across multiple batteries (averages, sums, and combined status/health).
  • Consistency with ros2_controllers style: Follows established controller/broadcaster structure, and documented best practices.

  • Test coverage: Includes unit tests aligned with the ros2_controllers testing style, ensuring reliability.

@ottojo

ottojo commented Sep 9, 2025

Copy link
Copy Markdown

Hi! Creator of https://github.com/ipa320/ros_battery_monitoring here! As already realized my package is pretty bare bones, and admittedly not in active use by us at the moment (in a previous robot we only had battery state information from the motor controller - now we have a dedicated package receiving CAN messages from the battery and publishing that as BatteryState).
There is a PR ipa320/ros_battery_monitoring#3 still open which adds more functionality to my package, but i have not had the time/priority to look at it. So i think leaving the entire topic to the ros2_control project seems reasonable.

I have only briefly skimmed over this PR, a technical question that i still have is: Is the battery state now coupled to joints? IIRC, i had one hardware interface plugin which implemented both the joint interfaces and the battery data interfaces as "sensor" tags beside the "joint"s in the XML. But maybe my setup is the one out of the ordinary here...

Anyways, i myself don't have an issue giving the battery_state_broadcaster package name to the ros2_control project, if thats the question, although i do wonder if anybody is using my package and will have their battery monitoring unexpectedly broken...

@christophfroehlich

Copy link
Copy Markdown
Member

Anyways, i myself don't have an issue giving the battery_state_broadcaster package name to the ros2_control project, if thats the question, although i do wonder if anybody is using my package and will have their battery monitoring unexpectedly broken...

Thanks for that offer. According to index.ros.org there is no released package dependent on it at least. I haven't checked if the new proposed broadcaster can be rewritten to support the existing broadcaster config? Maybe @YaraShahin can evaluate that. If this does not make sense, we can break it on rolling but leave the other distros as they are now.

@YaraShahin

YaraShahin commented Sep 9, 2025

Copy link
Copy Markdown
Author

I have only briefly skimmed over this PR, a technical question that i still have is: Is the battery state now coupled to joints? IIRC, i had one hardware interface plugin which implemented both the joint interfaces and the battery data interfaces as "sensor" tags beside the "joint"s in the XML. But maybe my setup is the one out of the ordinary here...

Thanks a lot @ottojo for your feedback 🙏

To your question: in our implementation the battery state is indeed exposed via state interfaces that are grouped under state_joints. This keeps it consistent with other broadcasters in ros2_control (IMU, force-torque, etc.), but it does mean batteries are defined alongside joints in the URDF. If I understand correctly, your case should still be supported, since you could list your sensor_name under state_joints parameter. The state interface exposed is the same in both cases, for example: <sensor-or-joint-name>/voltage.

I’ll also take a look at the differences between our current implementation and the PR you mentioned in your repo, to see if there’s anything we should carry over here.

Thanks again for the clarification and for being open to handing over the package name — we’ll make sure to handle the transition carefully so existing users aren’t left behind.

@YaraShahin

Copy link
Copy Markdown
Author

Thanks for that offer. According to index.ros.org there is no released package dependent on it at least. I haven't checked if the new proposed broadcaster can be rewritten to support the existing broadcaster config? Maybe @YaraShahin can evaluate that. If this does not make sense, we can break it on rolling but leave the other distros as they are now.

Thanks @christophfroehlich for checking the dependency situation. I’ll review whether the configuration style from ipa320/ros_battery_monitoring can be supported directly. If keeping full compatibility doesn’t make sense, then I’ll add a migration note in the docs for Rolling so existing users can adapt their configs.

@christophfroehlich

Copy link
Copy Markdown
Member

@YaraShahin any updates?

@YaraShahin

Copy link
Copy Markdown
Author

@YaraShahin any updates?

Thanks for the reminder, I’ll push the fixes this week.

@YaraShahin
YaraShahin force-pushed the add-battery-state-broadcaster branch from d5255d9 to 860e8ea Compare September 30, 2025 22:51
@YaraShahin

Copy link
Copy Markdown
Author

A few small cleanup steps, otherwise looks good!

Thanks for the feedback! I’ve fixed the points you mentioned.

YaraShahin and others added 6 commits June 7, 2026 15:05

@christophfroehlich christophfroehlich left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the iteration, parameters etc are perfect IMHO.

Clang job is failing with
/home/runner/work/ros2_controllers/ros2_controllers/.work/target_ws/src/ros2_controllers/battery_state_broadcaster/src/battery_state_broadcaster.cpp:325:27: error: implicit conversion changes signedness: 'int' to 'size_type' (aka 'unsigned long') [-Werror,-Wsign-conversion]

amongst others.

@mergify

mergify Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@YaraShahin

Copy link
Copy Markdown
Author

Thanks @christophfroehlich for following up! I fixed the clang issues & made sure it passed with rolling clang check locally. @destogl or @bmagyar Could you please take a look on the new friending/protected variables pattern and advise me if I need to revert it back? Thanks in advance!

@mergify

mergify Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.83106% with 52 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.84%. Comparing base (d420bdc) to head (76ab3f0).

Files with missing lines Patch % Lines
...tate_broadcaster/src/battery_state_broadcaster.cpp 79.27% 24 Missing and 22 partials ⚠️
...roadcaster/test/test_battery_state_broadcaster.hpp 92.59% 1 Missing and 5 partials ⚠️
Additional details and impacted files
@@                          Coverage Diff                          @@
##           feature/battery_state_broadcaster    #1888      +/-   ##
=====================================================================
+ Coverage                              86.58%   86.84%   +0.25%     
=====================================================================
  Files                                    151      152       +1     
  Lines                                  16338    16644     +306     
  Branches                                1371     1408      +37     
=====================================================================
+ Hits                                   14147    14455     +308     
+ Misses                                  1683     1654      -29     
- Partials                                 508      535      +27     
Flag Coverage Δ
unittests 86.84% <85.83%> (+0.25%) ⬆️

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

Files with missing lines Coverage Δ
...roadcaster/test/test_battery_state_broadcaster.cpp 100.00% <100.00%> (ø)
...aster/test/test_load_battery_state_broadcaster.cpp 100.00% <100.00%> (ø)
...roadcaster/test/test_battery_state_broadcaster.hpp 92.59% <92.59%> (ø)
...tate_broadcaster/src/battery_state_broadcaster.cpp 79.27% <79.27%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@christophfroehlich christophfroehlich left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for your patience.

I applied some changes:

  • harmonize the lifecycle transition with other tests
  • use a wait set for the subscription tests, the old pattern failed in CI
  • removed the friendclassing for the test, so we have the same pattern as in other packages
  • fixed the failing build of docs
  • Update handle API

Regarding Bence's comments on float/int/uint8: IMHO you have addressed them properly, I don't see any remaining issue here now.

@christophfroehlich christophfroehlich moved this from WIP to Needs review in Review triage Jul 23, 2026
@christophfroehlich
christophfroehlich merged commit 139aed1 into ros-controls:feature/battery_state_broadcaster Jul 24, 2026
23 of 24 checks passed
@github-project-automation github-project-automation Bot moved this from Needs review to Done in Review triage Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants