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

2023-05-16

在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

#include <iostream>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"

using namespace std::chrono_literals;

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("publish_velocity");
  auto publisher = node->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel", 10);
  geometry_msgs::msg::Twist message;
  rclcpp::WallRate loop_rate(500ms);

  while (rclcpp::ok()) {
    message.linear.x = ((double)rand()/(RAND_MAX)); 
    message.angular.z = 4.0*((double)rand()/(RAND_MAX))-2; 
    RCLCPP_INFO(node->get_logger(), "Publishing /turtle1/cmd_vel : linear='%f',angular='%f'", message.linear.x, message.angular.z);
    publisher->publish(message);
    rclcpp::spin_some(node);
    loop_rate.sleep();
  }
  rclcpp::shutdown();
  return 0;
}

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

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

subpose.cpp

#include "rclcpp/rclcpp.hpp"
#include "turtlesim/msg/pose.hpp"

rclcpp::Node::SharedPtr g_node = nullptr;

void topic_callback(const turtlesim::msg::Pose::SharedPtr msg)
{
  RCLCPP_INFO(g_node->get_logger(), "I heard: turtle1/pose position='%f','%f'; direction='%f'", msg->x, msg->y, msg->theta);
}

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  g_node = rclcpp::Node::make_shared("subscribe_to_pose");
  auto subscription =
    g_node->create_subscription<turtlesim::msg::Pose>("turtle1/pose", 10, topic_callback);
  rclcpp::spin(g_node);
  rclcpp::shutdown();

  subscription = nullptr;
  g_node = nullptr;
  return 0;
}

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

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

add_executable(pub_vel src/pub_vel.cpp)
ament_target_dependencies(pub_vel rclcpp geometry_msgs)

add_executable(sub_pose src/sub_pose.cpp)
ament_target_dependencies(sub_pose rclcpp nav_msgs)

install(TARGETS 
  pub_vel
  sub_pose
  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方式实现代码如下,(不推荐,不推荐,不推荐!!!):

#include <iostream>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("minimal_publisher");
  auto publisher = node->create_publisher<std_msgs::msg::String>("topic", 10);
  std_msgs::msg::String message;
  auto publish_count = 0;
  rclcpp::WallRate loop_rate(500ms);

  while (rclcpp::ok()) {
    message.data = "Hello, world! " + std::to_string(publish_count++);
    RCLCPP_INFO(node->get_logger(), "Publishing: '%s'", message.data.c_str());
    publisher->publish(message);
    rclcpp::spin_some(node);
    loop_rate.sleep();
  }
  rclcpp::shutdown();
  return 0;
}

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

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

新推荐样式有两种:

  • 第一种:lambda
#include <chrono>
#include <memory>

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

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    auto timer_callback =
      [this]() -> void {
        auto message = std_msgs::msg::String();
        message.data = "Hello, ros2 world! " + std::to_string(this->count_++);
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        this->publisher_->publish(message);
      };
    timer_ = this->create_wall_timer(500ms, timer_callback);
  }

private:
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  size_t count_;
};

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

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

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

  • 第二种:member_function
#include <chrono>
#include <memory>

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

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
  }

private:
  void timer_callback()
  {
    auto message = std_msgs::msg::String();
    message.data = "Hello, world! " + std::to_string(count_++);
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    publisher_->publish(message);
  }
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  size_t count_;
};

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

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

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

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

订阅器:

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

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

rclcpp::Node::SharedPtr g_node = nullptr;

/* We do not recommend this style anymore, because composition of multiple
 * nodes in the same executable is not possible. Please see one of the subclass
 * examples for the "new" recommended styles. This example is only included
 * for completeness because it is similar to "classic" standalone ROS nodes. */

void topic_callback(const std_msgs::msg::String::SharedPtr msg)
{
  RCLCPP_INFO(g_node->get_logger(), "I heard: '%s'", msg->data.c_str());
}

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  g_node = rclcpp::Node::make_shared("minimal_subscriber");
  auto subscription =
    g_node->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);
  rclcpp::spin(g_node);
  rclcpp::shutdown();

  subscription = nullptr;
  g_node = nullptr;
  return 0;
}

直接看官方注释吧:

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

  subscription = nullptr;
  g_node = nullptr;

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

  • 第一种:lambda
#include <iostream>
#include <memory>

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

class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic",
      10,
      [this](std_msgs::msg::String::UniquePtr msg) {
        RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
      });
  }

private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

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

 

  • 第二种:member_function
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  }

private:
  void topic_callback(const std_msgs::msg::String::SharedPtr msg)
  {
    RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
  }
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

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

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

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

