资讯详情

Ros1入门到入土

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

标签: 传感器线色白色fv1连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台