Skip to content

Conversation

@YaraShahin
Copy link

This Pull Request introduces the VDA5050 Safety State Broadcaster, a controller for publishing safety state information in ROS 2. The broadcaster reads safety-related state interfaces from hardware and exposes them in standardized ROS messages, as defined by the VDA5050 standard, for easy integration with VDA5050-based fleet management systems, safety monitoring, and higher-level decision-making nodes.

Dependency: This PR depends on control_msgs#266 which introduces the VDA5050SafetyState message.

Features

  • Standardized Safety Output: Publishes a control_msgs::msg::VDA5050SafetyState message representing the current safety state of the system: field violation and E-stop status.

  • Flexible Interface Support: Reads from configurable state interfaces covering field violation, manual E-stop, remote E-stop, and auto-acknowledged E-stop, ensuring full compatibility with the VDA5050 schema.

  • Parameterization: Fully configurable through YAML using generate_parameter_library.

Published Topic

  • ~/vda5050_safety_state (control_msgs::msg::VDA5050SafetyState) — publishes the combined safety state: field violation and prioritized E-stop status.

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.

Copy link
Member

@christophfroehlich christophfroehlich left a comment

Choose a reason for hiding this comment

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

First (very superficially) review

@YaraShahin YaraShahin force-pushed the add-vda5050-safety-state-broadcaster branch from 50ca35f to 317e7ea Compare November 27, 2025 15:20
Copy link
Member

@christophfroehlich christophfroehlich left a comment

Choose a reason for hiding this comment

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

CI fails because of the API breaking changes of the realtime_publisher.

I have a general question, shouldn't we use data_type bool for state_interfaces here? Or at least support both?

Comment on lines +113 to +126
/**
* @brief Safely converts a double value to bool, treating NaN as false.
* @param value The double value to convert.
* @return true if value is not NaN and not zero, false otherwise.
*/
bool safe_double_to_bool(double value) const
{
if (std::isnan(value))
{
return false;
}
return value != 0.0;
}
};
Copy link
Member

Choose a reason for hiding this comment

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

if we stay with double values, should we place this somewhere else? controller_interface/helpers for example?

Copy link
Member

Choose a reason for hiding this comment

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

Here, I would rather use the logic to support boolean interfaces too based on the get_data_type method

Copy link
Author

Choose a reason for hiding this comment

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

I added bool support with the get_data_type method as @saikishor recommended.

I’ve kept safe_double_to_bool for now because it's still necessary to handle NaN cases and the conversion logic for existing double-based interfaces. Regarding moving it to controller_interface/helpers: I agree it could be a useful utility for other broadcasters. I’m happy to move it there (or to a common header) if you think it's generic enough for the wider framework.

Copy link
Member

Choose a reason for hiding this comment

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

@saikishor where would this fit best?

@github-project-automation github-project-automation bot moved this from Needs review to WIP in Review triage Dec 31, 2025
Copy link
Member

@saikishor saikishor left a comment

Choose a reason for hiding this comment

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

Overall looking pretty good. Some nitpicks

Comment on lines 2 to 21
fieldViolation_interfaces: {
type: string_array,
default_value: [],
description: "names of interfaces that are used to acknowledge field violation events by setting the interface to 1.0.",
}
eStop_manual_interfaces: {
type: string_array,
default_value: [],
description: "names of interfaces that are used to manually acknowledge eStop events by setting the interface to 1.0.",
}
eStop_remote_interfaces: {
type: string_array,
default_value: [],
description: "names of interfaces that are used to remotely acknowledge eStop events by setting the interface to 1.0.",
}
eStop_autoack_interfaces: {
type: string_array,
default_value: [],
description: "names of interfaces that are used to autoacknowledge eStop events by setting the interface to 1.0.",
}
Copy link
Member

Choose a reason for hiding this comment

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

let's place then under the namesapce interfaces or eStop_Interfaces or emergency_stop_interfaces and remove the interfaces prefixes in the naming

Copy link
Author

Choose a reason for hiding this comment

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

I have moved the three E-Stop types under a nested eStop_interfaces namespace (e.g., eStop_interfaces.manual, eStop_interfaces.remote, etc.) and removed the repetitive prefixes.

I’ve kept fieldViolation_interfaces at the top level because it is logically distinct from the Emergency Stop handling in the VDA5050 specification. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

Nice. Thanks for the change. Regarding your comment, does it make sense like interfaces.fieldViolation and then interfaces.eStop.* ?

What do you think?. Just throwing an idea.

Copy link
Member

Choose a reason for hiding this comment

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

Nice. Thanks for the change. Regarding your comment, does it make sense like interfaces.fieldViolation and then interfaces.eStop.* ?

What do you think?. Just throwing an idea.

Copy link
Author

Choose a reason for hiding this comment

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

