ROS2 Parameters and Actions: Configuration and Asynchronous Communication
Parameters in ROS2
What are Parameters?
Parameters in ROS2 serve as configuration values for nodes, functioning similarly to variables that can be modified to alter node behavier dynamically. They provide a mechanism for runtime configuration without requiring code changes.
Supported Paramter Types
ROS2 supports several parameter types:
boolandbool[]: Boolean values for on/off controlsint64andint64[]: Integer values for numeric configurationsfloat64andfloat64[]: Floating-point values for precise measurementsstringandstring[]: Text-based configurationsbyte[]: Binary data for complex information like images or point clouds
Working with Parameters
Let's explore parameter usage with the turtlesim example:
# Start the turtlesim node
ros2 run turtlesim turtlesim_node
# Start the keyboard teleop node
ros2 run turtlesim turtle_teleop_key
# List all parameters in the system
ros2 param list
# Get detailed information about a specific parameter
ros2 param describe /turtlesim background_b
# Retrieve current parameter value
ros2 param get /turtlesim background_b
# Modify parameter values to change background color
ros2 param set /turtlesim background_r 44
ros2 param set /turtlesim background_g 156
ros2 param set /turtlesim background_b 10
Note that these changes are temporary. To persist parameters across restarts:
# Save parameters to a YAML file
ros2 param dump /turtlesim > turtlesim.yaml
# Load parameters from file
ros2 param load /turtlesim turtlesim.yaml
Implementing Parameters with RCLCPP
Here's how to implement parameters in a C++ node:
#include "rclcpp/rclcpp.hpp"
class ParameterExample : public rclcpp::Node {
public:
ParameterExample() : Node("parameter_example") {
// Declare a parameter with default value
this->declare_parameter<int64_t>("calculation_period", 1000);
// Retrieve parameter value
int64_t period;
this->get_parameter("calculation_period", period);
RCLCPP_INFO(this->get_logger(), "Calculation period: %ld", period);
}
};
Actions in ROS2
Action Components
Actions in ROS2 consist of three main parts:
- Goal: The task the client wants the server to perform
- Feedback: Progress updates during task exeuction
- Result: Final outcome of the task
CLI Tools for Actions
Listing Available Actions
# List all actions in the system
ros2 action list
# Show action types
ros2 action list -t
# Display action interface details
ros2 interface show turtlesim/action/RotateAbsolute
Action Information
# Get detailed action information
ros2 action info /turtle1/rotate_absolute
Sending Goals
# Send a goal to rotate the turtle
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.6}"
# Send with feedback
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.5}" --feedback
Custom Action Interface
Let's create a custom action for robot movement:
Define the Action Interface
# MoveRobot.action
float32 distance # Distance to move
---
float32 pose # Current position
int8 status # Movement status
---
float32 pose # Final position
Action Server Implementation
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "robot_control_interfaces/action/move_robot.hpp"
class RobotMover : public rclcpp::Node {
public:
using MoveRobot = robot_control_interfaces::action::MoveRobot;
using GoalHandle = rclcpp_action::ServerGoalHandle<MoveRobot>;
explicit RobotMover() : Node("robot_mover") {
using namespace std::placeholders;
action_server_ = rclcpp_action::create_server<MoveRobot>(
this,
"move_robot",
std::bind(&RobotMover::handle_goal, this, _1, _2),
std::bind(&RobotMover::handle_cancel, this, _1),
std::bind(&RobotMover::handle_accepted, this, _1));
}
private:
rclcpp_action::Server<MoveRobot>::SharedPtr action_server_;
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID & uuid,
std::shared_ptr<const MoveRobot::Goal> goal) {
RCLCPP_INFO(this->get_logger(), "Received goal request with distance: %f", goal->distance);
if (std::abs(goal->distance) > 100.0) {
RCLCPP_WARN(this->get_logger(), "Distance too large, rejecting goal");
return rclcpp_action::GoalResponse::REJECT;
}
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse handle_cancel(
const std::shared_ptr<GoalHandle> goal_handle) {
RCLCPP_INFO(this->get_logger(), "Received cancel request");
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_accepted(const std::shared_ptr<GoalHandle> goal_handle) {
std::thread{std::bind(&RobotMover::execute, this, goal_handle)}.detach();
}
void execute(const std::shared_ptr<GoalHandle> goal_handle) {
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<MoveRobot::Feedback>();
auto result = std::make_shared<MoveRobot::Result>();
RCLCPP_INFO(this->get_logger(), "Executing movement of %f", goal->distance);
// Simulate movement
float current_position = 0.0;
float target_position = goal->distance;
float step = 0.1;
rclcpp::Rate rate(2);
while (rclcpp::ok() && std::abs(current_position - target_position) > 0.01) {
if (goal_handle->is_canceling()) {
result->pose = current_position;
goal_handle->canceled(result);
RCLCPP_INFO(this->get_logger(), "Goal canceled");
return;
}
current_position += step;
feedback->pose = current_position;
feedback->status = MoveRobot::Feedback::STATUS_MOVING;
goal_handle->publish_feedback(feedback);
rate.sleep();
}
result->pose = current_position;
goal_handle->succeed(result);
RCLCPP_INFO(this->get_logger(), "Movement completed");
}
};
Action Client Implementation
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "robot_control_interfaces/action/move_robot.hpp"
class RobotController : public rclcpp::Node {
public:
using MoveRobot = robot_control_interfaces::action::MoveRobot;
using GoalHandle = rclcpp_action::ClientGoalHandle<MoveRobot>;
explicit RobotController() : Node("robot_controller") {
action_client_ = rclcpp_action::create_client<MoveRobot>(this, "move_robot");
// Send a goal after 1 second
timer_ = this->create_wall_timer(
std::chrono::seconds(1),
std::bind(&RobotController::send_goal, this));
}
private:
rclcpp_action::Client<MoveRobot>::SharedPtr action_client_;
rclcpp::TimerBase::SharedPtr timer_;
void send_goal() {
if (!action_client_->wait_for_action_server(std::chrono::seconds(10))) {
RCLCPP_ERROR(this->get_logger(), "Action server not available");
return;
}
auto goal_msg = std::make_shared<MoveRobot::Goal>();
goal_msg->distance = 5.0;
RCLCPP_INFO(this->get_logger(), "Sending movement goal");
auto send_goal_options = rclcpp_action::Client<MoveRobot>::SendGoalOptions();
send_goal_options.goal_response_callback =
std::bind(&RobotController::goal_response_callback, this, _1);
send_goal_options.feedback_callback =
std::bind(&RobotController::feedback_callback, this, _1, _2);
send_goal_options.result_callback =
std::bind(&RobotController::result_callback, this, _1);
action_client_->async_send_goal(goal_msg, send_goal_options);
}
void goal_response_callback(GoalHandle::SharedPtr goal_handle) {
if (!goal_handle) {
RCLCPP_ERROR(this->get_logger(), "Goal rejected");
return;
}
RCLCPP_INFO(this->get_logger(), "Goal accepted");
}
void feedback_callback(
GoalHandle::SharedPtr,
const std::shared_ptr<const MoveRobot::Feedback> feedback) {
RCLCPP_INFO(this->get_logger(), "Current position: %f", feedback->pose);
}
void result_callback(const GoalHandle::WrappedResult & result) {
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(), "Final position: %f", result.result->pose);
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal aborted");
break;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal canceled");
break;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
break;
}
}
};
This implementation demonstrates how to create a custom action interface and implement both the server and client sides for asynchronous robot movement control.