ROS学习2:ROS通信机制
【Autolabor初级教程】ROS机器人入门
机器人操作系统 ROS 快速入门教程
1. 引言
2. 话题通信
2.1 话题通信理论模型
- Talker注册
- Listener注册
- ROS Master实现信息匹配
- Listener向Talker发送请求
- Talker确认请求
- Listener与Talker建立连接
- Talker向Listener发送消息
2.2 话题通信基本操作
- 发布方实现
首先新建工作空间 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
)
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
流程
-
按照固定格式创建 srv 文件
-
编辑配置文件
-
编译
3.3 服务通信自定义 srv 调用 C++ 实现
4. 参数服务器
概念:以共享的方式实现不同节点之间数据交互的通信模式
作用:存储一些多节点共享的数据,类似于全局变量
案例:实现参数增、删、改、查操作
4.1 参数服务器理论模型
- Listener 获取参数
- 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. 通信机制比较
可以概括为: 两个节点通过话题关联到一起,并使用某种类型的数据载体实现数据传输
标签:
相关文章
-
无相关信息