Personally, I'm not a big fan of deep nesting because it makes the C++ access verbose, but I agree that grouping them under a unified interfaces namespace looks much cleaner in the YAML. I've implemented it as interfaces.field_violation and interfaces.e_stop.*. Thanks for the suggestion!

Comment on lines +113 to +126
/**
* @brief Safely converts a double value to bool, treating NaN as false.
* @param value The double value to convert.
* @return true if value is not NaN and not zero, false otherwise.
*/
bool safe_double_to_bool(double value) const
{
if (std::isnan(value))
{
return false;
}
return value != 0.0;
}
};
Copy link
Member

Choose a reason for hiding this comment

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

Here, I would rather use the logic to support boolean interfaces too based on the get_data_type method

Comment on lines 177 to 183
if (safe_double_to_bool(
state_interfaces_[itf_idx].get_optional().value_or(kUninitializedValue)))
{
fieldViolation_value = true;
break;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to also support boolean interfaces here based on the type you get from get_data_type

Copy link
Author

Choose a reason for hiding this comment

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

Done

@YaraShahin
Copy link
Author

CI fails because of the API breaking changes of the realtime_publisher.

I updated the APIs for realtime_publisher and LoanedStateInterface. I ran the tests locally on jazzy and rolling.

I have a general question, shouldn't we use data_type bool for state_interfaces here? Or at least support both?

Yes, thanks @christophfroehlich for the suggestion. I added bool support with the get_data_type method as @saikishor recommended and added a testcases for the new bool interfaces.

@saikishor
Copy link
Member

Thank you @YaraShahin . I'll take a look.

Copy link
Member

@christophfroehlich christophfroehlich left a comment

Choose a reason for hiding this comment

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

Thanks for the updates. Some comments in the code, and please consider failing tests, like that from clang

  /home/runner/work/ros2_controllers/ros2_controllers/.work/target_ws/src/ros2_controllers/vda5050_safety_state_broadcaster/src/vda5050_safety_state_broadcaster.cpp:211:27: error: implicit conversion changes signedness: 'int' to 'size_type' (aka 'unsigned long') [-Werror,-Wsign-conversion]
    211 |         state_interfaces_[itf_idx].get_name().c_str());
        |         ~~~~~~~~~~~~~~~~~ ^~~~~~~

std::shared_ptr<vda5050_safety_state_broadcaster::ParamListener> param_listener_;
std::shared_ptr<rclcpp::Publisher<control_msgs::msg::VDA5050SafetyState>>
vda5050_safety_state_publisher_;
control_msgs::msg::VDA5050SafetyState safety_state_msg;
Copy link
Member

Choose a reason for hiding this comment

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

nitpick, but we typically mark member variables as such

Suggested change
control_msgs::msg::VDA5050SafetyState safety_state_msg;
control_msgs::msg::VDA5050SafetyState safety_state_msg_;

Copy link
Author

Choose a reason for hiding this comment

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

Thank you. I did the change in the latest commit along with the necessary corresponding changes in cpp.

Comment on lines +113 to +126
/**
* @brief Safely converts a double value to bool, treating NaN as false.
* @param value The double value to convert.
* @return true if value is not NaN and not zero, false otherwise.
*/
bool safe_double_to_bool(double value) const
{
if (std::isnan(value))
{
return false;
}
return value != 0.0;
}
};
Copy link
Member

Choose a reason for hiding this comment

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

@saikishor where would this fit best?

@codecov
Copy link

codecov bot commented Jan 7, 2026

Codecov Report

❌ Patch coverage is 90.69767% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.88%. Comparing base (c703235) to head (6847ecc).

Files with missing lines Patch % Lines
...oadcaster/src/vda5050_safety_state_broadcaster.cpp 78.04% 14 Missing and 4 partials ⚠️
...ter/test/test_vda5050_safety_state_broadcaster.hpp 95.65% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1958      +/-   ##
==========================================
+ Coverage   84.79%   84.88%   +0.08%     
==========================================
  Files         151      156       +5     
  Lines       14607    14822     +215     
  Branches     1266     1285      +19     
==========================================
+ Hits        12386    12581     +195     
- Misses       1763     1777      +14     
- Partials      458      464       +6     
Flag Coverage Δ
unittests 84.88% <90.69%> (+0.08%) ⬆️

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

Files with missing lines Coverage Δ
...e_broadcaster/vda5050_safety_state_broadcaster.hpp 100.00% <100.00%> (ø)
...est/test_load_vda5050_safety_state_broadcaster.cpp 100.00% <100.00%> (ø)
...ter/test/test_vda5050_safety_state_broadcaster.cpp 100.00% <100.00%> (ø)
...ter/test/test_vda5050_safety_state_broadcaster.hpp 95.65% <95.65%> (ø)
...oadcaster/src/vda5050_safety_state_broadcaster.cpp 78.04% <78.04%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: WIP

Development

Successfully merging this pull request may close these issues.

3 participants