0%

机器人操作系统ROS_2_ROS通信机制与编程基础

ROS的通信机制

ROS通信架构:

  • 各种数据的处理
  • 进程的运行
  • 消息的传递
  • 等等

ROS的核心: 分布式通信机制

ROS的通信方式主要有:

  • 话题
  • 服务
  • 参数
  • 动作

ROS编程基础

ros编程

目前最常用的只有roscpp和rospy

Client Library 介绍
roscpp ROS的C++库, 是目前最广泛应用的ROS客户端库, 执行效率高
rospy ROS的Python库, 开发效率高, 通常用在对运行时间没有太大要求的场合, 例如配置、初始化等操作

整个ROS包括的packages:

ROS的packages

roscpp

  • roscpp位于/opt/ros/kinetic之下, 用C++实现了ROS通信
  • 在ROS中, C++的代码是通过catkin这个编译系统(扩展的CMake)来进行编译构建的(简单地理解, roscpp就是C++的库, 创建一个CMake工程, 在其中include了roscpp等ROS的libraries, 这样就可以在工程中使用ROS提供的函数)

调用ROS的C++接口, 首先
#include <ros/ros.h>

roscpp的主要部分:

内容 说明
ros::init() 解析传入的ROS参数, 创建node第一步需要用到的函数
ros::NodeHandle 和topic、service、param等交互的公共接口
ros::master 包含从master查询信息的函数
ros::this_node 包含查询这个进程(node)的函数
ros::service 包含查询服务的函数
ros::param 包含查询参数服务器的函数, 而不需要用到NodeHandle
ros::names 包含处理ROS图资源名称的函数

主要功能分类:

功能 说明
Initialization and Shutdown 初始与关闭
Topics 话题
Services 服务
Parameter Server 参数服务器
Timers 定时器
NodeHandles 节点句柄
Callbacks and Spinning 回调和自旋(轮询、循环)
Logging 日志
Names and Node Information 名称管理
Time 时钟
Exception 异常

初始化节点:

  • 调用ros::init()函数, 初始化节点的名称和其他信息, 一般ROS程序以这种方式开始
  • 创建ros::NodeHandle对象, 也就是节点的句柄, 它可以用来创建Publisher Subscriber 以及做其他事情

关闭节点:

  • 通常直接在终端上按Ctrl+C关闭一个节点, 系统会自动触发SIGINT句柄来关闭这个进程
  • 也可以通过调用ros::shutdown()来手动关闭节点, 但通常很少这样做

roscpp开发步骤

  • 创建源文件
    • mkdir -r ros_catkin_wksp/src
    • cd ros_catkin_wksp
    • catkin_make
    • cd src
    • catkin_create_pkg ros_node_test roscpp std_msgs
    • cd ros_node_test/src
    • rosed ros_node_test node_test.cpp 或 vim node_test.cpp
  • 编辑源文件
1
2
3
4
5
6
7
8
9
#include<ros/ros.h>
int main(int argc, char** argv){
ros::init(argc, argv, "my_node");
ros::NodeHandle nh;
//...节点功能
//...
ros::spin();//用于触发topic, service的相应队列
return 0;
}
  • 运行程序
    • 打开ros_node_test下的CMakeLists.txt文件, 添加
      • add_executable(node src/node_test.cpp)
      • target_link_libraries(node ${catkin_LIBRARIES})
    • cd ../..
    • catkin_make (编译, 如果成功)
    • source devel/setup.sh
    • roscore
    • rosrun ros_node_test node (新终端)
    • rosnode list (新终端, 查看新创建的节点)

roscpp总体编程步骤:

  • 创建工作空间(创建包之前的操作只需要一次, 以后不再需要)
  • 编写roscpp程序启动节点
  • 修改CMakeLists.txt文件
  • 编译并运行
  • rosnode查看节点是否运行成功

rospy

rospy是Python版本的ROS客户端库, 提供了Python编程需要的接口, 可以认为rospy就是一个Python的模块
这个模块位于/opt/ros/kinetic/lib/python2.7/dist-packages/rospy之中

由于Python的开发效率高, 但执行效率低
所以, 开发SLAM(即时定位与地图构建), 路径规划, 机器视觉等方面的算法时, 往往优先选择C++
对于一些简单的功能可以优先选用Python

rospy包含的功能与roscpp相似, 都有关于node, topic, service , param, time相关操作

但同时rospy和roscpp也有一些区别:

  • rospy没有一个NodeHandle, 像创建publisher, subscriber等操作都被直接封装成了rospy中的函数或类, 调用起来简单直观
  • rospy一些接口的命名和roscpp不一致, 有些地方需要开发者注意, 避免调用错误

rospy的组织形式

通常来说, Python代码有两种组织方式, 一种是单独的一个Python脚本, 适用于简单的程序, 另一种是Python模块, 适合体量较大的程序
对于一些小体量的ROS程序, 一般就是一个Python文件, 放在script/路径下

ROS建议按照以下规范来建立一个Python的模块:
your_package
|- src/
|- your_package/
|- init.py
|- modulefiles.py
|- scripts/
|- your_script.py
|- setup.py

