素材巴巴 > 程序开发 >

ROS学习2:ROS通信机制

程序开发 2023-09-03 21:27:20

【Autolabor初级教程】ROS机器人入门
机器人操作系统 ROS 快速入门教程

1. 引言

2. 话题通信

  • 作用:用于不断更新的、少逻辑处理的数据传输场景
  • 2.1 话题通信理论模型

    1. Talker注册
    2. Listener注册
    3. ROS Master实现信息匹配
    4. Listener向Talker发送请求
    5. Talker确认请求
    6. Listener与Talker建立连接
    7. Talker向Listener发送消息

    2.2 话题通信基本操作

  • 分析
  • 流程
    1. 发布方实现
      首先新建工作空间 demo03_ws ,然后创建功能包 plumbing_pub_sub 并添加依赖
  • // demo01_pub.cpp
     // 1.包含头文件 
     #include "ros/ros.h"
     #include "std_msgs/String.h" // 普通文本类型的消息
     #include  // 字符串拼接int main(int argc, char *argv[]) {// 设置编码,防止中文乱码setlocale(LC_ALL,"");// 2.初始化 ROS 节点:命名(唯一)// 参数1和参数2 后期为节点传值会使用// 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一ros::init(argc, argv, "erGouZi");// 3.实例化 ROS 句柄ros::NodeHandle nh; //该类封装了 ROS 中的一些常用功能// 4.实例化 发布者 对象// 泛型: 发布的消息类型// 参数1: 要发布到的话题// 参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)ros::Publisher pub = nh.advertise("fang",10);// 5.组织被发布的数据,并编写逻辑发布数据// 数据(动态组织)std_msgs::String msg;int count = 0; //消息计数器// 逻辑(发布频率:一秒10次)ros::Rate rate(10);ros::Duration(3).sleep(); // 延迟第一条数据的发送// 节点不死while (ros::ok()) {// 使用 stringstream 拼接字符串与编号std::stringstream ss;ss << "hello ---> " << count;msg.data = ss.str();// 发布消息pub.publish(msg);// 加入调试,打印发送的消息ROS_INFO("发送的消息:%s", msg.data.c_str());// 根据前面制定的发送频率自动休眠 休眠时间 = 1/频率;rate.sleep();count++; //循环结束前,让 count 自增ros::spinOnce();}return 0;
     }
     
    // demo02_sub.cpp
     // 1.包含头文件 
     #include "ros/ros.h"
     #include "std_msgs/String.h"void doMsg(const std_msgs::String::ConstPtr& msg){ROS_INFO("翠花订阅的数据:%s",msg->data.c_str());
     }int main(int argc, char *argv[]) {setlocale(LC_ALL,"");// 2.初始化 ROS 节点:命名(唯一)ros::init(argc, argv, "cuiHua");// 3.实例化 ROS 句柄ros::NodeHandle nh;// 4.实例化 订阅者 对象ros::Subscriber sub = nh.subscribe("fang", 10, doMsg); // doMsg 回调函数// 5.处理订阅的消息(回调函数)// 6.设置循环调用回调函数ros::spin(); // 循环读取接收的数据,并调用回调函数处理return 0;
     }
     
    // 下面这行默认的模板不能删除!
     // add_executable(${PROJECT_NAME}_node src/plumbing_pub_sub_node.cpp)
     add_executable(demo01_pub src/demo01_pub.cpp)
     add_executable(demo02_sub src/demo02_sub.cpp)target_link_libraries(demo01_pub${catkin_LIBRARIES}
     )
     target_link_libraries(demo02_sub${catkin_LIBRARIES}
     )
     
    $ roscore
     $ rosrun plumbing_pub_sub demo01_pub
     $ rosrun plumbing_pub_sub demo02_sub
     

    订阅时,第一条数据丢失

    回调函数:自己写的,但调用时机不受自己控制,而是由外部传入的消息控制调用时机的函数

    $ rosrun rqt_graph rqt_graph
     

    在这里插入图片描述

    使用C++编写的发布方实现可以和使用Python编写的订阅方实现进行节点通信

    2.3 话题通信自定义 msg

    ROS 中还有一种特殊类型:Header,标头包含时间戳和 ROS 中常用的坐标帧信息。会经常看到 msg 文件的第一行具有 Header 标头

    // Person.msg 内容
     string name
     uint16 age
     float64 height
     
  • 编辑配置文件
    // package.xml中添加编译依赖与执行依赖
     message_generation
     message_runtime
     
    // CMakeLists.txt 编辑 msg 相关配置
     // 需要加入 message_generation,必须有 std_msgs
     find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgsmessage_generation
     )// 配置 msg 源文件
     add_message_files(FILESPerson.msg
     )// 生成消息时依赖于 std_msgs
     generate_messages(DEPENDENCIESstd_msgs
     )// 执行时依赖
     catkin_package(
     #  INCLUDE_DIRS include
     #  LIBRARIES demo02_talker_listenerCATKIN_DEPENDS roscpp rospy std_msgs message_runtime
     #  DEPENDS system_lib
     )
     
  • 编译生成可以被 C++ 调用的中间文件
  • 2.4 话题通信自定义 msg 调用 C++ 实现

    add_executable(demo03_pub_person src/demo03_pub_person.cpp)
     add_executable(demo04_sub_person src/demo04_sub_person.cpp)add_dependencies(demo03_pub_person ${PROJECT_NAME}_generate_messages_cpp)
     add_dependencies(demo04_sub_person ${PROJECT_NAME}_generate_messages_cpp)target_link_libraries(demo03_pub_person${catkin_LIBRARIES}
     )
     target_link_libraries(demo04_sub_person${catkin_LIBRARIES}
     )
     

    2.5 机器人运动控制开源工程

    在这里插入图片描述

    $ mkdir -p catkin_ws/src
     $ cd catkin_ws
     $ catkin_make$ cd src
     $ git clone https://github.com/6-robot/wpr_simulation.git
     $ cd wpr_simulation/scripts/
     $ ./install_for_melodic.sh // 自动下载和安装编译需要的依赖项$ cd ~/catkin_ws
     $ catkin_make// 运行 demo 案例
     $ source ./devel/setup.bash
     $ roslaunch wpr_simulation wpb_simple.launch
     $ source ./devel/setup.bash // 重开窗口
     $ rosrun wpr_simulation demo_vel_ctrl
     
    $ catkin_create_pkg vel_pkg roscpp rospy geometry_msgs
     
    // 在 vel_pkg 功能包的 src 下新建 vel_node.cpp 文件
     
    #include 
     #include int main(int argc, char *argv[]) {ros::init(argc, argv, "vel_node");ros::NodeHandle n;ros::Publisher vel_pub = n.advertise("/cmd_vel", 10);geometry_msgs::Twist vel_msg;vel_msg.linear.x = 0.1;vel_msg.linear.y = 0.0;vel_msg.linear.z = 0.0;vel_msg.angular.x = 0;vel_msg.angular.y = 0;vel_msg.angular.z = 0;ros::Rate r(30);while (ros::ok()) {vel_pub.publish(vel_msg);r.sleep();}return 0;
     }
     // 修改 CMakeLists.txt 文件此处略
     // 到 catkin_ws 目录下编译
     
    $ echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
     $ roslaunch wpr_simulation wpb_simple.launch
     $ rosrun vel_pkg vel_node
     

    2.5.1 使用 Rviz 观测传感器数据

    在这里插入图片描述

    2.5.2 激光雷达消息包格式

    $ rostopic echo /scan --noarr
     

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    2.5.3 获取激光雷达数据的 C++ 节点

    $ cd ~/catkin_ws/
     $ roslaunch wpr_simulation wpb_simple.launch
     $ rosrun wpr_simulation demo_lidar_data
     
    $ catkin_create_pkg lidar_pkg roscpp rospy sensor_msgs
     
    // 在 lidar_pkg 功能包的 src 下新建 lidar_node.cpp 文件
     
    #include 
     #include void LidarCallback(const sensor_msgs::LaserScan msg) {float fMidDist = msg.ranges[180]; // 见下图,机器人正前方起始点ROS_INFO("qianfangceju ranges[180] = %f m", fMidDist);
     }int main(int argc, char *argv[]) {setlocale(LC_ALL, "");ros::init(argc, argv, "lidar_node");ros::NodeHandle n;ros::Subscriber lidar_sub = n.subscribe("/scan", 10, &LidarCallback);ros::spin();return 0;
     }
     
    在这里插入图片描述

    2.5.4 激光雷达避障 C++ 节点

    在这里插入图片描述

    #include 
     #include 
     #include ros::Publisher vel_pub; // 全局变量,保证回调函数也能使用
     int nCount = 0;void LidarCallback(const sensor_msgs::LaserScan msg) {float fMidDist = msg.ranges[180];ROS_INFO("qianfangceju ranges[180] = %f m", fMidDist);if(nCount > 0) {nCount--;return;}geometry_msgs::Twist vel_cmd; // 构建速度控制消息包 vel_cmdif(fMidDist < 1.5) {vel_cmd.angular.z = 0.3;nCount = 40; // 确保机器人旋转的角度足够避开障碍物} else {vel_cmd.linear.x = 0.1;}vel_pub.publish(vel_cmd);
     }int main(int argc, char *argv[]) {setlocale(LC_ALL, "");ros::init(argc, argv, "lidar_node");ros::NodeHandle n;ros::Subscriber lidar_sub = n.subscribe("/scan", 10, &LidarCallback);vel_pub = n.advertise("/cmd_vel", 10); // 发布速度控制话题 /cmd_velros::spin();return 0;
     }
     

    2.5.5 IMU 消息包

    在这里插入图片描述

    2.5.6 获取 IMU 数据的 C++ 节点

    #include 
     #include 
     #include void IMUCallback(sensor_msgs::Imu msg) {// 先对四元数 orientation 的协方差矩阵第一个数值进行判断,如果小于0// 说明四元数的值不存在,具体查看sensor_msgs/Imu Message说明if(msg.orientation_covariance[0] < 0) {return;}// 使用 tf 工具将四元数转换成 tf 四元数对象tf::Quaternion quaternion ( msg.orientation.x,msg.orientation.y,msg.orientation.z,msg.orientation.w);double roll, pitch, yaw; // 用来装载转换后的欧拉角结果// 先将 quaternion 转换成一个 tf 的 3×3 矩阵对象,然后调用 getRPY 转换成欧拉角tf::Matrix3x3(quaternion).getRPY(roll, pitch, yaw);// 将弧度值转换为角度值roll = roll*180/M_PI;pitch = pitch*180/M_PI;yaw = yaw*180/M_PI;ROS_INFO("roll = %.0f pitch = %.0f yaw = %.0f", roll, pitch, yaw);
     }int main(int argc, char *argv[]) {setlocale(LC_ALL, "");ros::init(argc, argv, "imu_node");ros::NodeHandle n;ros::Subscriber imu_sub = n.subscribe("/imu/data", 10, IMUCallback);ros::spin();return 0;
     }
     // 修改 CMakeLists.txt 文件此处略
     // 到 catkin_ws 目录下编译 
     

    2.5.7 IMU 航向锁定的 C++ 节点

    在这里插入图片描述

    #include "ros/ros.h"
     #include "sensor_msgs/Imu.h"
     #include "tf/tf.h"
     #include "geometry_msgs/Twist.h"// 速度消息发布对象(全局变量)
     ros::Publisher vel_pub;// IMU 回调函数
     void IMUCallback(const sensor_msgs::Imu msg) {// 检测消息包中四元数数据是否存在if(msg.orientation_covariance[0] < 0)return;// 四元数转成欧拉角tf::Quaternion quaternion(msg.orientation.x,msg.orientation.y,msg.orientation.z,msg.orientation.w);double roll, pitch, yaw;tf::Matrix3x3(quaternion).getRPY(roll, pitch, yaw);// 弧度换算成角度roll = roll*180/M_PI;pitch = pitch*180/M_PI;yaw = yaw*180/M_PI;ROS_INFO("滚转 = %.0f 俯仰 = %.0f 朝向 = %.0f", roll, pitch, yaw);// 速度消息包geometry_msgs::Twist vel_cmd;// 目标朝向角double target_yaw = 90;// 计算速度double diff_angle = target_yaw - yaw;vel_cmd.angular.z = diff_angle * 0.01;vel_cmd.linear.x = 0.1;vel_pub.publish(vel_cmd);
     }int main(int argc, char **argv) {setlocale(LC_ALL, "");ros::init(argc, argv, "imu_node"); ros::NodeHandle n;// 订阅 IMU 的数据话题ros::Subscriber sub = n.subscribe("imu/data", 100, IMUCallback);// 发布速度控制话题vel_pub = n.advertise("/cmd_vel",10);ros::spin();return 0;
     }
     

    2.5.8 ROS 标准消息包 std_msgs

    2.5.9 ROS 几何包 geometry_msgs 和 传感器包 sensor_msgs

    2.5.10 ROS 中的栅格地图格式

  • ROS 官网 nav_msgs/OccupancyGrid Message
    在这里插入图片描述

  • nav_msgs/MapMetaData
    在这里插入图片描述

  • 2.5.11 C++ 节点发布地图

    在这里插入图片描述

    $ cd ~/catkin_ws/src
     $ catkin_create_pkg map_pkg roscpp rospy nav_msgs
     $ cd ..
     $ code .
     
    // 在 map_pkg 功能包的 src 下新建 map_pub_node.cpp 文件
     
    #include 
     #include int main(int argc, char *argv[]) {ros::init(argc, argv, "map_pub_node");ros::NodeHandle n;ros::Publisher pub = n.advertise("/map", 10);ros::Rate r(1);while(ros::ok()) {nav_msgs::OccupancyGrid msg;msg.header.frame_id = "map";msg.header.stamp = ros::Time::now();msg.info.origin.position.x = 0;msg.info.origin.position.y = 0;msg.info.resolution = 1.0;msg.info.width = 4;msg.info.height = 2;msg.data.resize(4 * 2);msg.data[0] = 100;msg.data[1] = 100;msg.data[2] = 0;msg.data[3] = -1;pub.publish(msg);r.sleep();}return 0;
     }
     // 修改 CMakeLists.txt 文件此处略
     // 到 catkin_ws 目录下编译
     
    $ roscore
     $ rosrun map_pkg map_pub_node
     $ rviz
     
    在这里插入图片描述

    3. 服务通信

  • 概念:以请求响应的方式实现不同节点之间数据交互的通信模式
  • 作用:用于偶然的、对实时性有要求、有一定逻辑处理需求的数据传输场景
  • 3.1 服务通信理论模型

  • 1.Client注册

  • 2.ROS Master实现信息匹配

  • 3.Client发送请求

  • 4.Server发送响应

  • 3.2 服务通信自定义srv

  • 流程

    1. 按照固定格式创建 srv 文件

    2. 编辑配置文件

    3. 编译

  • 3.3 服务通信自定义 srv 调用 C++ 实现

    4. 参数服务器

  • 上述场景中,全局路径规划和本地路径规划时,就会使用到参数服务器
  • 概念:以共享的方式实现不同节点之间数据交互的通信模式
    作用:存储一些多节点共享的数据,类似于全局变量
    案例:实现参数增、删、改、查操作

    4.1 参数服务器理论模型

    1. Listener 获取参数
    1. ROS Master 向 Listener 发送参数值
  • 参数可使用数据类型:32-bit integers、booleans、strings、doubles、iso8601 dates、lists、base64-encoded binary data、字典
    注意:参数服务器不是为高性能而设计的,因此最好用于存储静态的非二进制的简单数据

    4.2 参数操作

    #include int main(int argc, char *argv[]) {ros::init(argc, argv, "set_param_c");ros::NodeHandle nh;// 参数新增// 方案1:ros::NodeHandle::setParam("键",值)nh.setParam("type", "xiaoHuang");nh.setParam("radius", 0.15);// 方案2:ros::param::setParam("键",值)ros::param::set("type_param", "xiaoBai");ros::param::set("radius_param", 0.15);// 参数修改(覆盖上面的值)nh.setParam("radius", 0.2);ros::param::set("radius_param", 0.25);return 0;
     }
     
    #include "ros/ros.h"
     /*
     ros::NodeHandle1. param(键,默认值) 存在,返回对应结果,否则返回默认值2. getParam(键,存储结果的变量)存在,返回 true,且将值赋值给参数2若果键不存在,那么返回值为 false,且不为参数2赋值3. getParamCached键,存储结果的变量)--提高变量获取效率存在,返回 true,且将值赋值给参数2若果键不存在,那么返回值为 false,且不为参数2赋值4. getParamNames(std::vector)获取所有的键,并存储在参数 vector 中 5. hasParam(键)是否包含某个键,存在返回 true,否则返回 false6. searchParam(参数1,参数2)搜索键,参数1是被搜索的键,参数2存储搜索结果的变量
     */
     int main(int argc, char *argv[]) {setlocale(LC_ALL, "");ros::init(argc, argv, "get_param_c");ros::NodeHandle nh;// 1. paramdouble radius = nh.param("radius", 0.5);ROS_INFO("radius = %.2f", radius);// 2. getParamdouble radius2 = 0.0;// bool result = nh.getParam("radius", radius2);// 3. getParamCachedbool result = nh.getParamCached("radius", radius2);if (result) {ROS_INFO("get radius: %.2f", radius2);} else {ROS_INFO("bucunzai");}// 4. getParamNamesstd::vector names;nh.getParamNames(names);for (auto &&name : names) {ROS_INFO("bianlideyuansu: %s", name.c_str());}// 5. hasParambool flag1 = nh.hasParam("radius");bool flag2 = nh.hasParam("radiusxxx");ROS_INFO("radius cunzaima? %d", flag1);ROS_INFO("radiusxxx cunzaima? %d", flag2);// 6. searchParamstd::string key;nh.searchParam("radius", key);ROS_INFO("sousuojieguo: %s", key.c_str());// ros::param -----------------------double radius_param = ros::param::param("radius", 100.5);ROS_INFO("radius_param = %.2f", radius_param);std::vector names_param;ros::param::getParamNames(names_param);for (auto &&name : names_param) {ROS_INFO("key: %s", name.c_str());}return 0;
     }
     
    #include "ros/ros.h"
     /* 参数服务器操作之删除_C++实现:1. ros::NodeHandledeleteParam("键")根据键删除参数,删除成功,返回 true,否则(参数不存在),返回 false2. ros::paramdel("键")根据键删除参数,删除成功,返回 true,否则(参数不存在),返回 false
     */
     int main(int argc, char *argv[]) {setlocale(LC_ALL,"");ros::init(argc,argv,"param_del_c");ros::NodeHandle nh;// 1. ros::NodeHandlebool flag1 = nh.deleteParam("radius");if (flag1) {ROS_INFO("delete!");} else {ROS_INFO("No delete!");}// 2. ros::parambool flag2 = ros::param::del("radius_param");if (flag2) {ROS_INFO("delete!");} else {ROS_INFO("No delete!");}return 0;
     }
     

    5. 通信机制常用命令

  • 作用:和之前介绍的文件系统操作命令比较,文件操作命令是静态的,操作的是磁盘上的文件,而上述命令是动态的,在ROS程序启动后,可以动态的获取运行中的节点或参数的相关信息;此外,这些命令还可在测试的时候使用

  • 5.1 rosnode

    rosnode ping    测试到节点的连接状态
     rosnode list    列出活动节点
     rosnode info    打印节点信息
     rosnode machine    列出指定设备上节点
     rosnode kill    杀死某个节点
     rosnode cleanup    清除不可连接的节点
     // 清除无用节点,启动乌龟节点,然后 ctrl + c 关闭,该节点并没被彻底清除,可以使用 cleanup 清除节点
     

    5.2 rostopic

    rostopic bw     列出消息发布带宽
     rostopic delay  显示带有 header 的主题延迟
     rostopic echo   获取指定话题当前发布的消息,并打印消息到屏幕
     rostopic find   根据消息类型查找话题
     rostopic hz     列出消息发布频率
     rostopic info   显示主题相关信息(消息类型、发布者信息和订阅者信息)
     rostopic list   显示所有活动状态下的主题,控制台将打印当前运行状态下的主题名称
     rostopic type   列出话题的消息类型
     rostopic pub    将数据发布到主题
     /* rostopic pub /主题名称 消息类型 消息内容
     rostopic pub /turtle1/cmd_vel geometry_msgs/Twist"linear:x: 1.0y: 0.0z: 0.0
     angular:x: 0.0y: 0.0z: 2.0"
     // 只发布一次运动信息rostopic pub -r 10 /turtle1/cmd_vel geometry_msgs/Twist"linear:x: 1.0y: 0.0z: 0.0
     angular:x: 0.0y: 0.0z: 2.0"
     // 以 10HZ 的频率循环发送运动信息
     */
     

    5.3 rosmsg

    rosmsg show    显示消息描述
     /*
     //rosmsg show 消息名称
     rosmsg show turtlesim/Pose结果:
     float32 x
     float32 y
     float32 theta
     float32 linear_velocity
     float32 angular_velocity
     */
     rosmsg info    显示消息信息 // 作用与 rosmsg show 一样
     rosmsg list    列出所有消息
     rosmsg md5    显示 md5 加密后的消息
     rosmsg package    显示某个功能包下的所有消息
     rosmsg packages    列出包含消息的功能包
     

    5.4 rosservice

    调用部分服务时,如果对相关工作空间没有配置 path,需要进入工作空间调用 source ./devel/setup.bash

    rosservice args    打印服务参数
     rosservice call    使用提供的参数调用服务
     rosservice find    按照服务类型查找服务
     rosservice info    打印有关服务的信息
     rosservice list    列出所有活动的服务
     rosservice type    打印服务类型
     rosservice uri     打印服务的 ROSRPC uri
     

    5.5 rossrv

    rossrv show    显示服务消息详情
     rossrv info    显示服务消息相关信息
     rossrv list    列出所有服务信息
     rossrv md5    显示 md5 加密后的服务消息
     rossrv package    显示某个包下所有服务消息
     rossrv packages    显示包含服务消息的所有包
     

    5.6 rosparam

    rosparam set    设置参数
     rosparam get    获取参数
     rosparam load    从外部文件加载参数(先准备 yaml 文件)
     rosparam dump    将参数写出到外部文件
     rosparam delete    删除参数
     rosparam list    列出所有参数
     

    6. 通信机制比较

    可以概括为: 两个节点通过话题关联到一起,并使用某种类型的数据载体实现数据传输

    在这里插入图片描述


    标签:

    上一篇: 同一局域网下访问angular项目 下一篇:
    素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。