Extend ROS Interface on Cartesian Motion Base

This tutorial will * Show how to realize a topic and a service interface on Cartesian Motion Base (CMB). * Print the received message from the topic. * Trigger a motion task by calling the service. * These components will be initialized in the on_init() function.

1. Create a new header file

cd ~/cmb_ws/src/my_cartesian_motion/include/my_cartesian_motion

Create a my_cartesian_interface.hpp file.

#ifndef MY_CARTESIAN_INTERFACE_HPP
#define MY_CARTESIAN_INTERFACE_HPP

#include "cartesian_motion_base/cartesian_motion_base.hpp"
#include "my_cartesian_motion/my_cartesian_motion_config.hpp"
#include "std_msgs/msg/string.hpp"
#include "std_srvs/srv/set_bool.hpp"

namespace my_cartesian_motion
{
class MyCartesianInterface : public cartesian_motion_base::CartesianMotionBase
{
public:
    MyCartesianInterface(const std::string &node_name,
       std::vector<cartesian_motion_base::RobotConfig> robot_configs,
       uint16_t rate)
    : cartesian_motion_base::CartesianMotionBase(
          node_name, robot_configs, rate){};

private:
    void on_init() override; // must override for custom initialization
    void tasks_init() override; // must override for task initialization

    // ROS interface
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber_;
    rclcpp::Service<std_srvs::srv::SetBool>::SharedPtr service_;

    bool service_flag_ = false;
};
} // namespace my_cartesian_motion
#endif // MY_CARTESIAN_INTERFACE_HPP

2. Implement the interface in cpp file

cd ~/cmb_ws/src/my_cartesian_motion/src

Create a my_cartesian_interface.cpp file.

#include "my_cartesian_motion/my_cartesian_interface.hpp"

using namespace std::chrono_literals;

namespace my_cartesian_motion
{
void MyCartesianInterface::on_init()
{
    // Subscriber example
    subscriber_ = this->create_subscription<std_msgs::msg::String>(
        "my_topic", 10,
        [this](const std_msgs::msg::String::SharedPtr msg){
            RCLCPP_INFO(this->get_logger(), "Received: %s", msg->data.c_str());
        });

    // Service example
    service_ = this->create_service<std_srvs::srv::SetBool>(
        "my_service",
        [this](const std::shared_ptr<std_srvs::srv::SetBool::Request> request,
               std::shared_ptr<std_srvs::srv::SetBool::Response> response){
            service_flag_ = request->data;
            response->success = true;
            response->message = "Trigger flag set to " + std::to_string(service_flag_);;
        });
}

void MyCartesianInterface::tasks_init()
{
    // Go Home by joint space
    task_pushback(TaskPtr("Go Home", [this](){
        std::vector<double> home_joints = {0.0, -1.57, 1.57, -1.57, -1.57, 0.0};

        // Create joint map
        JointMap joint_map;
        joint_map["ur"] = home_joints;
        if(joint_move(joint_map, 5.0)){
            set_task_finished();
        }
    }));

    // Wait for service trigger
    task_pushback(TaskPtr("Wait for Service Trigger", [this](){
        if(service_flag_){
            RCLCPP_INFO(this->get_logger(), "Service triggered, proceeding to next task.");
            set_task_finished();
        }
    }));

    // Move up with 0.1m in z direction by cartesian space
    task_pushback(TaskPtr("Move Up", [this](){
        geometry_msgs::msg::PoseStamped current_pose = get_current_pose("ur");
        geometry_msgs::msg::PoseStamped target_pose = current_pose;
        target_pose.pose.position.z += 0.1;
        // Create pose map
        PoseMap pose_map;
        pose_map["ur"] = target_pose;
        if(move(pose_map, 1.0)){
            set_task_finished();
        }
    }));

    // Shut down task
    task_pushback(TaskPtr("shutdown", [this](){
        rclcpp::shutdown();
    }));
}
} // namespace my_cartesian_motion

int main(int argc, char const *argv[])
{
    rclcpp::init(argc, argv);
    rclcpp::executors::SingleThreadedExecutor executor;
    auto robot_configs = my_cartesian_motion::get_test_robot_configs();
    auto my_interface_node = std::make_shared<my_cartesian_motion::MyCartesianInterface>(
        "my_cartesian_interface_node", robot_configs, 125);

    my_interface_node->start();

    executor.add_node(my_interface_node);
    executor.spin();
    rclcpp::shutdown();
    return 0;
}

3. Build and run the node

3.1 Modyfy CMakeLists.txt

Add the following lines in CMakeLists.txt of my_cartesian_interface package:

Note

Make sure the ament_package() is at the end of the CMakeLists.txt file.

add_executable(my_cartesian_interface src/my_cartesian_interface.cpp)
ament_target_dependencies(my_cartesian_interface ${THIS_PACKAGE_INCLUDE_DEPENDS})
target_include_directories(my_cartesian_interface PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

install(
    TARGETS
    my_cartesian_interface
    DESTINATION lib/${PROJECT_NAME}
)
cd ~/cmb_ws
colcon build --symlink-install
source ~/cmb_ws/install/setup.bash
ros2 run my_cartesian_motion my_cartesian_interface

Note

Keep your simulation environment running while you run this node.

Now, you have created a topic subscriber and a service server on Cartesian Motion Base.

Open a new terminal and send a test message to the topic:

source ~/cmb_ws/install/setup.bash
ros2 topic pub /my_topic std_msgs/msg/String "{data: 'Hello from topic'}" --once

You should see the message printed in the terminal running the my_cartesian_interface node.

Then, call the service to set the trigger flag:

source ~/cmb_ws/install/setup.bash
ros2 service call /my_service std_srvs/srv/SetBool "{data: true}"

The robot should move up by 0.1m in the z direction after the service is called.

Tip

You can further extend publisher and client interfaces similarly by following the ROS2 communication patterns.

Congratulations! You have successfully extended the ROS interface on Cartesian Motion Base.