Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

ROS2 Parameters and Actions: Configuration and Asynchronous Communication

Tech May 10 3

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:

  • bool and bool[]: Boolean values for on/off controls
  • int64 and int64[]: Integer values for numeric configurations
  • float64 and float64[]: Floating-point values for precise measurements
  • string and string[]: Text-based configurations
  • byte[]: 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.

Tags: ROS2

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.