附加题:

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

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 在 GitHub 下载某个程序的特定版本代码

    情况 github中某个项目已经更新到2 1 0版本 但是想要它的1 0 1版本怎么办 方法一 xff1a 首先点击这个repository下的这个branch按钮 点开了以后你会看到这个 xff0c 然后点tags 选择你想要下载的版本
  • Pixhawk之姿态控制

    原文地址 xff1a http blog csdn net qq 21842557 1 写在前面 无人机控制部分主要分为两个部分 xff0c 姿态控制部分和位置控制部分 xff1b 位置控制可用远程遥控控制 xff0c 而姿态控制一般由无人
  • Android注册表文件

    data system packages plist com google android ears 10043 0 data data com google android ears default 3003 1028 1015 com
  • Java 爬虫系列丨(一)爬虫介绍

    1 简介 1 1 背景 随着互联网的迅速发展 xff0c 网络资源越来越丰富 xff0c 信息需求者如何从网络中抽取信息变得至关重要 目前 xff0c 有效的获取网络数据资源的重要方式 xff0c 便是网络爬虫技术 简单的理解 xff0c
  • 基于龙伯格观测器的永磁同步电机仿真与实现

    摘 要 xff1a 在永磁同步电动机控制系统中 xff0c 使用转子位置传感器不仅会增加设计和制造的成本 xff0c 还会使系统的可靠性降低 因此 xff0c 无位置传感器技术已成为永磁同步电机控制领域的研究热点之一 本文对龙伯格观测器技术
  • 拷贝cp大文件报错“文件太大”

    问题 xff1a 今天在centos7系统下 xff0c u盘位vfat格式16个G xff0c 拷贝7个G大小的问文件 xff0c 无论是用dd还是cp都在拷贝到4 3G大小的时候显示失败 故写下这篇博客 无论什么系统 xff0c 只要分
  • CMakeList.txt

    一 Cmake 简介 cmake 是一个跨平台 开源的构建系统 它是一个集软件构建 测试 打包于一身的软件 它使用与平台和编译器独立的配置文件来对软件编译过程进行控制 二 常用命令 1 指定 cmake 的最小版本 cmake minimu
  • 安装centos7 卡在 “正在安装引导装载程序”界面

    今天系统突然起不来 xff0c 不知道什么原因删掉了一些文件 修复太浪费时间 xff0c 还是重新装一个系统 xff08 原来的分区有很多个人资料 xff0c 所以一定不能格调 xff0c 在无用的分区上装新的系统 所以你装系统的时候尽量不
  • insmod: ERROR: could not insert module: Invalid module format

    root 64 zn pc home zn sedriver 5000 new sedriver 5000 span class token comment insmod wst se echip drv ko span insmod ER
  • LoongArch上正常使用`pip install`

    原创 xff1a 你在使用loongarch架构操作系统时 xff0c 是否遇到pip install 安装失败的情况 xff1f 刷到这篇文章 xff0c 大家可添加评论或者私信我 xff0c 及时满足大家的需求 那么 xff0c 下面讲
  • python SOABI兼容性问题

    首先说明一点 xff1a 龙芯发布的仓库都是基于configure ac 中包含loongarch64 linux gnu定义的python所构建 https blog csdn net zhangna20151015 article de
  • python中为什么加上中文注释就会报错

    由于Python源代码也是一个文本文件 xff0c 所以 xff0c 当你的源代码中包含中文的时候 xff0c 在保存源代码时 xff0c 就需要务必指定保存为UTF 8编码 当Python解释器读取源代码时 xff0c 为了让它按UTF
  • 关于在linux操作系统下打不出汉字或者在敲打汉字时无法显示拼音的问题

    在linux下出现问题不比在window下形象 在window下 你发现哪个软件有问题了 xff0c 点击几下鼠标就完事了 xff1b 要是在linux系统下 xff0c 不懂代码 xff0c 可修复不了 打不出汉字 xff0c 在这我就说
  • 解析/etc/hosts文件

    1 xff0c etc hosts xff0c 主机名和ip配置文件 hosts The static table lookup for host name 主机名查询静态表 linux 的 etc hosts是配置ip地址和其对应主机名的
  • c++语法大全

    c 43 43 语法大全 一 变量和简单数据类型 1 变量名只能包含字母 数字和下划线 可以以字母和下划线开头 xff0c 但是不能从数字开头 xff1b 变量名不能包含空格 2 数据类型 字符串 字符串可以用双引号或者单引号括起来 xff
  • libxml2的安装及使用

    本文着重介绍解析xml的libxml2库的安装及使用 xff0c 举例说明创建和解析xml的过程 是针对C语言开发人员使用 你若想详细学习前端的一套东西 xff0c 即xml html css javascript JS 等 xff0c 可
  • dd 与cp的区别

    dd命令和cp命令的区别 cp与dd的区别在于cp可能是以字节方式读取文件 xff0c 而dd是以扇区方式记取 显然dd方式效率要高些 dd最大的用处是他可以进行格式转换和格式化 dd是对块进行操作的 xff0c cp是对文件操作的 比如有
  • 畸变校正与极线校正(具体原理+Matlab代码)

    附 xff1a 相关需要的工具函数源代码 xff08 投影函数 校正矩阵计算等 xff09 见最下面 1 畸变校正 1 1 形成原因 图像畸变一般有两种 xff0c 第一种是透镜本身的形状有问题 xff0c 使得图像发生径向畸变 xff1b
  • 无人驾驶项目——交通标志识别

    在无人驾驶项目中 xff0c 实现交通标志识别是一项重要工作 本文以德国交通标志数据集为训练对象 xff0c 采用深度神经网络LeNet架构处理图像 xff0c 实现交通标志识别 具体处理过程包括包括 xff1a 数据导入 探索和可视化数据
  • 使用机器人操作系统ROS 2和仿真软件Gazebo 9主题进阶实战(七)- mobot速度发布与里程计订阅

    在ROS2课程中已经学过并掌握了一个基本的发布器和订阅器 xff08 C 43 43 xff09 xff0c 官网的教程全部掌握大致需要20分钟吧 这过程包括 xff1a 创建一个功能包编程实现一个发布节点编程实现一个订阅节点编译与运行 这