ROS related practices
You can find more examples or specific examples for certain applications in ROS2 best practices section.
General Recommendations
-
Components (formerly "nodelets" in ROS 1) enable zero-copy communication when running in the same process container. This eliminates serialization overhead by passing messages as pointers
-
Safe Initialization
- Callbacks can execute immediately upon creation, potentially accessing uninitialized variables if the setup isn't complete.
- Best Practice: Always instantiate subscribers, timers, and service servers at the very end of your constructor or initialization method.
- Safety Net: For complex initializations, use an
std::atomic<bool> is_initialized_flag. Set it to true only when setup is complete, and check it at the start of every callback.
- Callbacks can execute immediately upon creation, potentially accessing uninitialized variables if the setup isn't complete.
-
Subscriptions Always check whether all subscribed messages are coming. If not, print a warning. Then you know the problem is not in your node and you know to look for the problem in topic remapping or the node publishing it.
- Possible problems:
- Wrong topic name (typo, missing remapping, etc.)
- Publisher node not running or not publishing
- QoS mismatch (e.g., publisher is using
best_effortbut subscriber is usingreliable) - Callback group issues
- Subscriber assigned to a Callback Group that isn't added to the Executor.
- Subscriber sharing a
MutuallyExclusive CallbackGroupwith a blocking service call (causing a deadlock).
- Possible problems:
Can have separate callback group for subscribers and services to avoid deadlocks. For example, assign all subscribers to a cbkgrp_subs_ and all service servers to a cbkgrp_servers_, etc. and add the groups to the executor.
Message Interfaces
- Aim to reuse existing message interfaces, for example from the mrs_msgs package or other well-known packages in the ROS 2 ecosystem. Examples:
geometry_msgs,sensor_msgs,nav_msgs,tf2_msgsetc. This promotes interoperability and reduces maintenance overhead. - If there isn't a viable existing message type that fits well for your use-case, create custom a message interface. - Custom message interfaces should be in their own packages with
_msgssuffix. This allows easy message reusage without depending on the full package. - Avoid using primitive type messages from the
std_msgs, such asFloat32,BoolandString, as they are meant to be used only for quick prototyping. Instead, use a custom message with a semantic meaning.
- Bad: Publishing a
Float32on a topic namedtemperaturewithout any context. - Good: Using
sensor_msgs::msg::Temperature(standard) or defining a customSystemStatus.msgthat includes the temperature field.
Using MRS Lib wrappers
Can find all the following in examples we provide using mrs_lib wrappers in template package
Node Creation
Use the mrs_lib::Node instead of rclcpp::Node, which actually extends rclcpp::Node wrapper that allows you to access the node shared pointer in the constructor, enabling safe and more convenient initialization of subscribers, timers, and service servers, etc.
Loading Parameters
Use mrs_lib::ParamLoader instead of native rclcpp interfaces.
- Why: It simplifies loading from launch/config files and automatically verifies if parameters were successfully loaded, saving debugging time.
- Bonus: Loading matrices into config files becomes much simpler.
Subscribing and Publishing
Use mrs_lib::SubscribeHandler and mrs_lib::PublishHandler
- They are more robust than native ROS 2 interfaces.
- They come with default QoS profiles (can be modified if needed).
Services and Clients
Use mrs_lib::ServiceServerHandler and mrs_lib::ServiceClientHandler.
- ROS 2 changed from synchronous to asynchronous services, which can be more complex to handle as you need to manage futures and callbacks for responses.
- These handlers simplify the process by providing intuitive interfaces for both synchronous and asynchronous calls.
Timers
Use mrs_lib::ThreadTimer for periodic execution instead of sleep loops. This ensures your node remains responsive.
rclcpp::TimerNative ROS 2 timers have known issues that can lead to high CPU usage. Always prefer mrs_lib::ThreadTimer.
Implementation Pattern: Define the timer type once to allow easy switching:
#if USE_ROS_TIMER == 1
typedef mrs_lib::ROSTimer TimerType;
#else
typedef mrs_lib::ThreadTimer TimerType; // Preferred
#endif
Logging
- Always use ROS 2 loggers instead of
print()orstd::cout. - Prioritize log levels appropriately:
- INFO: Normal operation, logged for informational purposes.
- WARN: Unexpected but recoverable, might require attention.
- ERROR: Critical failure; system no longer operates correctly, requires immediate action.
Pro Tip: Avoid log spam in high-frequency callbacks! Use throttled logs (
RCLCPP_INFO_THROTTLE) to keep output clean.