使用机器人操作系统ROS 2和仿真软件Gazebo 9主题进阶实战(七)- mobot速度发布与里程计订阅

举报
zhangrelay 发表于 2021/07/15 01:24:54 2021/07/15
【摘要】 在ROS2课程中已经学过并掌握了一个基本的发布器和订阅器(C++),官网的教程全部掌握大致需要20分钟吧。 这过程包括: 创建一个功能包编程实现一个发布节点编程实现一个订阅节点编译与运行 这部分内容作为复习,放置于文末,本文在Gazebo 9仿真环境中,使用mobot编程实现一个速度发布器和里程计订阅。 实现效果参考如下视频: ROS2和Gazebo9中mob...

在ROS2课程中已经学过并掌握了一个基本的发布器和订阅器(C++),官网的教程全部掌握大致需要20分钟吧。

这过程包括:

  • 创建一个功能包
  • 编程实现一个发布节点
  • 编程实现一个订阅节点
  • 编译与运行

这部分内容作为复习,放置于文末,本文在Gazebo 9仿真环境中,使用mobot编程实现一个速度发布器和里程计订阅。

实现效果参考如下视频:

ROS2和Gazebo9中mobot速度发布和坐标订阅


在mobot/src文件夹,新建pub_vel.cpp和sub_pose.cpp。

要点:

  • 随机速度如何实现?
  • 速度发布和里程计订阅需要包含的头文件?

提示:

  • rand()
  • geometry_msgs/msg/twist
  • nav_msgs/msg/odometry

为了避免难度过大,这里提供turtlesim随机速度发布和里程计订阅的源代码示例。

pubvel.cpp


  
  
  1. #include <iostream>
  2. #include <chrono>
  3. #include "rclcpp/rclcpp.hpp"
  4. #include "geometry_msgs/msg/twist.hpp"
  5. using namespace std::chrono_literals;
  6. int main(int argc, char * argv[])
  7. {
  8. rclcpp::init(argc, argv);
  9. auto node = rclcpp::Node::make_shared("publish_velocity");
  10. auto publisher = node->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel", 10);
  11. geometry_msgs::msg::Twist message;
  12. rclcpp::WallRate loop_rate(500ms);
  13. while (rclcpp::ok()) {
  14. message.linear.x = ((double)rand()/(RAND_MAX));
  15. message.angular.z = 4.0*((double)rand()/(RAND_MAX))-2;
  16. RCLCPP_INFO(node->get_logger(), "Publishing /turtle1/cmd_vel : linear='%f',angular='%f'", message.linear.x, message.angular.z);
  17. publisher->publish(message);
  18. rclcpp::spin_some(node);
  19. loop_rate.sleep();
  20. }
  21. rclcpp::shutdown();
  22. return 0;
  23. }

这样等于速度发布直接给答案了……

但是里程计订阅有所不同!

subpose.cpp


  
  
  1. #include "rclcpp/rclcpp.hpp"
  2. #include "turtlesim/msg/pose.hpp"
  3. rclcpp::Node::SharedPtr g_node = nullptr;
  4. void topic_callback(const turtlesim::msg::Pose::SharedPtr msg)
  5. {
  6. RCLCPP_INFO(g_node->get_logger(), "I heard: turtle1/pose position='%f','%f'; direction='%f'", msg->x, msg->y, msg->theta);
  7. }
  8. int main(int argc, char * argv[])
  9. {
  10. rclcpp::init(argc, argv);
  11. g_node = rclcpp::Node::make_shared("subscribe_to_pose");
  12. auto subscription =
  13. g_node->create_subscription<turtlesim::msg::Pose>("turtle1/pose", 10, topic_callback);
  14. rclcpp::spin(g_node);
  15. rclcpp::shutdown();
  16. subscription = nullptr;
  17. g_node = nullptr;
  18. return 0;
  19. }

mobot实现此功能订阅里程计位置姿态信息,请参考提示。

然后需要修改一下CMakeLists.txt,在合适位置增加如下代码:


  
  
  1. add_executable(pub_vel src/pub_vel.cpp)
  2. ament_target_dependencies(pub_vel rclcpp geometry_msgs)
  3. add_executable(sub_pose src/sub_pose.cpp)
  4. ament_target_dependencies(sub_pose rclcpp nav_msgs)
  5. install(TARGETS
  6. pub_vel
  7. sub_pose
  8. DESTINATION lib/${PROJECT_NAME})

然后编译并运行如下命令即可,分别在不同的终端哦。

  • ros2 launch mobot topic.launch.py 

如何编写launch,参考之前章节。

  • ros2 run mobot sub_pose

  • ros2 run mobot pub_vel 

当然也可以在launch中将pub_vel和sub_pose添加,这样就无需新开窗口了,如何实现?


关于发布器和订阅器的更多说明:

  • ROS1为功能实现,代码其实没有美感,更不用说精致了,优美的代码是艺术品!
  • ROS2更进一步,代码风格更好

比如发布器:

类ROS1方式实现代码如下,(不推荐,不推荐,不推荐!!!):


  
  
  1. #include <iostream>
  2. #include <chrono>
  3. #include "rclcpp/rclcpp.hpp"
  4. #include "std_msgs/msg/string.hpp"
  5. using namespace std::chrono_literals;
  6. int main(int argc, char * argv[])
  7. {
  8. rclcpp::init(argc, argv);
  9. auto node = rclcpp::Node::make_shared("minimal_publisher");
  10. auto publisher = node->create_publisher<std_msgs::msg::String>("topic", 10);
  11. std_msgs::msg::String message;
  12. auto publish_count = 0;
  13. rclcpp::WallRate loop_rate(500ms);
  14. while (rclcpp::ok()) {
  15. message.data = "Hello, world! " + std::to_string(publish_count++);
  16. RCLCPP_INFO(node->get_logger(), "Publishing: '%s'", message.data.c_str());
  17. publisher->publish(message);
  18. rclcpp::spin_some(node);
  19. loop_rate.sleep();
  20. }
  21. rclcpp::shutdown();
  22. return 0;
  23. }

但请务必注意!官方解释:

我们不推荐使用这种样式,因为在同一个可执行文件中不可能有多个节点组成。请参阅其中一个子类的例子,了解 "新 "推荐的样式。这个例子只是为了完整,因为它与 "经典 "独立的 ROS 节点类似。

新推荐样式有两种:

  • 第一种:lambda

  
  
  1. #include <chrono>
  2. #include <memory>
  3. #include "rclcpp/rclcpp.hpp"
  4. #include "std_msgs/msg/string.hpp"
  5. using namespace std::chrono_literals;
  6. class MinimalPublisher : public rclcpp::Node
  7. {
  8. public:
  9. MinimalPublisher()
  10. : Node("minimal_publisher"), count_(0)
  11. {
  12. publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
  13. auto timer_callback =
  14. [this]() -> void {
  15. auto message = std_msgs::msg::String();
  16. message.data = "Hello, ros2 world! " + std::to_string(this->count_++);
  17. RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
  18. this->publisher_->publish(message);
  19. };
  20. timer_ = this->create_wall_timer(500ms, timer_callback);
  21. }
  22. private:
  23. rclcpp::TimerBase::SharedPtr timer_;
  24. rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  25. size_t count_;
  26. };
  27. int main(int argc, char * argv[])
  28. {
  29. rclcpp::init(argc, argv);
  30. rclcpp::spin(std::make_shared<MinimalPublisher>());
  31. rclcpp::shutdown();
  32. return 0;
  33. }

这其实才是优雅的代码,当然它的问题是(初学者一眼看不懂了!):

使用了一个精致的C++11 lambda函数来缩短回调语法,但代价是让代码看起来更难理解。

  • 第二种:member_function

  
  
  1. #include <chrono>
  2. #include <memory>
  3. #include "rclcpp/rclcpp.hpp"
  4. #include "std_msgs/msg/string.hpp"
  5. using namespace std::chrono_literals;
  6. class MinimalPublisher : public rclcpp::Node
  7. {
  8. public:
  9. MinimalPublisher()
  10. : Node("minimal_publisher"), count_(0)
  11. {
  12. publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
  13. timer_ = this->create_wall_timer(
  14. 500ms, std::bind(&MinimalPublisher::timer_callback, this));
  15. }
  16. private:
  17. void timer_callback()
  18. {
  19. auto message = std_msgs::msg::String();
  20. message.data = "Hello, world! " + std::to_string(count_++);
  21. RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
  22. publisher_->publish(message);
  23. }
  24. rclcpp::TimerBase::SharedPtr timer_;
  25. rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  26. size_t count_;
  27. };
  28. int main(int argc, char * argv[])
  29. {
  30. rclcpp::init(argc, argv);
  31. rclcpp::spin(std::make_shared<MinimalPublisher>());
  32. rclcpp::shutdown();
  33. return 0;
  34. }

这段代码并不优雅,但十分规范!!!推荐使用。

使用std::bind()来注册一个成员函数作为定时器的回调。

上述三段代码所实现的功能其实是一样的。

订阅器:

类似ROS1风格,不推荐,不推荐,不推荐!!!


  
  
  1. #include "rclcpp/rclcpp.hpp"
  2. #include "std_msgs/msg/string.hpp"
  3. rclcpp::Node::SharedPtr g_node = nullptr;
  4. /* We do not recommend this style anymore, because composition of multiple
  5. * nodes in the same executable is not possible. Please see one of the subclass
  6. * examples for the "new" recommended styles. This example is only included
  7. * for completeness because it is similar to "classic" standalone ROS nodes. */
  8. void topic_callback(const std_msgs::msg::String::SharedPtr msg)
  9. {
  10. RCLCPP_INFO(g_node->get_logger(), "I heard: '%s'", msg->data.c_str());
  11. }
  12. int main(int argc, char * argv[])
  13. {
  14. rclcpp::init(argc, argv);
  15. g_node = rclcpp::Node::make_shared("minimal_subscriber");
  16. auto subscription =
  17. g_node->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);
  18. rclcpp::spin(g_node);
  19. rclcpp::shutdown();
  20. subscription = nullptr;
  21. g_node = nullptr;
  22. return 0;
  23. }

直接看官方注释吧:

我们不再推荐这种样式,因为在同一个可执行文件中不可能有多个节点组成。请参阅其中一个子类的例子,了解 "新 "推荐的样式。这个例子只是为了完整,因为它与 "经典 "独立的 ROS 节点类似。
TODO(clalancette)。

  subscription = nullptr;
  g_node = nullptr;

最好是把这两个nullptr分配都去掉,让destructors来处理,但我们不能,因为https://github.com/eProsima/Fast-RTPS/issues/235 。 一旦这个问题解决了,我们或许应该考虑删除这两个任务。

  • 第一种:lambda

  
  
  1. #include <iostream>
  2. #include <memory>
  3. #include "rclcpp/rclcpp.hpp"
  4. #include "std_msgs/msg/string.hpp"
  5. class MinimalSubscriber : public rclcpp::Node
  6. {
  7. public:
  8. MinimalSubscriber()
  9. : Node("minimal_subscriber")
  10. {
  11. subscription_ = this->create_subscription<std_msgs::msg::String>(
  12. "topic",
  13. 10,
  14. [this](std_msgs::msg::String::UniquePtr msg) {
  15. RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
  16. });
  17. }
  18. private:
  19. rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
  20. };
  21. int main(int argc, char * argv[])
  22. {
  23. rclcpp::init(argc, argv);
  24. rclcpp::spin(std::make_shared<MinimalSubscriber>());
  25. rclcpp::shutdown();
  26. return 0;
  27. }

 

  • 第二种:member_function

  
  
  1. #include <memory>
  2. #include "rclcpp/rclcpp.hpp"
  3. #include "std_msgs/msg/string.hpp"
  4. using std::placeholders::_1;
  5. class MinimalSubscriber : public rclcpp::Node
  6. {
  7. public:
  8. MinimalSubscriber()
  9. : Node("minimal_subscriber")
  10. {
  11. subscription_ = this->create_subscription<std_msgs::msg::String>(
  12. "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  13. }
  14. private:
  15. void topic_callback(const std_msgs::msg::String::SharedPtr msg)
  16. {
  17. RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
  18. }
  19. rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
  20. };
  21. int main(int argc, char * argv[])
  22. {
  23. rclcpp::init(argc, argv);
  24. rclcpp::spin(std::make_shared<MinimalSubscriber>());
  25. rclcpp::shutdown();
  26. return 0;
  27. }

至此,其实ROS2的发布订阅并未全部讲完,后续还会新开一节再深入讲解。

member_function这种编程方式是官方教程推荐学习的方式。详细代码解析请务必查阅官网和认真阅读源码。

附加题:

  • 使用新式编程风格实现turtlesim和mobot,速度发布和位置订阅的代码。

 

文章来源: zhangrelay.blog.csdn.net,作者:zhangrelay,版权归原作者所有,如需转载,请联系作者。

原文链接:zhangrelay.blog.csdn.net/article/details/106040763

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。