通常常用的ROS命令, 大多数其实都是一个个Python模块, 源代码存放在ros_comm仓库的tools路径下:
https://github.com/ros/ros_comm/tree/lunar-devel/tools
一个命令行工具(如rosbag, rosmsg)都是用模块的形式组织核心代码, 然后在script/下建立一个脚本来调用模块

rospy中具体的API参考
http://docs.ros.org/api/rospy/html/rospy-module.html

rospy中的各种接口

node相关接口

topic相关接口

topic相关接口-Publisher

topic相关接口-Subscriber

service相关接口

service相关接口-Service类(server)

service相关接口-ServiceProxy类(client)

param相关接口

时钟相关接口-函数

时钟相关接口-Time类

rospy开发的步骤

  • 创建源文件:
    cd my_ros_test/src
    catkin_create_pkg node_rospy std_msgs rospy
    cd node_rospy
    mkdir scripts
    cd scripts
    vim node.py
  • 编辑源文件:
1
2
3
4
5
6
7
#!/usr/bin/env python
# -*- coding:usf-8 -*-
import rospy
if __name__ == "__main__":
rospy.init_node('rospy_node')
while not rospy.is_shutdown():
pass
  • 执行程序:
    chmod +x node.py
    source my_ros_test/devel/setup.sh
    rosrun node_rospy node.py 或者直接 ./node.py
    rosnode list (新终端)

ROS话题通信机制

话题在ROS中使用最为频繁, 通信模型也相对复杂
话题分为发布者和订阅者, 发布和订阅的顺序没有要求

ROS话题通信机制

话题通信流程

  • Talker注册
  • Listener注册
  • ROS Master进行信息匹配
  • Listener发送连接请求
  • Talker确认连接请求
  • Listener尝试与Talker建立网络连接
  • Talker向Listener发布数据

Publisher的编程

Publisher开发流程:

  • 初始化ROS节点
  • 向ROS Master注册节点信息, 包括发布的话题名和话题中的消息类型
  • 创建消息数据
  • 按照一定的频率循环发布消息

Subscriber的编程

Subscriber的编程:

  • 初始化ROS节点
  • 订阅需要的话题
  • 循环等待话题消息, 接收到消息后进入回调函数
  • 回调函数中完成消息处理

自定义消息的发布和订阅

  1. 自定义话题消息, 创建msg文件, 内容如下

string name
uint8 age
uint8 sex

uint8 unknow = 0
uint8 male = 1
uint8 female = 2

  1. 在package.xml中添加功能包依赖
1
2
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
  1. 在CMakeLists.txt中添加编译选项

find_package(… message_generation)
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs turtlesim message_runtime)

  1. 编辑roscpp和rospy程序

  2. 在CMakeLsts.txt中添加

1
2
3
4
5
6
7
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)

ROS服务通信机制

服务是一种带有应答的通信机制, 与话题相比它减少了listener与talker之间的rpc通信

服务通信流程

  • Talker注册
  • Listener注册
  • ROS Master 进行信息匹配
  • Listener与Talker建立网络连接
  • Taker向Listener发布服务应答数据

服务通信机制的特点

  • 同步通信
  • 双向
  • 简单高效

客户端client的编程

客户端client开发流程

  • 初始化一个ROS节点
  • 创建一个Client实例
  • 发布服务请求数据
  • 等待server处理之后的应答结果

服务端server的编程

服务端server开发流程

  • 初始化ROS节点
  • 创建Server实例
  • 循环等待服务请求, 进入回调函数
  • 在回调函数中完成服务功能的处理, 并反馈应答数据

自定义服务数据的编程

  1. 定义srv文件

string name
uint8 age
uint8 sex

uint8 unknown = 0
uint8 male = 1
uint8 female = 2

  1. 在package.xml中添加功能包依赖
1
2
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
  1. 在CMakeLists.txt中添加编译选项

find_package(… message_generation)
add_service_files(FILES Person.srv)
generate_messages(DEPENDENCIES std_msgs)

  1. 编写相应程序

  2. 配置CMakeLists.txt, 向其中添加

1
2
3
4
5
6
7
add_executable(person_server src/person_server.cpp)
target_link_libraries(person_server ${catkin_LIBRARIES})
add_dependencies(person_server ${PROJECT_NAME}_gencpp)

add_executable(person_clent src/person_client.cpp)
target_link_libraries(person_client ${catkin_LIBRARIES})
add_dependencies(person_client ${PROJECT_NAME}_gencpp)

ROS参数通信机制

ROS参数管理机制

参数类似于ROS中的全局变量, 有ROS Master进行管理, 其通信机制较为简单
不涉及TCP, UDP的通信

参数通信流程:

  • Talker设置变量(使用RPC向master发送参数设置数据)
  • Listener查询参数值(使用RPC向master查询参数值)
  • ROS Master向Listener发送参数值

注意: 如果Listener不主动查询, 则无法获得更新后的参数

ROS参数通信程序设计

