Ros从入门到入土
文章总结与古月居ROS入门21,对哪一章有问题可以去看原片。
古月·ROS入门21讲 | 一学就会的ROS机器人入门教程_哔哩哔哩_bilibili
1.Ros环境配置
(1) 安装VMware并配置虚拟机Ubuntu并安装虚拟环境C 和Python的编程环境
略…(帖子太多,自己去csdn)
(2) 在Ubuntu上安装Ros(本文使用的是Ubuntu18.04)
1)基本环境配置
如果没有选择,必须选择这四个。
2)换源
Ros有自己独立的源,需要添加到原来的源中,命令行输入
sudo sh -c '. /etc/lsb-release && echo "deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu/ $DISTRIB_CODENAME main" > /etc/apt/sources.list.d/ros-latest.list'
3 ) 输入密钥
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
4) 更新源
sudo apt-get update sudo apt-get upgrade
5 ) 安装ros
sudo apt-get update sudo apt-get install ros-melodic-desktop-full
6 ) 安装rq4t工具箱:
sudo apt-get install ros-melodic-rqt*
7 ) 初始化rosdepc
sudo apt-get install python-pip sudo pip install rosdepc sudo rosdepc init rosdepc update
8 ) 设置环境变量
echo "source /opt/ros/melodic/setup.bash" >>~/.bashrc source ~/.bashrc
9 ) 安装rosinstall
sudo apt-get install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
10) 验证ros安装成功与否
创建终端输入(启动ros服务)
roscore
别动这个终端,创建一个新的终端(启动海龟仿真器)【ps:创建新终端快捷键 ==> Ctrl Alt t】 新终端2:
rosrun turtlesim turtlesim_node
创建第三个终端,启动海龟控制节点 新终端3:
rosrun turtlesim turtle_teleop_key
新界面有小乌龟,由上下左右方向键控制。【ps:输入上下左右方向时,需要输入终端3的窗口,而不是小海龟的窗口。
2.Ros简介
ros是什么
ros = 通信机制 开发工具 应用功能 生态系统
目的是提高机器人研发中的软件复用率(复用他人开发的好东西,然后更改)
通信机制:
Ros提供了一个松耦合分布式通信框架
Ros所有框架都可以抽象成节点图,每个具体功能都是椭圆节点,通过箭头连接。箭头代表节点间数据的流向和数据。所有节点都与箭头形成一对。
开发工具
ros它提供了许多开发工具、命令工具、可视化工具、模拟工具
应用功能
ros应用功能很多,ros社区周围有很多社区ros对于协议标准开发的功能包,我们只需要关我们只需要关心它们界面的输入和输出,至于核心算法,我们只需要回去看看。最后,我们实现了像积木一样的功能包。
生态系统
1.发行版(Distribution) : ROS发行版包括一系列带有版本号、可直接安装的功能包。 2.软件源(Repository) : ROS不同的组织可以开发或共享自己的机器人软件,依靠共享网络上的开源代码。 3.ROS wiki:记录ROS主要论坛的信息文档。 4.邮件列表(Mailing list):交流ROS更新的主要渠道也可以交流ROS各种开发问题。 5.ROS Answers:咨询ROS网站相关问题。 6.博客(Blog):发布ROS社区新闻、图片、视频(http://www.ros.org/news)
3.Ros的核心概念
(1)节点-执行单元
节点是执行具体任务和独立运行的可执行文件的过程
不同的节点可以在不同的主机上使用不同的编程语言和分布式操作
系统中节点的名称必须是唯一的
(2) 节点管理器-控制中心
为节点提供命名和注册服务
跟踪和记录主题/服务通信,辅助节点相互搜索,建立连接
提供参数服务器(全局对象字典,记录全局变量名和变量值),节点使用该服务器存储和检索运行中的参数
Ros为节点间的通信设置了两种通信方式——主题服务
(3)话题(Topic)——异步通信集资
话题通讯机制是单向数据传输
主题是节点间传输数据的重要总线
使用发布/订阅模型,数据可以由发布者传输给订阅者,同一主题的发布者和订阅者不是唯一的
(4)消息-话题数据
具有一定类型和数据结构,包括ROS标准类型和用户自定义类型
使用编程语言无关的 在编译过程中生成相应的代码文件
主题是数据传输的渠道,新闻是描述我们传输信息时的信息数据类型。
(5)服务(Service)——同步通信机制
使用客户端/服务端(C/S)模型,客户端发送请求数据,服务器处理后返回响应数据
使用编程语言无关的 ” 在编译过程中生成相应的代码文件,定义请求和响应数据结构。
主题通信与服务通信的区别
话题 | 服务 | |
---|---|---|
同步性 | 异步 | 同步 |
通信模型 | 发布/订阅模型 | 服务器/客户端模型 |
底层协议 | ROTCP/ROSUDP | ROSTCP/ROSUDP |
反馈机制 | 无 | 有 |
缓冲区 | 有 | 无 |
实时性 | 弱 | 强 |
节点关系 | 多对多(尽量一个发布者) | 一对多(一个Server,类似于一个服务器多个终端) |
适用场景 | 数据传输 | 逻辑处理 |
(6)参数——全局共享字典
可通过访问的共享、多变量 节点使用此服务器来存储和检索 适合存储静态、非二进制的,不适合存储动态配置的数据。
(7)功能包(Package)
ROS软件中的基本单元,包含节点源码、配置文件、数据定义等
(8)功能包清单(Package manifest)
记录功能包的基本信息,包含作者信息、许可信息、依赖选项、编译标志等
(9)元功能包(Meta Packages)
组织多个用于同一目的功能包
4.Ros命令行工具的使用
常用命令
rostopic
rosservice
rosnode
rosparam
rosmsg
rossrv
先命令行输入roscore启动ros-master,也就是节点管理器
使用rosrun运行某个功能包的某个节点,比如调用turtlesim的turtlesim_node节点(ps:rosrun 包名 之后输入两个Tap。可以查看这个包所有的节点)
rosrun turtlesim turtlesim_node
(1) rqt_graph工具
ros中有很多rqt开头的工具,都是有界面的命令行工具,rqt_graph会显示当前系统计算图的工具,ros通信机制的核心就是有箭头和节点组成的计算图。
命令行输入rqt_graph即可查看系统计算图,帮助我们快速了解系统,回车之后会输出计算图
(2) rosnode list
列出所有系统的节点
(3) rosnode info /节点名称
查看当前节点的全部信息
(4) rostopic list
打印当前系统的所有话题列表
(5) rostopic pub
发布数据给某一个topic,输入rostopic pub 话题名 后,按两下Tap可以显示当前数据的数据结构,再按两下Tap可以显示当前数据的数据内容,我们只需要改变数据参数即可。
回车之后海龟动了,但是就动了一下
所以我们需要在pub后面加参数, -r 10表示已10赫兹每秒动,一直动,撞墙也继续动
(6) rosmsg show 消息名
显示某个消息的数据结构
(7)rosservice list
查看当前仿真器里面所有的服务内容
(8)rosservice call + 服务名
前面显示服务里面的spawn就是产生一直海龟,如果我们需要再产生一直海龟该怎么操作呢。
可以 rosservice call /spawn然后两下Tap,会出现这个服务初始化信息
(9)话题记录工具
可以记录当前系统内部的所有话题数据并且保存下来
:
rosbug record -a -0 cmd_record
// cmd_record为保存文件名
我们输入这个指令当显示这几行就说明开始记录了,然后我们去终端用上下左右操作海龟运行一会儿。
运行完毕直接回到这个终端,Ctrl + C结束,然后就会把刚才的运行话题记录成一个.bag文件存放带默认文件夹,某认文件夹为主目录
:
我们启动roscore和海龟节点,不启动海龟运动节点。
然后新建终端运行如下命令
rosbag play cmd_record.bag
我并不需要移动海龟,海龟就会自己移动。
5.创建工作空间与功能包
工作空间(workspace)是一个存放工程开发相关文件的文件夹
src:代码空间(Source Space)【放置功能包的代码,功能包的配置文件等】
build:编译空间(Build Space)【放置编译过程中所产生的的中间文件,这个文件夹可以不用管】
devel:开发空间(Development Space)【放置编译生成的可执行文件,库以及一些脚本等】
install:安装空间(Install Space)【用install安装的东西放在这个空间,也可以不用管】
(1)创建工作空间
创建工作空间
mkdir -p ~/addam_first/src // addam_first是文件夹名字,随便怎么取,创建文件夹以及文件夹的子文件夹src
cd ~/addam_first/src // 进入这个src文件夹
catkin_init_wordspace // 初始化生成一个.txt文件,把这个文件夹初始化变成一个工作空间
编译工作空间
cd ~/admin_first/ //进入项目根目录
catkin_make // 产生build和devel
catkin_make install //产生install安装空间
设置环境变量
source devel/setup.bash
检查环境变量
echo $ROS_PACKAGE_PATH
(2) 创建功能包
功能包是放置Ros源码的最小单元,所以所有源码必须放在功能包里面。
我们需要先创建一个功能包
功能包的放置一定要放置到src中。test_pkg是我们的包名,后面std_msgs rospy roscpp使我们这个功能包需要依赖的一些库,std_msgs是一些基本的数据类型,rospy是ros的Python依赖,roscpp是c++依赖。【PS:,不同的工作空间下允许存在相同的功能包名】
cd ~/admin_first/src
catkin_create_pkg test_pkg std_msgs rospy roscpp
编译功能包
cd ~/admin_first
catkin_make
source ~/admin_first/devel/setuo.bash
功能包结构如下:
package.xml描述了这个功能包的一些基本信息,比如名字,版本号等。
下面还有这个功能包的依赖信息
CMakeList.txt文件设置了功能包代码的编译规则
6:发布者Publisher的编程实现
实现程序发布指令使得海龟动起来
C++创建Publisher过程如下
第一步:创建功能包
cd ~/addam/src/
catkin_create_pkg learning_topic rospy roscpp std_msgs geometry_msgs turtlesim
第二步:C++实现一个发布者(四步)
初始化ROS节点
向ROS Master 注册节点信息,包括发布的话题名和话题中的消息类型
创建消息数据
按照一定频率循环发布消息。
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
int main(int argc, char **argv)
{
// ROS节点初始化 [velocity_publisher为节点名字]
ros::init(argc, argv, "velocity_publisher");
// 创建节点句柄 [管理结点的资源]
ros::NodeHandle n;
// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10『表示如果发布者发布特别块,先把数据存到对列里去,如果队列存满了,就根据时间戳抛弃最久的』
ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
// 设置循环的频率
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
// 初始化geometry_msgs::Twist类型的消息
geometry_msgs::Twist vel_msg;
vel_msg.linear.x = 0.5;
vel_msg.angular.z = 0.2;
// 发布消息
turtle_vel_pub.publish(vel_msg);
ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]",
vel_msg.linear.x, vel_msg.angular.z);
// 按照循环频率延时
loop_rate.sleep();
}
return 0;
}
第三步:配置CMakeList.txt中的编译规则
设置需要编译的代码和生成的可执行文件
设置链接库
# 描述把src/velocity_publisher.cpp和可执行文件velocity_publisher建立连接
add_executable(velocity_publisher src/velocity_publisher.cpp)
# 把可执行文件和ROS的库去做连接
target_link_libraries(velocity_publisher ${catkin_LIBRARIES})
第四步: 编译并运行发布者
cd ~/addam_first
catkin_make
source devel/setup.bash
roscore
Ctrl+Alt+T
rosrun tue=rtlesim turtlesim_node
Ctrl+Alt+T
source devel/setup.bash
rosrun learning_topic velocity_publisher
每一次运行都需要使用“source devel/setup.bash”重新编译,非常容易忘而且非常麻烦,我们在系统根目录下找到“ .bashrc ”,点开之后在最后加上这样一条命令
source /home/系统名字/项目名/devel/setup.bash
// 比如我的就是source /home/addam/addam_first/devel/setup.bash
之后就直接运行就好啦。
Python创建Publisher过程如下
第一步:为了区分python和c++代码,我们在原功能包上创建一个文件夹,随便叫什么,就叫scripts。
第二步:python实现一个发布者
和C++一样都是四步,python实现一个发布者源码如下
# 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist
import rospy
from geometry_msgs.msg import Twist
def velocity_publisher():
# ROS节点初始化
rospy.init_node('velocity_publisher', anonymous=True)
# 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)
#设置循环的频率
rate = rospy.Rate(10)
while not rospy.is_shutdown():
# 初始化geometry_msgs::Twist类型的消息
vel_msg = Twist()
vel_msg.linear.x = 0.5
vel_msg.angular.z = 0.2
# 发布消息
turtle_vel_pub.publish(vel_msg)
rospy.loginfo("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]",
vel_msg.linear.x, vel_msg.angular.z)
# 按照循环频率延时
rate.sleep()
if __name__ == '__main__':
try:
velocity_publisher()
except rospy.ROSInterruptException:
pass
还有就是这个权限一定要放开,右击属性,打钩允许作为程序执行文件
7:订阅者Subscriber的编程实现
实现通过海龟仿真器去发布数据,实现一个订阅者去订阅海龟的位置信息(Post信息)。由ROS Master管理节点,Publisher就是海龟仿真器,Subscriber就是需要实现的程序。两者之间传输的Message消息就是海龟的post信息,话题内容分就是/turtle1/pose,数据传输是从海龟仿真器传输到需要实现的程序。
7.1: C++实现一个订阅者
(1)初始化ROS节点
(2)订阅需要的话题
(3)循环等待话题消息,接收到消息后进入回调函数
(4)在回调函数中完成消息处理【ps:回调函数如果执行时间非常长就会一直卡在这里,回调函数是不会嵌套的,所以我们需要保证回调函数处理是高效,时间不能太长】
C++实现一个发布者(四步)
代码如下
/**
* 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
*/
#include <ros/ros.h>
#include "turtlesim/Pose.h"
// 接收到订阅的消息后,会进入消息回调函数
void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Turtle pose: x:%0.6f, y:%0.6f", msg->x, msg->y);
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "pose_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/turtle1/pose的topic,设置长度为10的缓冲区队列(和之前的缓冲区一样,如果缓冲区内元素超过10,就会抛弃时间戳最老的数据,保证队列里的数据是最新的数据),注册回调函数poseCallback(订阅者不知道发布者什么时候会发布消息,所以注册回调函数就是当订阅者订阅到消息,就会立刻调用回调函数)
ros::Subscriber pose_sub = n.subscribe("/turtle1/pose", 10, poseCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
配置CMakeList.txt中的编译规则
和之前发布者一样的
# 描述把src/pose_subscriber.cpp和可执行文件pose_subscriber建立连接
add_executable(pose_subscriber src/pose_subscriber.cpp)
# 把可执行文件和ROS的库去做连接
target_link_libraries(pose_subscriber ${catkin_LIBRARIES})
编译并运行发布者
编译是一样的
cd ~/addam_first
catkin_make
运行试一下,先运行roscore,在运行海龟仿真器=
roscore
Ctrl+Alt+T
rosrun turtlesim turtlesim_node
Ctrl+Alt+T
rosrun learning_topic post_subscriber
由于海龟没动,所以现在一直是x:5,y:5。我们运行海龟控制器改变一下海龟位置
rosrun turtlesim turtle_teleop_key
7.2: python实现订阅者
流程和C++是一样的,就是实现方式略有偏差
代码如下
# 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
import rospy
from turtlesim.msg import Pose
def poseCallback(msg):
rospy.loginfo("Turtle pose: x:%0.6f, y:%0.6f", msg.x, msg.y)
def pose_subscriber():
# ROS节点初始化
rospy.init_node('pose_subscriber', anonymous=True)
# 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback
rospy.Subscriber("/turtle1/pose", Pose, poseCallback)
# 循环等待回调函数
rospy.spin()
if __name__ == '__main__':
pose_subscriber()
python代码无需设置编译规则,改一下权限之后可以直接运行,效果和C++的订阅者是一样的。
8: 话题消息的定义与使用
之前我们都是使用ROS给我们定义好的Twist(发布)和Post(订阅)方法。但是有的时候开发过程中ROS定义好的消息无法满足我们的需求,这个时候我们就需要自己去定义消息类型。
我们可以自己定义消息类别,然后传输一个Person的个人信息。
– 通过语言无关的文件定义一个Topic(话题),话题名叫“ person_info ”
– Publisher(发布者)会通过这个话题发布这个人的信息
– Subscriber(订阅者)会通过这个话题订阅这个人的信息。
8.1:自定义话题消息
(1) 定义一个msg文件
关于一个人有如下定义:
string name //这个人的名字
uint8 sex //这个人的性别
uint8 age //这个人的年纪
// 然后我们对这个人的性别进行一些宏定义,做一些限制
uint8 unknown = 0 //为知性别为0
uint8 male = 1 //男性为1
uint8 female = 2 //女性为2
所有的person定义我们需要放到一个“ .msg ”的文件中去,这个文件是一个跟语言无关的文件。string uint8都是表示我们需要在对应程序里扩展成该种程序的表示方法。
所以我们具体实施如下:
1) 在组件目录下创建一个文件夹名为“msg”,把跟消息相关的定义都放在该文件夹里面。
2)在msg文件夹下右键,新建终端,打开该文件夹下的终端。然后输入以下代码创建一个“ .msg ”文件
touch Person.msg
3)双击打开这个Person.msg文件,输入以下内容然后保存(每一句的解释见上面)
string name
uint8 sex
uint8 age
uint8 unknown = 0
uint8 male = 1
uint8 female = 2
(2) 添加功能包依赖及编译选项
在package.xml中添加功能包依赖
<!-- 编译依赖 -->
<build_depend>message_generation</build_depend>
<!-- 执行依赖 -->
<exec_depend>message_runtime</exec_depend>
然后在CMakeList.txt添加编译选项
find_package(
......
message_generation
)
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(
......
message_runtime
)
现在我们就完成了msg文件的生成,可以回到工作空间的根目录下试一下编译catkin_make,只要底下的输出没出现红色,出现什么颜色都是好颜色。编译完成之后就会生成一个C++的头文件。
8.2:C++实现发布者和订阅者创建
发布者的创建
/**
* 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "person_publisher");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10
ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);
// 设置循环的频率
ros::Rate loop_rate(1);
int count = 0;
while (ros::ok())
{
// 初始化learning_topic::Person类型的消息
learning_topic::Person person_msg;
person_msg.name = "Addam";
person_msg.age = 21;
person_msg.sex = learning_topic::Person::male;
// 发布消息
person_info_pub.publish(person_msg);
ROS_INFO("Publish Person Info: name:%s age:%d sex:%d",
person_msg.name.c_str(), person_msg.age, person_msg.sex);
// 按照循环频率延时
loop_rate.sleep();
}
return 0;
}
还是一样的流程,先初始化,创建节点句柄,创建一个Publisher,设置循环频率
初始化一个learning_topic::Person类型的消息,消息主要就是名字,年龄,和调用刚才宏定义生成的性别。
然后发布消息并添加延时,这个发布者会不停发布这个个人信息。
订阅者的创建
/**
* 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Subcribe Person Info: name:%s age:%d sex:%d",
msg->name.c_str(), msg->age, msg->sex);
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "person_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
先初始化节点,创建节点句柄,创建一个Subscriber,订阅名为/person_info的主题,并且注册回调函数
回到函数就会把这个人的信息打印出来。
订阅者发布者都需要先引入这个头文件,而且这个发布和订阅的话题也是我们自己定义的
#include "learning_topic/Person.h"
接下来编译我们的发布者订阅者
配置CMakeList.txt中的编译规则
1)设置需要编译的代码和生成的可执行文件
2)设置链接库
3)添加依赖项
add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)
add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)
编译是一样的
cd ~/addam_first
catkin_make
运行试一下
roscore
Ctrl+Alt+T
rosrun learning_topic person_subscriber
订阅者不会接收到数据,因为发布者还没发布数据,订阅者只能等待。
Ctrl+Alt+T
rosrun learning_topic person_publisher
开始有数据了。
8.3:Python实现发布者和订阅者创建
发布者
# 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
import rospy
from learning_topic.msg import Person
def velocity_publisher():
# ROS节点初始化
rospy.init_node('person_publisher', anonymous=True)
# 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10
person_info_pub = rospy.Publisher('/person_info', Person, queue_size=10)
#设置循环的频率
rate = rospy.Rate(10)
while not rospy.is_shutdown():
# 初始化learning_topic::Person类型的消息
person_msg = Person()
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = Person.male;
# 发布消息
person_info_pub.publish(person_msg)
rospy.loginfo("Publsh person message[%s, %d, %d]",
person_msg.name, person_msg.age, person_msg.sex)
# 按照循环频率延时
rate.sleep()
if __name__ == '__main__':
try:
velocity_publisher()
except rospy.ROSInterruptException:
pass
订阅者
# 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
import rospy
from learning_topic.msg import Person
def personInfoCallback(msg):
rospy.loginfo("Subcribe Person Info: name:%s age:%d sex:%d",
msg.name, msg.age, msg.sex)
def person_subscriber():
# ROS节点初始化
rospy.init_node('person_subscriber', anonymous=True)
# 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
rospy.Subscriber("/person_info", Person, personInfoCallback)
# 循环等待回调函数
rospy.spin()
if __name__ == '__main__':
person_subscriber()
python代码最舒服的就是不需要编译
运行起来效果是一样的
9: 客户端Clien的编程实现
之前讲的都是话题通信的发布/订阅模型,但是ROS还有另一个通信方式就是Service服务的通信方式
Service服务由客户端(Clien)和服务端(server)组成
实现通过程序发布请求,使得客户端能够让海龟仿真器产生一直新的海龟。
Client客户端就是我们即将要实现的程序,会发布一个产生海龟的Request请求并发给Server端,Server端就是海龟仿真器会产生一个海龟并且回馈一个Response给Client客户端,这样客户端知道海龟有没有生成成功。Service的消息结构叫做turtlesim::Spawn,这个是turtlesim这个功能包自定义的一个消息结构。
创建一个功能包
本功能包对标learning_topic,取名为learning_service
cd ~/addam_first/src
catkin_create_pkg learning_service std_msgs rospy roscpp geometry_msgs turtlesim
C++创建客户端
C++创建客户端代码如下
/**
* 该例程将请求/spawn服务,服务数据类型turtlesim::Spawn
*/
#include <ros/ros.h>
#include <turtlesim/Spawn.h>
int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "turtle_spawn");
// 创建节点句柄
ros::NodeHandle node;
// 查询系统中是否有spawn这个服务,如果没有就一直等着,只有这个服务在系统当中存在了,才能去请求这个服务
ros::service::waitForService("/spawn");
// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的服务service,请求的数据类型是turtlesim::Spawn
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");
// 初始化turtlesim::Spawn的请求数据
turtlesim::Spawn srv;
srv.request.x = 2.0;
srv.request.y = 2.0;
srv.request.name = "turtle2";
// 请求服务调用
ROS_INFO("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]",
srv.request.x, srv.request.y, srv.request.name.c_str());
// 通过call方法把封装的数据发出去,并且这个cal函数也是一个阻塞性的函数,发出之后会一直等待服务器返回的response
add_turtle.call(srv);
// 显示服务调用结果(输出产生海龟的名字)
ROS_INFO("Spwan turtle successfully [name:%s]", srv.response.name.c_str());
return 0;
};
所以在Ros中创建客户端一共有四步
(1)初始化ROS节点
(2)创建一个Client实例
(3)发布服务请求数据
(4)等待Server处理之后的应答结果
配置CMakeList.txt中的编译规则
和前面是完全一样的
add_executable(turtle_spawn src/turtle_spawn.cpp)
target_link_libraries(turtle_spawn ${catkin_LIBRARIES})
开始编译
回到项目根目录
catkin_make
最后运行程序
新建终端
roscore
Ctrl+Alt+T
rosrun turtlesim turtlesim_node
Ctrl+Alt+T
rosrun learning_service turtle_spawn
运行之后就会在(2,2)的位置上生成一只名叫turtle2的新的小海龟
Python创建客户端
Python创建客户端代码如下
# 该例程将请求/spawn服务,服务数据类型turtlesim::Spawn
import sys
import rospy
from turtlesim.srv import Spawn
def turtle_spawn():
# ROS节点初始化
rospy.init_node('turtle_spawn')
# 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
rospy.wait_for_service('/spawn')
try:
add_turtle = rospy.ServiceProxy('/spawn', Spawn)
# 请求服务调用,输入请求数据
response = add_turtle(2.0, 2.0, 0.0, "turtle2")
return response.name
except rospy.ServiceException, e:
print "Service call failed: %s"%e
if __name__ == "__main__":
#服务调用并显示调用结果
print "Spwan turtle successfully [name:%s]" %(turtle_spawn())
代码和C++代码逻辑是一样的,就是有一些方法的函数名不太相同
最后运行程序
新建终端
roscore
Ctrl+Alt+T
rosrun turtlesim turtlesim_node
Ctrl+Alt+T
rosrun learning_service turtle_spawn.py
运行之后效果和C++运行之后的效果是一样的
10: 服务端Server的编程实现
任务介绍
本章主要实现通过Client端的程序发送Request,控制Server端的海龟运动还是停止。当Client发一个Request后,海龟随即开始运动,当再发送一个Requests后,海龟就停止运动。Server端就接收Client端发送过来的指令,并且完成海龟是否运动的指令的发送,同时我们需要返回一个Response告诉Client端你的指令是否成功发送了,所以Client就可以实时知道海龟当前的运动状态。
这一次的Server实现时机会包含Service的Server这一端的实现,也会包含发布者(Publisher)的实现。
C++服务端Server实现
创建一个Server服务端的步骤
(1)初始化ROS节点
(2)创建Server实例
(3)循环等待服务请求,进入回调函数
(4)在回调函数中完成服务功能的处理,并反馈应答数据
代码如下
/**
* 该例程将执行/turtle_command服务,服务数据类型std_srvs/Trigger
*/
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <std_srvs/Trigger.h>
// 由于我们之后会在回调函数中使用这个publisher,所以我们把他创建成全局变量
ros::Publisher turtle_vel_pub;
// 这个额作为标志位标志我们的海龟是运动还是停止(false-停止,true-运动)
bool pubCommand = false;
// service回调函数,输入参数req,输出参数res
bool commandCallback(std_srvs::Trigger::Request &req,
std_srvs::Trigger::Response &res)
{
// 对标志位进行取反
pubCommand = !pubCommand;
// 显示请求数据==把日志打印来
ROS_INFO("Publish turtle velocity command [%s]", pubCommand==true?"Yes":"No");
// 设置反馈数据
res.success = true; // 执行是否成功
res.message = "Change turtle command state!";
return true;
}
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "turtle_command_server");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个名为/turtle_command的server,注册回调函数commandCallback
ros::ServiceServer command_service = n.advertiseService("/turtle_command", commandCallback);
// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10;为了给海龟发送输入指令,让海龟能够动起来
turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
// 循环等待回调函数
ROS_INFO("Ready to receive turtle command.");
// 设置循环的频率
ros::Rate loop_rate(10);
while(ros::ok())
{
// 查看一次回调函数队列
ros::spinOnce();
// 如果标志为true,则发布速度指令
if(pubCommand)
{
geometry_msgs::Twist vel_msg;
vel_msg.linear.x = 0.5;
vel_msg.angular.z = 0.2;
turtle_vel_pub.publish(vel_msg);
}
//按照循环频率延时
loop_rate.sleep();
}
return 0;
}
接下来开始重复之前的工作环节
配置CMakeList.txt中的编译规则
和前面是完全一样的
add_executable(turtle_command_server src/turtle_command_server.cpp)
target_link_libraries(turtle_command_server ${catkin_LIBRARIES})
开始编译
回到项目根目录
catkin_make
最后运行程序
新建终端
roscore
Ctrl+Alt+T
rosrun turtlesim turtlesim_node
Ctrl+Alt+T
rosrun learning_service turtle_command_server
Ctrl+Alt+T(下面这个运行一次就会跑起来,再运行一次就会停下来,再发送就就运动,类似于开关)
rosservice call /turtle_command "{}"
Python服务端Server实现
代码如下
# 该例程将执行/turtle_command服务,服务数据类型std_srvs/Trigger
import rospy
import thread,time
from geometry_msgs.msg import Twist
from std_srvs.srv import Trigger, TriggerResponse
# 全局变量(标志位和一个发布指令的publisher)
pubCommand = False;
turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)
# 在python中不同于C++,c++中有ros::spinOnce()函数可以查看一次回调队列,看有没有数据,如果有就进入回调函数,如果没有就跳出函数继续where循环的程序,但是在Python中并没有实现spinOnce,只有spin可以不断循环,所以我们需要通过多线程的机制来实现spinOnce加if的标志位判断。
#所以在这里创建一个线程,如果标志位是true就发宋海龟运动指令,否则就跳过
def command_thread():
while True:
if pubCommand:
vel_msg = Twist()
vel_msg.linear.x = 0.5
vel_msg.angular.z = 0.2
turtle_vel_pub.publish(vel_msg)
time.sleep(0.1)
def commandCallback(req):
# Python中的取反操作
global pubCommand
pubCommand = bool(1-pubCommand)
# 显示请求数据
rospy.loginfo("Publish turtle velocity command![%d]", pubCommand)
# 反馈数据
return TriggerResponse(1, "Change turtle command state!")
def turtle_command_server():
# ROS节点初始化
rospy.init_node('turtle_command_server')
# 创建一个名为/turtle_command的server,注册回调函数commandCallback
s = rospy.Service('/turtle_command', Trigger, commandCallback)
# 循环等待回调函数
print "Ready to receive turtle command."
# 创建一个线程
thread.start_new_thread(command_thread, ())
rospy.spin()
if __name__ == "__main__":
turtle_command_server()
运行起来效果是一样的
11: 服务数据的定义与使用
很多场景下ROS定义好的服务数据很难满足我们的需求,所以如何自己定义一个ROS的服务数据类型就很重要了
之前定义了一个Person消息类别,然后传输一个Person的个人信息。但是无论订阅者是否接收到了数据发布者都一直在发送,非常不人性化。所以我们可以利用Service信息传递的机制,让数据接收者也就是客户端只有接收到数据,并且返回成功状态给服务端,服务端才会继续发送数据。
– 通过语言无关的文件定义一个Service,服务名叫“ /show_person”,服务数据类型叫“ learning_service::Person ”
11.1:自定义服务数据的步骤
(1)定义srv文件
这里的数据类型和之前订阅发布者模型类似,就不过多注释了,区别就是Service模式是有反馈的,所以我们用“ — ”来区别发送信息和接收反馈。三个横线以上就是发送的信息,三个横线以下就是接受到的反馈。
一样的都是新建一个srv文件夹,然后新建文件输入一下代码。
Person.srv
string name
uint8 s