文章目录
前言
马上开学,目前学校很多实验室都是人工智能这块,大部分都是和机器人相关,然后软件这块就是和cv、ros相关,就打算开始学习一下。
本章节是虚拟机安装Ubuntu18.04以及安装ROS的环境。
学习教程:【Autolabor初级教程】ROS机器人入门,博客中一些知识点是来源于赵老师的笔记在线笔记,本博客主要是做归纳总结,如有侵权请联系删除。
视频中的案例都基本敲了遍,这里给出我自己的源代码文件:
链接:https://pan.baidu.com/s/13CAzXk0vAWuBsc4oABC-_g
提取码:0hws
所有博客文件目录索引:博客目录索引(持续更新)
目标解决问题:
1、如何关联不同的功能包,繁多的ROS节点应该如何启动?
2、功能包、节点、话题、参数重名时应该如何处理?
3、不同主机上的节点如何通信?
一、ROS元功能包
1.1、认识元功能包
若是我们编写了多个不同功能的包,那么在提供给用户使用时,难道需要用户一个一个包去下载呢?我们试想是否能够通过一个配置将所需要的包全部直接进行下载引入,将其组织到一起。
解答:这就设计到元功能包,在ROS中,提供了一种方式可以将不同的功能包打包成一个功能包,当安装某个功能模块时,直接调用打包后的功能包即可,该包又称之为元功能包(metapackage)。。
概念:MetaPackage是Linux的一个文件管理系统的概念。是ROS中的一个虚包,里面没有实质性的内容,但是它依赖了其他的软件包,通过这种方法可以把其他包组合起来,我们可以认为它是一本书的目录索引,告诉我们这个包集合中有哪些子包,并且该去哪里下载。
- 例如:sudo apt install ros-noetic-desktop-full 命令安装ros时就使用了元功能包,该元功能包依赖于ROS中的其他一些功能包,安装该包时会一并安装依赖。
好处:方便用户的安装,我们只需要这一个包就可以把其他相关的软件包组织到一起安装了。
1.2、自定义一个元功能包
目标:再此之前我们在ROS通信机制中就创建了关于话题通信、服务通信、参数服务器的三个功能包,那么在此我们就可以使用一个元功能包将其进行组织在一起。
实际案例场景:navigation2
步骤一:创建一个元空间包。
# 进入工程目录中的src目录下
cd /home/workspace/roslearn/src
# 创建功能包名为plumbing_my
catkin_create_pkg --rosdistro melodic plumbing_my
步骤二:编写package.xml配置,引入相应的功能包
<exec_depend>plumbing_server_client</exec_depend>
<exec_depend>plumbing_pub_sub</exec_depend>
<exec_depend>plumbing_param_server</exec_depend>
<!-- 新增一个<metapackage />标签 -->
<export>
<metapackage />
</export>
步骤三:修改CMakeLists.txt。
cmake_minimum_required(VERSION 3.0.2)
project(plumbing_my)
find_package(catkin REQUIRED)
catkin_metapackage()
- 添加最后一行即可。
最后我们来编译整个项目即可,若是没有报错,说明元功能包配置没有问题。
二、ROS节点管理launch文件
2.1、快速使用launch文件来启动节点
# 进入工程目录中的src目录下
cd /home/workspace/roslearn/src
# 创建功能包名为launch01_basic
catkin_create_pkg --rosdistro melodic launch01_basic roscpp rospy std_msgs
01start_turtle.launch:
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="myTurtle" output="screen" />
<node pkg="turtlesim" type="turtle_teleop_key" name="myTurtleContro" output="screen" />
</launch>
接着我们来编译整个项目,并进行启动该节点:
source ./devel/setup.bash
roslaunch launch01_basic 01start_turtle.launch
2.2、launch文件标签之group节点
介绍
<group>
标签可以对节点分组,具有 ns 属性,可以让节点归属某个命名空间。
属性:
-
ns=“名称空间” (可选)
-
clear_params=“true | false” (可选),启动前,是否删除组名称空间的所有参数(慎用…此功能危险)。
实战示例
目的:启动两组相同的服务,看在group下会不会加上对应的namespace前缀。
分两组启动小乌龟以及键盘控制程序:05demo_group.launch
<launch>
<group ns="changlu1">
<node pkg="turtlesim" type="turtlesim_node" name="turtle_node" output="screen" />
<node pkg="turtlesim" type="turtle_teleop_key" name="turtle_key" output="screen" />
</group>
<group ns="changlu2">
<node pkg="turtlesim" type="turtlesim_node" name="turtle_node" output="screen" />
<node pkg="turtlesim" type="turtle_teleop_key" name="turtle_key" output="screen" />
</group>
</launch>
接着去刷新环境变量并进行加载launch文件:
source ./devel/setup.bash
roslaunch launch01_basic 05demo_group.launch
对于service、topic都增加上了命名空间:
launch文件标签之node节点
节点的启动时多进程执行的,并非是按照配置文件顺序向下执行。
属性
# 节点所属的包
pkg="包名"
# 节点类型(与之相同名称的可执行文件)
type="nodeType"
# 节点名称
name="nodeName"
# 参数传递
args="xxx xxx xxx" (可选)
# 机器名
machine="机器名"
# 若是该结点退出,是否自动重启
respawn="true | false" (可选)
# 一般搭配respawn使用,若是respawn为true,那么延迟 N 秒后启动节点
respawn_delay=" N" (可选)
# 该节点是否必须,如果为 true,那么如果该节点退出,将杀死整个 roslaunch
required="true | false" (可选)
# 在指定命名空间 xxx 中启动节点【效果就是在对应发布的话题、服务前缀加上命名空间】
ns="xxx" (可选)
# 在启动前,删除节点的私有空间的所有参数(慎重使用)
clear_params="true | false" (可选)
# 日志发送目标,可以设置为 log 日志文件,或 screen 屏幕,默认是 log
output="log | screen" (可选)
子级标签
env 环境变量设置
remap 重映射节点名称
rosparam 参数设置
param 参数设置
launch文件标签之remap
若是我们想要将节点中的话题进行更改,就可以在node节点下新增一个remap节点:
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="myTurtle" output="screen">
<!-- 话题重命名 -->
<remap from="/turtle1/cmd_vel" to="/cmd_vel" />
</node>
<node pkg="turtlesim" type="turtle_teleop_key" name="myTurtleContro" output="screen" />
</launch>
接着我们启动launch,并查看topic列表看是否有被重命名:
source ./devel/setup.bash
roslaunch launch01_basic 01start_turtle.launch
2.3、launch文件标签之include
include
标签用于将另一个 xml 格式的 launch 文件导入到当前文件。
实战案例:使用include标签来包含之前的启动乌龟案例。
02turtle_include.launch:
<launch>
<!-- include表示直接包含某个launch文件,来直接启动 -->
<include file="$(find launch01_basic)/launch/01start_turtle.launch" />
</launch>
接着去编译执行一下:
source ./devel/setup.bash
roslaunch launch01_basic 02turtle_include.launch
2.4、launch文件标签之param
介绍
name="命名空间/参数名"
:参数名称,可以包含命名空间。
value="xxx" (可选)
:定义参数值,如果此处省略,必须指定外部文件作为参数源。
type="str | int | double | bool | yaml" (可选)
:指定参数类型,如果未指定,roslaunch 会尝试确定参数类型,规则如下:如果包含 ‘.’ 的数字解析未浮点型,否则为整型"true" 和 “false” 是 bool 值(不区分大小写)其他是字符串。
实战
创建launch文件:03turtle_parm.launch
<launch>
<!-- param标签:向param服务器添加键值对参数 -->
<!-- 方式一:设置在与node节点同级 -->
<param name="param_a" type="int" value="100" />
<node pkg="turtlesim" type="turtlesim_node" name="changlu" output="screen">
<!-- 方式二:在node节点下添加param(这里在node节点对应的parm参数名称前要对应命名空间) -->
<param name="param_b" type="double" value="3.14" />
</node>
<node pkg="turtlesim" type="turtle_teleop_key" name="myTurtleContro" output="screen" />
</launch>
编译项目,然后去启动节点:
source ./devel/setup.bash
roslaunch launch01_basic 03turtle_parm.launch
可以看到下方的param参数第一个是node节点下的,第二个是与node节点平级的:
rosparam list
2.5、launch文件标签之rosparam
介绍
<rosparam>
标签可以从 YAML 文件导入参数,或将参数导出到 YAML 文件,也可以用来删除参数,<rosparam>
标签在<node>
标签中时被视为私有。
参数:
- command=“load | dump | delete” (可选,默认 load)加载、导出或删除参数
- file="$(find xxxxx)/xxx/yyy…"加载或导出到的 yaml 文件
- param=“参数名称”
- ns=“命名空间” (可选)
实战
主要介绍load、dump以及delete删除操作。
load命令:04demo_rosparam.launch
<launch>
<!--
rosparam位置:node节点平级、node的子节点
commond="load",表示导入
-->
<rosparam command="load" file="$(find launch01_basic)/launch/params.yaml" />
<node pkg="turtlesim" type="turtlesim_node" name="changlu" output="screen">
<rosparam command="load" file="$(find launch01_basic)/launch/params.yaml" />
</node>
</launch>
params.yaml:
cl_r: 155
cl_g: 155
cl_b: 155
执行命令:
source ./devel/setup.bash
roslaunch launch01_basic 04demo_rosparam.launch
dump命令:04demo_dumpparam.launch
说明:一般dump命令都需要在其他launch文件执行完成之后再执行,若是直接写在launch中不太推荐,因为可能会漏掉一些参数。
<launch>
<!-- dump所有param -->
<rosparam command="dump" file="$(find launch01_basic)/launch/params_out.yaml" />
</launch>
执行命令:
source ./devel/setup.bash
roslaunch launch01_basic 04demo_dumpparam.launch
delete功能:04demo_deleteparam.launch
<launch>
<!-- 删除指定param -->
<rosparam command="delete" param="cl_r" />
</launch>
执行命令:
source ./devel/setup.bash
roslaunch launch01_basic 04demo_deleteparam.launch
实战
编写launch文件06demo_arg.launch:根据launch命令添加指定的参数并借助param标签读取arg标签参数值保存到param服务器。
<launch>
<!--
default表示默认值,在命令行启动launch时可以不用传参也不会报错。
name表示参数名称
-->
<arg name="param1" default="haha"/>
<!-- 想要获取到arg标签中的参数需要使用表达式:$(arg xxx) -->
<param name="param1" value="$(arg param1)" />
</launch>
实际测试:
source ./devel/setup.bash
# 携带参数
roslaunch launch01_basic 06demo_arg.launch param1:=12
rosparam get /param1
roslaunch launch01_basic 06demo_arg.launch
三、ROS工作空间覆盖
当我们进行source /home/用户/路径/工作空间A/devel/setup.bash
,实际上就会将我们对应工程的src目录添加到ros的包路径,我们可以使用命令来查看:
echo $ROS_PACKAGE_PATH
空间覆盖问题:若是我们有两个工程A和B,在两个工程中都有一个相同名称包turtle,那么最后执行source命令的其环境变量优先级更高。
创建一个新的工程并进行动态刷新配置来查看下效果:
mkdir -p roslearn2/src
cd roslearn2
catkin_make
source ./devel/setup.bash
隐藏的一个bug问题
若是按照上面的123顺序来进行source加载依赖,那么就会出现问题,注意此时pg2/A的优先级最高,若是在执行pg1的时候,其依赖的是pg3/A,但是在程序代码中引用一般只是A/执行文件,所以此时就会去执行pg2/A,最终造成严重问题!
解决方案:尽可能不同的工程中不要设置相同的包,以免出现问题。
四、重名问题
4.1、ROS节点名称重名(三种方式)
方式一:rosrun设置命名空间
# 语法: rosrun 包名 节点名 __ns:=新名称
rosrun turtlesim turtlesim_node __ns:=/xxx
方式二:launch文件编写时在node标签中添加ns键值对
<launch>
<!-- ns表示命名空间 -->
<node pkg="turtlesim" type="turtlesim_node" name="t1" ns="hello"/>
</launch>
方式三:代码编写
//代码1:name_时间戳
ros::init(argc,argv,"zhangsan",ros::init_options::AnonymousName);
//代码2:传入map的形式
std::map<std::string, std::string> map;
map["__ns"] = "xxxx";
ros::init(map,"wangqiang");
4.2、ROS话题名称设置
方式一:启动节点命令时进行话题重映射
# 示例:将话题/cmd_vel重映射为/turtle1/cmd_vel
rosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/turtle1/cmd_vel
方式二:launch文件中ndoe节点下编写remap节点来实现话题重映射
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="t1" />
<node pkg="teleop_twist_keyboard" type="teleop_twist_keyboard.py" name="key">
<!-- 指定话题重映射 -->
<remap from="/cmd_vel" to="/turtle1/cmd_vel" />
</node>
</launch>
方式三:代码编写
//全局名称:与节点名称无关
ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter",1000); //结果:/chatter
//相对名称:与命名空间、节点名称连接
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000); //结果:xx/chatter
//私有名称:节点名称为hello,并且设置了命名空间xx
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000); //结果:/xx/hello/chatter
//使用~,而话题名称有时/开头时,那么话题名称是绝对的(不受影响)
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money",1000); //结果依旧是:/chatter/money
4.3、ROS参数名称设置
方式一:rosrun命令设置参数
# 设置参数为A=100
rosrun turtlesim turtlesim_node _A:=100
方式二:launch文件来设置参数,node平级与node节点下
<launch>
<param name="p1" value="100" />
<node pkg="turtlesim" type="turtlesim_node" name="t1">
<param name="p2" value="100" />
</node>
</launch>
方式三:编码设置参数
ros::param::set("/set_A",100); //全局,和命名空间以及节点名称无关
ros::param::set("set_B",100); //相对,参考命名空间
ros::param::set("~set_C",100); //私有,参考命名空间与节点名称
假设设置的 namespace 为 xxx,节点名称为 yyy,使用 rosparam list 查看:
/set_A
/xxx/set_B
/xxx/yyy/set_C
五、ROS分布式通信
准备两台机子:
步骤1、确定ip地址以及hostname主机名
# 获取ip地址
ifconfig
# 获取主机名
hostname
主机:192.168.3.41 changlu-VirtualBox
从机:192.168.3.18 changlu-VirtualBox2
步骤2、配置本地的hostname映射(ip与主机域名)
打开并编辑/etc/hosts文件
主机:
127.0.1.1 changlu-VirtualBox
192.168.3.41 changlu-VirtualBox2
从机:
127.0.1.1 changlu-VirtualBox2
192.168.3.18 changlu-VirtualBox
生效hosts文件命令如下:
sudo /etc/init.d/networking restart
步骤3、打开/root/.bashrc文件,添加两条参数配置信息
模板如下:
export ROS_MASTER_URI=http://主机IP:11311
export ROS_HOSTNAME=主机IP
export ROS_MASTER_URI=http://主机IP:11311
export ROS_HOSTNAME=从机IP
主机:
export ROS_MASTER_URI=http://192.168.3.18:11311
export ROS_HOSTNAME=192.168.3.18
# 或者
# export ROS_MASTER_URI=http://changlu-VirtualBox:11311
# export ROS_HOSTNAME=changlu-VirtualBox
从机(多个从节点同样如此配置Master_URI都填一个):
export ROS_MASTER_URI=http://192.168.3.18:11311
export ROS_HOSTNAME=192.168.3.41
# 或者
export ROS_MASTER_URI=http://changlu-VirtualBox:11311
export ROS_HOSTNAME=changlu-VirtualBox2
分别令其生效:
source /root/.bashrc
步骤四:主机从机来进行测试通信
主机:
roscore
# 弹出图像化界面
rosrun turtlesim turtlesim_node
从机:
# 读取键盘上下左右移动信息控制乌龟行动
rosrun turtlesim turtle_teleop_key
其实也可以从机来打开图形化界面,主机来进行键盘控制,不过需要注意的是必须是master启动roscore。