2、创建第一个节点(C++)

创建您的第一个节点(C++)

Turtlebot4的教程将介绍创建ROS2包和用C++编写ROS2节点的步骤。有关Python示例,请单击此处here。这些步骤类似于 ROS 2 Tutorial教程,但侧重于与TurtleBot 4的交互。有关源代码,请单击此处 here

 Note

您可以在TurtleBot 4的Raspberry Pi或您的电脑上遵循本教程。


创建工作空间

如果您还没有工作空间,请打开一个终端并在您选择的目录中创建一个:

mkdir ~/turtlebot4_ws/src -p


创建包和节点

  • 您需要创建一个ROS 2包来保存您的文件。在本教程中,我们将创建一个名为 turtlebot4_cpp_tutorials的包,其中包含一个称为turtlebot4_first_cpp_node的节点。

    source /opt/ros/humble/setup.bash cd ~/turtlebot4_ws/src ros2 pkg create --build-type ament_cmake --node-name turtlebot4_first_cpp_node turtlebot4_cpp_tutorials

这将创建一个 turtlebot4_cpp_tutorials文件夹,并用一个基本的“Hello World”节点以及ROS 2 C++包所需的CMakeLists.txt和package.xml文件填充它。

Write your node

下一步是开始编码。在本教程中,我们的目标是使用Create®3界面按钮1更改Create®3光环的颜色。在您最喜欢的文本编辑器中打开“Hello World”.cpp文件,该文件位于~/turtlebot4_ws/src/turtlebot4_cpp_tutorials/src/turtlebot4_first_cpp_node.cpp

添加您的依赖项

对于本教程,我们需要使用 rclcpp 和 irobot_create_msgs包。rclcpp包允许我们创建ROS2节点,并使我们能够完全访问C++中的所有基本ROS2功能。 irobot_create_msgs包使我们能够访问create®3用于读取按钮按压和控制灯环的自定义消息。

在CMakeLists.txt文件中,在 find_package(ament_cmake REQUIRED)下添加以下行:

find_package(rclcpp REQUIRED) find_package(irobot_create_msgs REQUIRED)

并在 add_executable(turtlebot4_first_cpp_node src/turtlebot4_first_cpp_node.cpp)下添加此行:

ament_target_dependencies(turtlebot4_first_cpp_node rclcpp irobot_create_msgs)

在package.xml中,在 <buildtool_depend>ament_cmake</buildtool_depend>下添加以下行:

<depend>rclcpp</depend> <depend>irobot_create_msgs</depend>

最后,在nodes .cpp文件中,您需要包含以下标头:

#include <chrono> #include <functional> #include <memory> #include <string>  #include "rclcpp/rclcpp.hpp"  #include "irobot_create_msgs/msg/interface_buttons.hpp" #include "irobot_create_msgs/msg/lightring_leds.hpp"

创建一个类

既然已经设置了依赖项,我们就可以创建一个从 rclcpp::Node类继承的类。我们将这个类称为 TurtleBot4FirstNode.

class TurtleBot4FirstNode : public rclcpp::Node { public:   TurtleBot4FirstNode()   : Node("turtlebot4_first_cpp_node")   {} };

注意,我们的类调用 Node构造函数,并将节点的名称turtlebot4_first_cpp_node传递给它。

我们现在可以在 main函数中创建我们的节点并旋转它。由于我们的节点是空的,所以节点会被创建,但它不会做任何事情。

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

订阅Create®3界面按钮

我们的下一步是订阅Create®3界面按钮主题以接收按钮按压。

我们需要为订阅创建一个 rclcpp::Subscription以及一个回调函数。每次我们收到关于界面按钮主题的消息时,都会调用回调函数。

