quarta-feira, 6 de abril de 2022

ROS 2: Creating a Simple Publisher / Subscriber

     We'll create a simple example of a publisher and a subscriber like we did for DDS, but this time using ROS to manage the process communication for us.

    By the end of this example, we'll have two nodes running, one of them will be publishing Hello World! and the other will be receiving it.


    First we create a new package called hello_pubsub

ros2 pkg create --build-type ament_cmake hello_pubsub

    

    Now, let's create the HelloPublisher class on a file called hello_publisher.cpp


#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

#define MAX_MESSAGES_QUEUE 10

class HelloPublisher : public rclcpp::Node {
private:
size_t count;
rclcpp::TimerBase::SharedPtr timer;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher;

void send_message () {
auto msg = std_msgs::msg::String();
msg.data = "Hello World! " + std::to_string(count);
count++;
publisher->publish(msg);

RCLCPP_INFO(this->get_logger(), "message #%d sent", count);
}

public:
HelloPublisher() : Node("hello_publisher"), count(0) {
publisher = this->create_publisher<std_msgs::msg::String>(
                "hello_world_messages_topic", MAX_MESSAGES_QUEUE);

/* A timer will be sending a message each 500ms */
timer = this->create_wall_timer(500ms,
                std::bind(&HelloPublisher::send_message, this));
}
};

int main (int argc, char *argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<HelloPublisher>());
rclcpp::shutdown();
return 0;
}



    The publisher is very straighforward. ROS handles all the trouble here. All we have to do is to create an object of type rclcpp::Publisher that is typed to msg::String, because it is what we're sending.
    
    After that, we need to periodically send our message (because this is our USE CASE for this testing project)

    And that's it.

    The holding class, HelloPublisher, must inherits from the Node class, in order to be a "node" in the ROS process tree,

    
Now that we've created the publisher, let's do the same thing for the subscriber. We could repeat all of this and create it in another package, but for now let's create it on the same package as the publisher

    We'll name it hello_subscriber.cpp


#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using std::placeholders::_1;

#define MAX_MESSAGES_QUEUE 10

class HelloSubscriber : public rclcpp::Node {
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber;

void read_message (const std_msgs::msg::String & msg) {
RCLCPP_INFO(this->get_logger(), "received: '%s'",
                msg.data.c_str());
}

public:
HelloSubscriber() : Node("hello_subscriber") {
subscriber = this->create_subscription<std_msgs::msg::String>(
                "hello_world_messages_topic", MAX_MESSAGES_QUEUE,
                std::bind(&HelloSubscriber::read_message, this, _1));
}
};

int main (int argc, char *argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<HelloSubscriber>());
rclcpp::shutdown();
return 0;
}


The subscriber is also very straighforward. ROS handles all the trouble here too. 

    All we have to do is to create an object of type rclcpp::Subscription that is typed to msg::String, because it is what we're expecting, set the topic we wish to observe and then define a callback function that get's called whenever something is published on the topic .
    
    And that's it.


    To finish our example, we need to set up things to run.  

    We need to edit the package.xml file to add dependencies and the CMakeList.txt to teach the compiler how to compile our code.

    On package.xml, we add the dependecies for the standard message format and the rclcpp:

  <export>
<build_type>ament_cmake</build_type>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
</export>


    On CMakeLists.txt, we also add dependencies for those packages, and add executable configurations. Another thing that we add is the install( )  method, which is responsible for defining a path to the output of the building.


cmake_minimum_required(VERSION 3.8)
project(hello_pubsub)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(publisher src/hello_publisher.cpp)
add_executable(subscriber src/hello_subscriber.cpp)

ament_target_dependencies(publisher rclcpp std_msgs)
ament_target_dependencies(subscriber rclcpp std_msgs)

install(TARGETS
publisher
subscriber
DESTINATION lib/${PROJECT_NAME})

ament_package()



    All set. Now, let's download the dependencies by issuing:

rosdep install -i --from-path src --rosdistro galactic -y
    And then, we compile our new package

colcon build --packages-select hello_pubsub
    Finally, let's open two separate consoles, one running the publisher and the other running the subscriber


Publisher
. install/setup.bash
ros2 run hello_pubsub publisher
Subscriber
. install/setup.bash
ros2 run hello_pubsub subscriber
[INFO] [1649219711.222277763] [hello_publisher]: message #86 sent
[INFO] [1649219711.722307938] [hello_publisher]: message #87 sent
[INFO] [1649219712.222268412] [hello_publisher]: message #88 sent
[INFO] [1649219712.722280066] [hello_publisher]: message #89 sent
[INFO] [1649219713.222285281] [hello_publisher]: message #90 sent
(...)

[INFO] [1649219711.722458435] [hello_subscriber]: received: 'Hello World! 86'
[INFO] [1649219712.222437726] [hello_subscriber]: received: 'Hello World! 87'
[INFO] [1649219712.722470352] [hello_subscriber]: received: 'Hello World! 88'
[INFO] [1649219713.222397218] [hello_subscriber]: received: 'Hello World! 89'
(..)

Nenhum comentário:

Postar um comentário

ROS2: Executables, Plugins and Components

    Introduction       In ROS-2, we have basically three types of code-run generation:  an executable, a plugin and a component.       An ex...