参数通信程序设计流程:

  • 初始化ROS节点
  • get函数获取参数
  • set函数设置参数

launch文件的编写

launch文件介绍

启动文件(launch file)便是ros中一种同时启动多个节点的途径, 还可以自动启动ROS Master节点管理器, 而且可以实现每个节点的各种配置, 为多个节点的操作提供了很大便利

  • 在ros中以.launch.xml后缀结尾的文件就是启动文件
  • 使用launch文件启动时, 会自动启动ROSMaster
  • launch文件的编写使用xml格式
  • 运行launch文件的方法: roslaunch 包名称 launch文件名
  • 在功能包目录下创建文件夹launch, 存放所有的launch文件

launch文件编写

基本元素

一个简单的launch文件:

1
2
3
4
<launch>
<nodepkg="turtlesim" name="sim1" type="turtlesim_node"/>
<nodepdg="turtlesim" name="sim2" type="turtlesim_node"/>
</launch>

这是一个简单而完整的launch文件, 包含一个根元素和两个节点元素

  • 元素
    xml文件必须要包含一个根元素, launch文件中的根元素采用标签定义, 文件中的其他内容都必须包含在这个标签之中
1
2
3
<launch>
...
</launch>
  • 元素
    启动文件的核心是启动ros节点, 采用标签定义, 语法如下
1
<node pkg="package-name" type="executable-name" name = "node-name" />

的其他属性
output = “screen”: 将节点的标准输出打印到中断屏幕, 默认输出为日志文档
respawn = “true”: 复位属性, 该节点停止时, 会自动重启, 默认为false
required = “true”: 必要节点, 当该节点终止时, launch文件中的其他节点也被终止
ns = “namespace”: 命名空间, 为节点内的相对名称添加命名空间前缀
args = “arguments”: 节点需要的输入参数

实际机器人中的节点:
见robot.launch
文件中还出现了, , , 这些都是常用的标签元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- robot.launch -->
<launch>

<node pkg="mrobot_bringup" type="mrobot_bringup" name="mrobot_bringup" output="screen" />

<arg name="urdf_file" default="$(find xacro)/xacro --inorder '$(find mrobot_description)/urdf/mrobot_with_rplidar.urdf.xacro'" />
<param name="robot_description" command="$(arg urdf_file)" />

<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />

<node pkg="robot_state_publisher" type="robot_state_publisher" name="state_publisher">
<param name="publish_frequency" type="double" value="5.0" />
</node>
<node name="base2laser" pkg="tf" type="static_transform_publisher" args="0 0 0 0 0 0 1 /base_link /laser 50"/>

<node pkg="robot_pose_ekf" type="robot_pose_ekf" name="robot_pose_ekf">
<remap from="robot_pose_ekf/odom_combined" to="odom_combined"/>
<param name="freq" value="10.0"/>
<param name="sensor_timeout" value="1.0"/>
<param name="publish_tf" value="true"/>
<param name="odom_used" value="true"/>
<param name="imu_used" value="false"/>
<param name="vo_used" value="false"/>
<param name="output_frame" value="odom"/>
</node>

<include file="$(find mrobot_bringup)/launch/rplidar.launch" />

</launch>

参数

- parameter是ros系统运行中的参数, 存储在参数服务器中 - 在launch文件中通过元素加载parameter - launch文件执行后, parameter就加载到ROS的参数服务器上了 - 每个活跃的节点都可以通过`ros::param::get()`接口来获取parameter的值, 用户也可以在终端中通过rosparam命令获得parameter的值 用法: - ``
- 在很多复杂的系统中, 参数的数量很多, ros也为我们提供了另外一种类似的参数加载方式 用法: - `` - 将一个yaml格式文件中的参数全部加载到ROS参数服务器中
- argument是另外一个概念, 类似于launch文件内部的局部变量, 仅限于launch文件使用, 便于launch文件的重构, 和ROS节点内部的实现没有关系 设置launch文件的内部变量 - `` launch文件中需要使用到argument时, 可以使用如下方式调用: - `` - ``

重映射

- ros的设计目标是提高代码的复用率, 所以ros社区中的很多功能包我们都可以拿来直接使用, 而不需要关注功能包的内部实现, 那么问题就来了, 别人功能包的接口不一定和我们的系统兼容 - ROS提供一种重映射的机制, 简单来说就是取别名 例如: - ``

嵌套复用

- 在复杂的系统当中, launch文件往往有很多, 这些launch文件之间也会存在依赖关系 - 如果需要直接复用一个已有launch文件中的内容, 可以使用``标签包含其他launch文件, 这个C语言中的include几乎是一样的 例如: - ``

内容小结

launch是ROS框架中非常实用, 灵活的功能, 它类似于一种高级编程语言, 可以帮助我们管理启动系统时的方方面面
在使用ROS的过程中, 很多情况下我们并不需要编写大量代码, 仅需要私用已有的功能包, 编辑一下launch文件, 就可以完成很多机器人功能

参考:
http://wiki.ros.org/roslaunch/XML
https://blog.csdn.net/weixin_41995979/article/details/81784987

Thank you for your reward !