class TurtleBot4FirstNode : public rclcpp::Node { public:   TurtleBot4FirstNode()   : Node("turtlebot4_first_cpp_node")   {     // Subscribe to the /interface_buttons topic     interface_buttons_subscriber_ =       this->create_subscription<irobot_create_msgs::msg::InterfaceButtons>(       "/interface_buttons",       rclcpp::SensorDataQoS(),       std::bind(&TurtleBot4FirstNode::interface_buttons_callback, this, std::placeholders::_1));   } private:   // Interface buttons subscription callback   void interface_buttons_callback(     const irobot_create_msgs::msg::InterfaceButtons::SharedPtr create3_buttons_msg)   {}   // Interface Button Subscriber   rclcpp::Subscription<irobot_create_msgs::msg::InterfaceButtons>::SharedPtr interface_buttons_subscriber_; };

请注意, interface_buttons_subscriber_使用 InterfaceButtons消息类型,服务质量为 rclcpp::SensorDataQoS()。这些参数必须与主题匹配,否则订阅将失败。如果您不确定主题使用的消息类型或QoS,可以使用ROS 2 CLI查找此信息。

调用 ros2 topic info /<topic> --verbose以获取完整的详细信息。

topic_info.png
ROS 2主题信息

Test Create® 3 按钮 1

现在我们已经订阅了,让我们通过每次按下按钮1时打印一条消息来测试我们的节点。

编辑 interface_buttons_callback函数如下所示:

  // Interface buttons subscription callback   void interface_buttons_callback(     const irobot_create_msgs::msg::InterfaceButtons::SharedPtr create3_buttons_msg)   {     // Button 1 is pressed     if (create3_buttons_msg->button_1.is_pressed) {       RCLCPP_INFO(this->get_logger(), "Button 1 Pressed!");     }   }

现在,每当我们收到关于 /interface_buttons主题的消息时,我们都会检查按钮1是否被按下,如果是,则节点将打印一条消息。

To test this out, we will need to build our package using colcon:为了测试这一点,我们需要使用 colcon构建我们的包:

cd ~/turtlebot4_ws colcon build --packages-select turtlebot4_cpp_tutorials source install/local_setup.bash

 --packages-select标志允许您输入要构建的任意数量的包,以防您不想在工作区中构建所有包。

现在,尝试运行节点:

ros2 run turtlebot4_cpp_tutorials turtlebot4_first_cpp_node

当你运行它时,除非你按下TurtleBot 4上的按钮1,否则什么都不会发生。

按下按钮,您应该会在终端中看到以下消息:

[INFO] [1652379086.090977658] [turtlebot4_first_cpp_node]: Button 1 Pressed!

 

Tip

像这样打印消息是调试代码的好方法。


创建光环发布者

现在我们可以接收按钮按下,让我们创建一个lightring发布器。

class TurtleBot4FirstNode : public rclcpp::Node { public:   TurtleBot4FirstNode()   : Node("turtlebot4_first_cpp_node")   {     // Subscribe to the /interface_buttons topic     interface_buttons_subscriber_ =       this->create_subscription<irobot_create_msgs::msg::InterfaceButtons>(       "/interface_buttons",       rclcpp::SensorDataQoS(),       std::bind(&TurtleBot4FirstNode::interface_buttons_callback, this, std::placeholders::_1));     // Create a publisher for the /cmd_lightring topic     lightring_publisher_ = this->create_publisher<irobot_create_msgs::msg::LightringLeds>(       "/cmd_lightring",       rclcpp::SensorDataQoS());   } private:   // Interface buttons subscription callback   void interface_buttons_callback(     const irobot_create_msgs::msg::InterfaceButtons::SharedPtr create3_buttons_msg)   {     // Button 1 is pressed     if (create3_buttons_msg->button_1.is_pressed) {       RCLCPP_INFO(this->get_logger(), "Button 1 Pressed!");     }   }   // Interface Button Subscriber   rclcpp::Subscription<irobot_create_msgs::msg::InterfaceButtons>::SharedPtr interface_buttons_subscriber_;   // Lightring Publisher   rclcpp::Publisher<irobot_create_msgs::msg::LightringLeds>::SharedPtr lightring_publisher_; };
 Note

Lightring发布者使用 LightringLeds消息类型。

Next, lets create a function that will populate a LightringLeds message, and publish it.接下来,让我们创建一个函数来填充 LightringLeds消息并发布它。

Add this code below your interface_buttons_callback function:在您的 interface_buttons_callback函数下面添加以下代码:

  // Perform this function when Button 1 is pressed.   void button_1_function()   {     // Create a ROS 2 message     auto lightring_msg = irobot_create_msgs::msg::LightringLeds();     // Stamp the message with the current time     lightring_msg.header.stamp = this->get_clock()->now();     // Override system lights     lightring_msg.override_system = true;     // LED 0     lightring_msg.leds[0].red = 255;     lightring_msg.leds[0].blue = 0;     lightring_msg.leds[0].green = 0;     // LED 1     lightring_msg.leds[1].red = 0;     lightring_msg.leds[1].blue = 255;     lightring_msg.leds[1].green = 0;     // LED 2     lightring_msg.leds[2].red = 0;     lightring_msg.leds[2].blue = 0;     lightring_msg.leds[2].green = 255;     // LED 3     lightring_msg.leds[3].red = 255;     lightring_msg.leds[3].blue = 255;     lightring_msg.leds[3].green = 0;     // LED 4     lightring_msg.leds[4].red = 255;     lightring_msg.leds[4].blue = 0;     lightring_msg.leds[4].green = 255;     // LED 5     lightring_msg.leds[5].red = 0;     lightring_msg.leds[5].blue = 255;     lightring_msg.leds[5].green = 255;     // Publish the message     lightring_publisher_->publish(lightring_msg);   }

This function creates a LightringLeds message and populates the parameters.此函数创建 LightringLeds消息并填充参数。

我们首先在消息上标记当前时间:

lightring_msg.header.stamp = this->get_clock()->now();

Then we set the override_system parameter to true so that our command overrides whatever commands the Create® 3 is sending to the lightring.然后,我们将 override_system参数设置为 true,以便我们的命令覆盖Create®3发送到光环的任何命令。

lightring_msg.override_system = true;

Next, we populate the 6 LEDs in the leds array with whatever colours we want.接下来,我们用我们想要的任何颜色填充 leds阵列中的6个LED。

// LED 0 lightring_msg.leds[0].red = 255; lightring_msg.leds[0].blue = 0; lightring_msg.leds[0].green = 0; // LED 1 lightring_msg.leds[1].red = 0; lightring_msg.leds[1].blue = 255; lightring_msg.leds[1].green = 0; // LED 2 lightring_msg.leds[2].red = 0; lightring_msg.leds[2].blue = 0; lightring_msg.leds[2].green = 255; // LED 3 lightring_msg.leds[3].red = 255; lightring_msg.leds[3].blue = 255; lightring_msg.leds[3].green = 0; // LED 4 lightring_msg.leds[4].red = 255; lightring_msg.leds[4].blue = 0; lightring_msg.leds[4].green = 255; // LED 5 lightring_msg.leds[5].red = 0; lightring_msg.leds[5].blue = 255; lightring_msg.leds[5].green = 255;
Tip

每个RGB值可以设置在0和255之间。您可以查找任何颜色的RGB值并在此处进行设置。

最后,我们发布消息。

lightring_publisher_->publish(lightring_msg);

按下按钮即可发布照明环命令

现在我们可以将我们的界面按钮订阅连接到我们的lightring发行商。只需在 interface_buttons_callback.中调用button_1_function即可。

  // Interface buttons subscription callback   void interface_buttons_callback(     const irobot_create_msgs::msg::InterfaceButtons::SharedPtr create3_buttons_msg)   {     // Button 1 is pressed     if (create3_buttons_msg->button_1.is_pressed) {       RCLCPP_INFO(this->get_logger(), "Button 1 Pressed!");       button_1_function();     }   }

通过构建包并像以前一样运行节点来测试这一点。

按下按钮1,指示灯应如下所示:

lightring.jpglightring.jpg

只需按下按钮即可控制明亮的色彩!

切换照明环

你会注意到,一旦你设置了发光二极管,它们将永远保持原样。让我们每次按下按钮时都切换灯的打开或关闭。

添加布尔值以跟踪灯光状态:

bool lights_on_;

初始化类构造函数中的布尔值:

TurtleBot4FirstNode()   : Node("turtlebot4_first_cpp_node"), lights_on_(false)

并修改 button_1_function以切换灯光:

  void button_1_function()   {     // Create a ROS 2 message     auto lightring_msg = irobot_create_msgs::msg::LightringLeds();     // Stamp the message with the current time     lightring_msg.header.stamp = this->get_clock()->now();     // Lights are currently off     if (!lights_on_) {       // Override system lights       lightring_msg.override_system = true;       // LED 0       lightring_msg.leds[0].red = 255;       lightring_msg.leds[0].blue = 0;       lightring_msg.leds[0].green = 0;       // LED 1       lightring_msg.leds[1].red = 0;       lightring_msg.leds[1].blue = 255;       lightring_msg.leds[1].green = 0;       // LED 2       lightring_msg.leds[2].red = 0;       lightring_msg.leds[2].blue = 0;       lightring_msg.leds[2].green = 255;       // LED 3       lightring_msg.leds[3].red = 255;       lightring_msg.leds[3].blue = 255;       lightring_msg.leds[3].green = 0;       // LED 4       lightring_msg.leds[4].red = 255;       lightring_msg.leds[4].blue = 0;       lightring_msg.leds[4].green = 255;       // LED 5       lightring_msg.leds[5].red = 0;       lightring_msg.leds[5].blue = 255;       lightring_msg.leds[5].green = 255;     }     // Lights are currently on     else {       // Disable system override. The system will take back control of the lightring.       lightring_msg.override_system = false;     }     // Publish the message     lightring_publisher_->publish(lightring_msg);     // Toggle the lights on status     lights_on_ = !lights_on_;   }

现在,如果我们再次按下按钮1,Create®3将重新控制光环。

您的第一个C++节点

您已经完成了第一个C++节点的编写!最后的 .cpp文件应该如下所示:

#include <chrono> #include <functional> #include <memory> #include <string>  #include "rclcpp/rclcpp.hpp"  #include "irobot_create_msgs/msg/interface_buttons.hpp" #include "irobot_create_msgs/msg/lightring_leds.hpp"  class TurtleBot4FirstNode : public rclcpp::Node { public:   TurtleBot4FirstNode()   : Node("turtlebot4_first_cpp_node"), lights_on_(false)   {     // Subscribe to the /interface_buttons topic     interface_buttons_subscriber_ =       this->create_subscription<irobot_create_msgs::msg::InterfaceButtons>(       "/interface_buttons",       rclcpp::SensorDataQoS(),       std::bind(&TurtleBot4FirstNode::interface_buttons_callback, this, std::placeholders::_1));     // Create a publisher for the /cmd_lightring topic     lightring_publisher_ = this->create_publisher<irobot_create_msgs::msg::LightringLeds>(       "/cmd_lightring",       rclcpp::SensorDataQoS());   } private:   // Interface buttons subscription callback   void interface_buttons_callback(     const irobot_create_msgs::msg::InterfaceButtons::SharedPtr create3_buttons_msg)   {     // Button 1 is pressed     if (create3_buttons_msg->button_1.is_pressed) {       RCLCPP_INFO(this->get_logger(), "Button 1 Pressed!");       button_1_function();     }   }   // Perform a function when Button 1 is pressed.   void button_1_function()   {     // Create a ROS 2 message     auto lightring_msg = irobot_create_msgs::msg::LightringLeds();     // Stamp the message with the current time     lightring_msg.header.stamp = this->get_clock()->now();     // Lights are currently off     if (!lights_on_) {       // Override system lights       lightring_msg.override_system = true;       // LED 0       lightring_msg.leds[0].red = 255;       lightring_msg.leds[0].blue = 0;       lightring_msg.leds[0].green = 0;       // LED 1       lightring_msg.leds[1].red = 0;       lightring_msg.leds[1].blue = 255;       lightring_msg.leds[1].green = 0;       // LED 2       lightring_msg.leds[2].red = 0;       lightring_msg.leds[2].blue = 0;       lightring_msg.leds[2].green = 255;       // LED 3       lightring_msg.leds[3].red = 255;       lightring_msg.leds[3].blue = 255;       lightring_msg.leds[3].green = 0;       // LED 4       lightring_msg.leds[4].red = 255;       lightring_msg.leds[4].blue = 0;       lightring_msg.leds[4].green = 255;       // LED 5       lightring_msg.leds[5].red = 0;       lightring_msg.leds[5].blue = 255;       lightring_msg.leds[5].green = 255;     }     // Lights are currently on     else {       // Disable system override. The system will take back control of the lightring.       lightring_msg.override_system = false;     }     // Publish the message     lightring_publisher_->publish(lightring_msg);     // Toggle the lights on status     lights_on_ = !lights_on_;   }   // Interface Button Subscriber   rclcpp::Subscription<irobot_create_msgs::msg::InterfaceButtons>::SharedPtr     interface_buttons_subscriber_;   // Lightring Publisher   rclcpp::Publisher<irobot_create_msgs::msg::LightringLeds>::SharedPtr lightring_publisher_;   // Lights on status   bool lights_on_; }; int main(int argc, char * argv[]) {   rclcpp::init(argc, argv);   rclcpp::spin(std::make_shared<TurtleBot4FirstNode>());   rclcpp::shutdown();   return 0; }

不要忘记在运行节点之前重新构建包。