简介
机器人是各种硬件与软件的集成【上层软件设计会采集机器人感知到的外部信息,再进行处理、运算,再将执行命令下达(下达给嵌入式软件设计)。】(不重复发明轮子原则)
ROS是一套机器人通用软件框架,可以提升功能模块的复用性。
概念
1、 ROS是使用与机器人的开源元操作系统;
2、ROS集成了大量的工具,库,协议,提供类似OS所提供的功能,简化了对机器人的操作;
3、ROS提供了用于在多台计算机上获取、构建、编写和运行代码的工具和库,ROS在某些方面类似于“机器人框架”;(分布式架构)
4、ROS是通讯机制、工具软件包、机器人高层技能以及机器人生态系统的集合体。(ROS=通信+工具+功能+生态)【通信:ROS核心,ROS中常用到数据交换;工具:平台仿真功能可创建机器人,脱离实体;功能:拿别人的功能包来用,主要就是根据自己想要的场景调参。】
设计目标
gethab有众多功能包,遇见问题可以直接去上面找封装好的功能包。
常见问题
话题通信
发布者(C++)
代码:
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
/*
发布方实现:
1、包含头文件;
ROS中文本类型 --->std_msgs/String.h
2、初始化ROS节点;
3、创建节点句柄;
4、创建发布者对象;
5、编写发布逻辑并发布数据。
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");//解决中文乱码问题
//2、初始化ROS节点;
ros::init(argc,argv,"zhangsan");
//3、创建节点句柄;
ros::NodeHandle nh;
//4、创建发布者对象;
ros::Publisher pub = nh.advertise<std_msgs::String>("Imfor",10);
//5、编写发布逻辑并发布数据。
//先创建被发布的消息
std_msgs::String msg;
//发布频率
ros::Rate rate(50);
//设置编号
int count = 0;
//循环中发布数据
ros::Duration(3).sleep();
while(ros::ok())
{
count++;
//实现字符串拼接数字
std::stringstream ss;
ss << "hello ---> " << count;
//msg.data = "hello";
msg.data = ss.str();
pub.publish(msg);
//添加日志:
ROS_INFO("张三发布的数据是:%s",ss.str().c_str());
rate.sleep();
ros::spinOnce();//官方建议回调函数
}
return 0;
}
基本实现步骤:
1、包含头文件;
ROS中文本类型 --->std_msgs/String.h
2、初始化ROS节点;
3、创建节点句柄;
4、创建发布者对象;
5、编写发布逻辑并发布数据。
遇见问题及解决
节点名必须在这个拓扑链中唯一标识
解决发布消息的乱码问题:在主函数开头使用函数:setlocale(LC_ALL,"");
如果使用到字符串类型需要添加相应头文件:#include "std_msgs/String.h"
发布频率函数ros::Rate rate(x); x是每秒钟发布的次数
在订阅消息时可能接收不到前几条发送的消息,因为发布者在roscore中还未注册完成,所以需要给一定的时间取完成发布者在roscore中的注册。(先打开订阅,再打开发布)
建议写上回调函数:ros::spinOnce(); 虽然不写也不会报错
订阅者(C++)
代码:
#include "ros/ros.h"
#include "std_msgs/String.h"
/*
发布方实现:
1、包含头文件;
ROS中文本类型 --->std_msgs/String.h
2、初始化ROS节点;
3、创建节点句柄;
4、创建订阅者对象;
5、处理订阅到的数据;
6、spin()函数。
*/
void doMsg(const std_msgs::String::ConstPtr &msg){
//通过msg获取并操作订阅到的数据
ROS_INFO("李四订阅的消息:%s",msg->data.c_str());
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2、初始化ROS节点;
ros::init(argc,argv,"lisi");
//3、创建节点句柄;
ros::NodeHandle nh;
//4、创建订阅者对象;
ros::Subscriber sub = nh.subscribe("Imfor",10,doMsg);
//5、处理订阅到的数据;
ros::spin();//无回调函数可能订阅不到消息
return 0;
}
基本实现步骤:
1、包含头文件;
ROS中文本类型 --->std_msgs/String.h
2、初始化ROS节点;
3、创建节点句柄;
4、创建订阅者对象;
5、处理订阅到的数据;
6、spin()函数。
遇见的问题
节点名必须在这个拓扑链中唯一标识
接收乱码也使用:setlocale(LC_ALL,"");
创建的订阅对象的范型不用写(会根据发布者的自动推导),话题(话题不一致则订阅不到)与队列长度必须与发布者一致,需要有一个回调函数去处理订阅到的数据。
文件
两个程序运行前需先打开roscore,再在每一个程序运行前刷新环境变量:source ./devel/setup.bash
记得每加一个程序文件需要到CMakeLists.txt里面修改或添加相应的add_executable与target_link_libraries
注意事项
检查 CMakeList.txt find_package 出现重复,删除内容少的即可
未创建依赖包虽然可以正常编译,但可能出现中断现象,最好删掉重建
回调函数
每订阅到一条消息就会调用一次回调函数(可以看作中断)
发布者(python)
代码:
#! /usr/bin/env python
import rospy
from std_msgs.msg import String #发布的消息类型
"""
使用python 实现消息发布:
1、导包;
2、初始化ROS节点;
3、创建发布者对象;
4、编写发布逻辑并发布数据。
"""
if __name__ == "__main__":
#2、初始化ROS节点;
rospy.init_node("wangwu")
#3、创建发布者对象;
pub = rospy.Publisher("hh",String,queue_size=10)
#4、编写发布逻辑并发布数据。
#创建数据
msg = String()
#指定发布频率
rate = rospy.Rate(1)
#设置计数
count = 0
#建立延时,完成发布方在roscore中的注册
rate.sleep()
#编写循环发布数据
while not rospy.is_shutdown():
count += 1
msg.data = "hello" + str(count)
#发布数据
pub.publish(msg)
rospy.loginfo("发布的数据是:%s",msg.data)
rate.sleep()
pass
基本实现步骤:
1、导包;
2、初始化ROS节点;
3、创建发布者对象;
4、编写发布逻辑并发布数据。
常见问题及解决
1、注意缩进,python语法的规范性很强,缩进严格
2、发布者对象的话题名称不能和其它的话题名相同
3、仍需解决发布者在roscore中建立注册所用的时间,该点可能导致前几个发布的消息不能被订阅到
4、使用频率函数rate = rospy.Rate(1);通过控制睡眠时间rate.sleep()来完成定时发布
订阅者(python)
代码:
#! /usr/bin/env python
from std_msgs.msg import String
import rospy
"""
订阅实现流程:
1、导包
2、初始化ROS节点
3、创建订阅者对象
4、回调函数处理数据
5、spin()
"""
def doMsg(msg):
rospy.loginfo("我订阅的数据是:%s",msg.data)
if __name__ == "__main__":
#2、初始化ROS节点
rospy.init_node("qianduoduo")
#3、创建订阅者对象
sub = rospy.Subscriber("hh",String,doMsg,queue_size=10)
#4、回调函数处理数据
#5、spin()
rospy.spin()
pass
基本实现步骤:
1、导包
2、初始化ROS节点
3、创建订阅者对象
4、回调函数处理数据
5、spin()
常见问题及解决:
1、需要回调函数才能打印订阅到的消息
2、结束时需要写rospy.spin()
3、导包是要写正确、完整from std_msgs.msg import String
4、订阅到的消息可能没有发布方的前几条,在发布者地代码里添加与ROSMaster注册的时间
注意事项
1、给python文件添加可执行权限(chmod +x *.py)
2、在CMakeLists.txt文件中配置相应python文件
3、运行节点文件前需先启动roscore,再刷新环境变量(source ./devel/setup.bash),最后执行节点
4、订阅节点消息语句(rostopic echo + 话题名)
5、使用rqt_graph可以查看启动了的节点的拓扑关系图/网,里面有相应的节点名称与发布话题
节耦合
在ROS中,使用不同的语言编写的节点,他们之间也能实现数据的交换,需要保证其有相同的话题。
自定义msg
基本流程(C++实现):
创建msg消息:
1、在功能包下新建msg目录,添加Person.msg文件
2、再编辑2个配置文件:
(1)、在package.xml中添加依赖<build_depend>message_generation</build_depend>(编译时依赖)与<exec_depend>message_runtime</exec_depend>(运行时依赖)
(2)、在CMakeLists.txt文件中找到find_package添加依赖包message_generation;找到add_message_files并取消注释加入自己写的.msg文件的文件名;找到generate_messages语句,打开注释,不做更改;找到catkin_package语句,打开里面的CATKIN_DEPENDS roscpp rospy std_msgs注释并加上message_runtime。(catkin_package里面的功能包依赖于find_package里面的功能包)
(3)、可以简单地认为find_package是编译时依赖,catkin_package是运行时依赖
3、编译,重点在于其生成的中间文件:
C++调用的在/devel/include/plumbing_pub_sub/Person.h里面
python调用的在/devel/lib/python3/dist-packages/plumbing_pub_sub/msg/_Person.py里面
调用自定义的msg消息:
1、先复制Person.h的包名路径,终端输入pwd
2、将复制的路径.vscode下的c_cpp_properties.json文件中,includePath下:
3、创建.cpp文件的发布方
发布方:发布人的消息
(1)包含头文件
#include "plumbing_pub_sub/Person.h"
(2)初始化ROS节点
(3)创建节点句柄
(4)创建发布者对象
(5)编写发布逻辑、发布数据
代码:
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
/*
发布方:发布人的消息
1、包含头文件
#include "plumbing_pub_sub/Person.h"
2、初始化ROS节点
3、创建节点句柄
4、创建发布者对象
5、编写发布逻辑、发布数据
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ROS_INFO("这是消息发布方");
//2、初始化ROS节点
ros::init(argc,argv,"banzhuren");
//3、创建节点句柄
ros::NodeHandle nh;
//4、创建发布者对象
ros::Publisher pub = nh.advertise<plumbing_pub_sub::Person>("talk",10);
//5、编写发布逻辑、发布数据
//5-1、创建被发布的数据
plumbing_pub_sub::Person person;
person.name = "张三";
person.age = 1;
person.height = 179;
//5-2设置发布频率
ros::Rate rate(1);
//5-3循环发布数据
while(ros::ok()){
//
//核心:发布数据
pub.publish(person);
ROS_INFO("发布的消息是:%s,%d,%.2f",person.name.c_str(),person.age,person.height);
//休眠
rate.sleep();
//建议:调用回头函数
ros::spinOnce();
}
return 0;
}
4、配置CMakeLists.txt文件
(1)在其中找到add_executable,大概在136行左右;然后添加一句add_executable(pub_person src/pub_person.cpp);新创建的cpp文件名
(2)再找到target_link_libraries,150行左右;然后添加(新创建的cpp文件名)
target_link_libraries(pub_person
${catkin_LIBRARIES}
)
(3)再找到add_dependencies;添加(保证依赖关系)(保证了先编译msg文件再编译cpp文件,严谨逻辑性)
add_dependencies(pub_person ${PROJECT_NAME}_generate_messages_cpp)
(4)发布方实现结果测验:
在终端中运行ROS核心;再打开一个终端运行功能包下的文件,运行前要刷新环境变量;再打开另一个终端,进入相应工作空间、刷新环境变量,再使用rostopic echo订阅话题(talk)
最终结果显示如下:
5、创建.cpp文件的订阅方
订阅方:订阅发布方发布的人的消息
(1)包含头文件
#include "plumbing_pub_sub/Person.h"
(2)初始化ROS节点
(3)创建节点句柄
(4)创建订阅者对象
(5)编写回调函数,处理订阅的数据
(6)调用spin()函数
代码:
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
/*
订阅方:发布人的消息
1、包含头文件
#include "plumbing_pub_sub/Person.h"
2、初始化ROS节点
3、创建节点句柄
4、创建订阅者对象
5、编写回调函数,处理订阅的数据
6、调用spin()函数
*/
void doPerson(const plumbing_pub_sub::Person::ConstPtr& person){
ROS_INFO("订阅的人的信息:%s,%d,%.2f",person->name.c_str(),person->age,person->height);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ROS_INFO("订阅方实现");
//2、初始化ROS节点
ros::init(argc,argv,"jiazhang");
//3、创建节点句柄
ros::NodeHandle nh;
//4、创建订阅者对象
ros::Subscriber sub = nh.subscribe("talk",10,doPerson);
//5、编写回调函数,处理订阅的数据
//6、调用spin()函数
ros::spin();
return 0;
}
4、配置CMakeLists.txt文件
(1)在其中找到add_executable,大概在136行左右;然后添加一句add_executable(sub_person src/sub_person.cpp);新创建的cpp文件名
(2)再找到target_link_libraries,150行左右;然后添加(新创建的cpp文件名)
target_link_libraries(sub_person
${catkin_LIBRARIES}
)
(3)再找到add_dependencies;添加(保证依赖关系)(保证了先编译msg文件再编译cpp文件,严谨逻辑性)
add_dependencies(sub_person ${PROJECT_NAME}_generate_messages_cpp)
(4)订阅方实现结果测验:
在终端中运行ROS核心;再打开一个终端进入相应工作空间,刷新环境变量,运行发布方文件;再打开另一个终端,进入相应工作空间、刷新环境变量,再使用rostopic echo订阅话题(talk)
最终结果显示如下:
使用计算图查看节点间的消息传递及话题:rqt_graph
基本流程(Python实现):
准备操作,在编写python时有自动补齐功能: