ROS机器人学习——ROS基础
ROS机器人学习——ROS基础
ROS系统结构
- 节点(Node) : 软件模块
- 节点管理器(ROS Master) : 控制中心,提供参数管理
- 话题(Topic) : 异步通信机制,传输消息Message,可有多个发布者和订阅者
- 服务(Service) : 同步通信机制,传输请求/应答数据,只允许有一个节点提供指定命名的服务
ROS工作空间
工作空间(workspace)是存放工程相关文件的文件夹,包括以下四个目录空间
src
:代码空间,存放源码build
:编译空间,存储编译的缓存和中间文件devel
:开发空间,存放编译生成的可执行文件install
:安装空间,非必须
创建工作空间
(以下使用WORKSPACE
代表你的工作空间名)
创建工作空间
1
2
3mkdir -p ~/WORKSPACE/src
cd ~/WORKSPACE/src
catkin_init_workspace编译工作空间
1
2cd ~/WORKSPACE/
catkin_make设置环境变量
编译完成后,会自动产生
build
和devel
,devel
文件夹中会产生setup.*sh
样子的环境变量设置脚本,用source
运行脚本,以生效工作空间中的环境变量.1
source devel/setup.bash
但是这么设置环境变量只能在当前终端下生效,如希望在所有终端都生效,则需要在终端的配置文件中添加环境变量的设置:
1
2
echo "source /WORKSPACE/devel/setup.bash">>~/.bashrc
source ~/.bashrc
检查环境变量
1
echo $ROS_PACKAGE_PATH
如果打印的路径中包含当前工作空间的路径,说明环境变量设置成功.
TODO:添加成功的截图
创建功能包
(以下使用PACKAGE
代表创建的功能包)
功能包结构:
PACKAGE/
CmakeList.txt -> 纪录功能报的编译规则
package.xml -> 描述功能包属性的信息
……
功能包不能嵌套,多个功能包需平行放置于代码空间src
中
创建功能包
1
2
3
4# catkin_create_pkg命令使用方法:
# catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
cd ~/WORKSPACE/src
catkin_create_pkg learning_communication std_msgs rospy roscpp创建完成后,src下会生成一个
learning_communication
文件夹,已包含CmakeList.txt
和package.xml
编译功能包
1
2
3cd ~/WORKSPACE
catkin_make
source ~/WORKSPACE/devel/setup.bash同一工作空间下不能存在同名功能包
不同工作空间下可以存在同名功能包
所有工作空间的路径会依次顺序记录在ROS_PACKAGE_PATH环境变量中,即新的路径会排在前面.当在不同工作空间下存在同名功能包,ROS会优先查找纪录在最前端的工作空间中有没有需要的功能包,如果不存在则继续向下查找.
可通过以下命令查看所有ROS的环境变量
1 | env | grep ros |
查找功能包存放路径
1 | rospack find PACKAGE |
TODO:添加查找功能包的路径图片
ROS通信编程
话题编程
话题编程流程
- 创建发布者
- 创建订阅者
- 添加编译选项
- 添加可执行程序
创建发布者Publisher
1 | /** |
创建订阅者Subscriber
1 | /** |
编译功能包
设置编译规则CmakeList.txt
设置头文件路径
include_directories(头文件相对路径)
1
2
3
4
5
6## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
include
${catkin_INCLUDE_DIRS}
)设置需要编译的代码和可生成的可执行文件
add_executable(生成的可执行文件 参与编译的源码文件1 参与编译的源码文件2)
配置链接的第三方库文件
target_link_libraries(生成的可执行文件 链接的库1 链接的库2)
若生成的可执行文件需要依赖其他文件生成的代码,如消息类型,则需设置依赖
add_dependencies(生成的可执行文件 ${PROJECT_NAME}_generate_messages_cpp)
1
2
3
4
5
6
7
8
9
10## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
#add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
#add_dependencies(listener ${PROJECT_NAME}_generate_messages_cpp)
在工作空间路径下编译
1 | cd ~/WORKSPACE |
系统会生成talker
和listener
两个可执行文件,位于~/WORKSPACE/devel/lib/PACKAGE
路径下
运行功能包
每次编译之后都需要设置环境变量,假定已将环境变量脚本添加到终端配置文件中,运行以下代码刷新环境变量
1 | source ~/.bashrc |
启动
roscore
1
roscore
启动
Publisher
1
rosrun learning_communication talker
(TODO:添加运行截图)
启动
Subscriber
1
rosrun learning_communication listener
(TODO:添加运行截图)
若先运行
Subscriber
,节点会处于等待状态直到Publisher
启动
至此,已经完成了话题通信.
自定义话题消息
以上,chatter
话题的消息类型为ROS预定的String
,在ROS元功能包common_msgs
中提供了许多不同消息类型,几乎满足一般需求.但有些情况下需要针对自己的机器人设计特定的消息类型
.msg文件是ROS中定义消息类型的文件,放置在功能包根目录下的msg文件夹中
例如:
1 | string name |
还可以定义常量,在发布或订阅消息数据时可直接使用,相当于宏定义.如
1 | uint8 unknown = 0 |
很多消息定义中还会包含一个标准格式的头信息std_msgs/Header
,此处定义消息类型较为简单,也可以不加头信息
1 | uint32 seq |
seq
为消息顺序标识无,无需手动设置,Publisher
发布消息时会自动累加;
stamp
为消息中与数据相关联的时间戳,可用于时间同步
frame_id
为消息中与数据相关联的参考坐标系id
为了使用这个自定义的消息类型,需要编译msg文件
在
package.xml
中添加以下功能包依赖1
2<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>设置
CmakeList.txt
find_package
中添加消息声称依赖的功能包message_generation
1
2
3
4
5
6
7find_package(catkin REQUIRED COMPONENTS
geometry_msgs
roscpp
rospy
std_msgs
message_generation
)设置
catkin
依赖1
2
3
4
5catkin_package(
......
CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime
......
)设置需要编译的msg文件
1
2
3
4
5
6
7
8add_message_files(
FILES
Person.msg
)
generate_messages(
DEPENDENCIES
std_msgs
)
在根目录下
catkin_make
编译,使用如下命令查看自定义的Person消息类型1
rosmsg show person
(TODO:添加运行截图)
即可在代码中使用,参考
String
类型使用方法
服务编程
下面以加法运算为例,Client
发布两个需要相加的int
变量,Server
节点接收请求后完成运算并返回结果
自定义服务数据
通过.srv
文件进行接口定义,放置于功能包根目录下的srv
文件夹中.
该文件包含请求与应答两个数据域,中间用—-分割,内容格式与自定义话题相同
以加法运算为例,创建AddTwoInts.srv
1 | int64 a |
与话题一样,需要在package.xml
和CMakeList.txt
中配置依赖和编译规则
package.xml
添加以下依赖(与话题相同)1
2<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>CMakeList.txt
添加以下配置find_package
中添加消息声称依赖的功能包message_generation
(与话题相同)1
2
3
4
5
6
7find_package(catkin REQUIRED COMPONENTS
geometry_msgs
roscpp
rospy
std_msgs
message_generation
)设置需要编译的srv文件
1
2
3
4add_service_files(
FILES
AddTwoInts.srv
)
创建服务器Server
1 | /** |
可见,Server类似于话题中的Subscriber
创建客户端Client
1 | /** |
可见,Client类似于话题中的Publisher
编译功能包
设置编译规则CmakeList.txt
1 | add_executable(server src/server.cpp) |
catkin_make编译,刷新环境变量
运行功能包
启动
roscore
1
roscore
启动
Server
1
rosrun learning_communication server
(TODO:添加运行截图)
启动
Client
1
rosrun learning_communication client 3 5
(TODO:添加运行截图)
分布式多机通信
(TODO:待添加)
ROS常用组件
launch启动文件
每当我们需要运行一个ROS节点或工具时,都需要打开一个新的终端运行一个命令。当系统中的节点数量不断增加时,每个节点一个终端的模式会变得非常麻烦。启动文件(Launch File)便是ROS中一种同时启动多个节点的途径,还可以自动启动ROSMaster节点管理器,而且可以实现每个节点的各种配置,为多个节点的操作提供了很大便利。
下面是一个最简单的launch文件
1 | <launch> |
基本元素
<launch>
XML文件必须要包含一个根元素,launch文件中的根元素采用
<launch>
标签定义,文件中的其他内容都必须包含在这个标签之中:1
2
3<launch>
……
</launch><node>
启动文件的核心是启动ROS节点,采用
<node>
标签定义,语法如下:1
<node pkg="package-name" name="node-name" type="executable-name"/>
从上边的定义规则可以看出,在启动文件中启动一个节点需要三个属性:name、pkg和type。
name
: 节点运行的名称,将覆盖节点中ros::init()
定义的节点名称;pkg
: 节点所在的功能包名称type
: 节点的可执行文件名称在某些情况下,我们还有可能用到以下属性:
| 属性 | 属性作用 |
| :—————————- | —————————————————————————————— |
|output="screen"
| 终端输出转储在当前的控制台上,而不是在日志文件中 |
|respawn="true"
| 当roslaunch启动完所有该启动的节点之后,会监测每一个节点,保证它们正常的运行状态。对于任意节点,当它终止时,roslaunch 会将该节点重启 |
|required="true"
| 当被此属性标记的节点终止时,roslaunch会将其他的节点一并终止。注意此属性不可以与respawn="true"
一起描述同一个节点 |
|ns = "NAME_SPACE"
| 这个属性可以让你在自定义的命名空间里运行节点 |
|args = "arguments"
| 节点需要的输入参数 |
参数设置
<param>
parameter是ROS系统运行中的参数,存储在参数服务器中。在launch文件中可以通过
<param>
元素加载parameter。launch文件执行后,parameter就加载到ROS的参数服务器上了。每个活跃的节点都可以通过 ros::param::get()接口来获取parameter的值,用户也可以在终端中通过rosparam命令获得parameter的值。
<param>
使用方法:1
<param name="output_frame" value="odom"/>
运行launch文件后,output_frame这个parameter的值就设置为odom,并且加载到ROS参数服务器上了。但是在很多复杂的系统中,参数的数量很多,如果这样一个一个的设置会非常麻烦,ROS也为我们提供了另外一种类似的参数加载方式:
<rosparam>
<rosparam>
使用方法1
<rosparam file="$(find 2dnav_pr2)/config/costmap_common_params.yaml" command="load" ns="local_costmap" />
<rosparam>
可以帮助我们将一个yaml格式文件中的参数全部加载到ROS参数服务器中,需要设置command属性为“load”,还可以选择设置命名空间“ns”。<arg>
argument是launch文件内部的局部变量,仅限于launch文件内部使用,便于launch文件的重构,与ROS节点内部无关
1
<arg name="MyArg"/>
像上面这样,就简单地声明了一个参数,名叫demo,但是声明不等于定义,我们需要给他赋值,在赋值之后参数才能够发挥作用。
1
2<arg name="MyArg1" value="123"/>
<arg name="MyArg2" default="123"/>以上是两种简单的赋值方法,两者的区别是使用后者赋值的参数可以在命令行中像下面这样被修改,前者则不行。
1
roslaunch xxx xxx.launch MyArg2=1234
launch文件中需要使用到argument时,可以使用如下方式调用:
1
2
3<arg name="arg-name" value="123"/>
<param name="foo" value="$(arg arg-name)" />
<node name="node" pkg="package" type="type "args="$(arg arg-name)" />当
$(arg arg_name)
出现在launch文件任意位置时,将会自动替代为所给参数的值。
重映射机制
ROS的设计目标是提高代码的复用率,所以ROS社区中的很多功能包我们都可以拿来直接使用,而不需要关注功能包的内部实现。那么问题就来了,别人功能包的接口不一定和我们的系统兼容呀?
ROS提供一种重映射的机制,简单来说就是取别名,类似于C++中的别名机制,我们不需要修改别人功能包的接口,只需要将接口名称重映射一下,取个别名,我们的系统就认识了(接口的数据类型必须相同)。launch文件中的<remap>
标签顾名思义重映射,emap标签里包含一个original-name
和一个new-name
,及原名称和新名称。
比如turtlebot的键盘控制节点,发布的速度控制指令话题可能是/turtlebot/cmd_vel
,但是我们自己的机器人订阅的速度控制话题是/cmd_vel
,这个时候使用<remap>
就可以轻松解决问题,将/turtlebot/cmd_vel
重映射为/cmd_vel
,我们的机器人就可以接收到速度控制指令了:
1 | <remap from="/turtlebot/cmd_vel" to="/cmd_vel"/> |
重映射机制在ROS中的使用非常广泛,也非常重要,方法不止这一种,也可以在终端rosrun命令中实现重映射.
嵌套复用
在复杂的系统当中,launch文件往往有很多,这些launch文件之间也会存在依赖关系。如果需要直接复用一个已有launch文件中的内容,可以使用<include>
标签包含其他launch文件,这和C语言中的include几乎是一样的。
1 | <include file="$(dirname)/other.launch" /> |
launch是ROS框架中非常实用、灵活的功能,它类似于一种高级编程语言,可以帮助我们管理启动系统时的方方面面。在使用ROS的过程中,很多情况下我们并不需要编写大量代码,仅需要使用已有的功能包,编辑一下launch文件,就可以完成很多机器人功能。
注 : 使用
roslaunch
命令 和 使用rosrun
命令 单独运行每个节点之间的重要区别默认情况下,roslaunch 命令 从启动节点开始,标准输出信息会重定向到一个日志文件中,而不会像 rosrun 命令那样,将 log 信息显示在终端(console)上。日志文件所在路径:
∼/.ros/log/run_id/node_name-number-stdout.log
.如果想将标准输出信息显示在终端(console)上,需要在 node 元素中使用 output 属性:output=”screen”.但node 元素的 output 属性只能影响这个节点自己。除了 output 属性,我们可以使用 roslaunch命令行工具的 –screen 命令行选项强制性的在终端的窗口中显示所有节点的输出信息。1
roslaunch --screen package-name launch-file-name
TF坐标变换
如图所示A,B两个坐标系,A坐标系下的位姿可以通过平移和旋转变换成B坐标系下的位姿,这里的平移和旋转可以用一个4*4的变换矩阵来描述(详见机器人学)
创建TF广播器
1 | /** |
创建TF监听器
1 | /** |
编译功能包
设置编译规则CmakeList.txt
1 | add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp) |
catkin_make编译,刷新环境变量
launch文件启动
1 | <launch> |