前言
本系列将依托赵虚左老师的ROS课程,写下自己的一些心得与笔记。
课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ
讲义链接:http://www.autolabor.com.cn/book/ROSTutorials/index.html
文章可能存在疏漏的地方,恳请大家指出。
所谓工作空间覆盖,是指不同工作空间中,存在重名的功能包的情形。
ROS 开发中,会自定义工作空间且自定义工作空间可以同时存在多个,可能会出现一种情况:虽然特定工作空间内的功能包不能重名,但是自定义工作空间的功能包与内置的功能包可以重名或者不同的自定义的工作空间中也可以出现重名的功能包,那么调用该名称功能包时,会调用哪一个呢?比如:自定义工作空间A存在功能包 turtlesim,自定义工作空间B也存在功能包turtlesim,当然系统内置空间也存在turtlesim,如果调用turtlesim包,会调用哪个工作空间中的呢?
一个例子:
1.新建工作空间A与工作空间B,两个工作空间中都创建功能包: turtlesim。
2.在 ~/.bashrc 文件下追加当前工作空间的 bash 格式如下:
source /home/用户/路径/工作空间A/devel/setup.bash
source /home/用户/路径/工作空间B/devel/setup.bash
3.新开命令行:source .bashrc加载环境变量
4.查看ROS环境环境变量echo $ROS_PACKAGE_PATH
结果:自定义工作空间B:自定义空间A:系统内置空间
5.调用命令:roscd turtlesim会进入自定义工作空间B
原因
ROS 会解析 .bashrc 文件,并生成 ROS_PACKAGE_PATH
ROS包路径,该变量中按照 .bashrc 中配置设置工作空间优先级,在设置时需要遵循一定的原则:ROS_PACKAGE_PATH 中的值,和 .bashrc 的配置顺序相反—>后配置的优先级更高,如果更改自定义空间A与自定义空间B的source顺序,那么调用时,将进入工作空间A。
优先级顺序也可以用下列命令查看:
echo $ROS_PACKAGE_PATH
隐患
存在安全隐患,比如当前工作空间B优先级更高,意味着当程序调用 turtlesim 时,不会调用工作空间A也不会调用系统内置的 turtlesim,如果工作空间A在实现时有其他功能包依赖于自身的 turtlesim,而按照ROS工作空间覆盖的涉及原则,那么实际执行时将会调用工作空间B的turtlesim,从而导致执行异常,出现安全隐患。
BUG 说明:
当在 .bashrc 文件中 source 多个工作空间后,可能出现的情况,在 ROS PACKAGE PATH 中只包含两个工作空间,可以删除自定义工作空间的 build 与 devel 目录,重新 catkin_make,然后重新载入 .bashrc 文件,问题解决。
ROS中没有对功能包重名的补救措施,所以避免重名是十分必要的,
对于ROS节点名称重名问题,ROS给出了两种解决策略: 使用命名空间或名称重映射.同时,实现两种策略有以下三种途径:
当同时启动两个同名的节点时,会使第一个节点崩溃.
rosrun turtlesim turtlesim_node
以rosrun的途径, 有以下三种方式进行节点重命名:
设置命名空间:
rosrun turtlesim turtlesim_node __ns:=first
rosrun turtlesim turtlesim_node __ns:=second
两个节点都成功启动,再用rosnode Iist
进行查看:
/first/turtlesim
/rosout
/second/turtlesim
不同节点前有不同的前缀.
为节点起别名:
rosrun turtlesim turtlesim_node __name:=first
rosrun turtlesim turtlesim_node __name:=second
两个节点都成功启动,再用rosnode Iist
进行查看:
/first
/rosout
/second
或者
rosrun turtlesim turtlesim_node /turtlesim:=first
yuan@yuan-Legion-Y9000P-IAH7H:~$ rosnode list
/first
/rosout
不过第二种方式不适用于python文件
rosrun publisher demo07.py /publisher:=pub
yuan@yuan-Legion-Y9000P-IAH7H:~$ rosnode list
/rosout
/talker_person_p
rosrun turtlesim turtlesim_node __ns:=second __name:=nd
rosrun turtlesim turtlesim_node __ns:=first __name:=st
/first/st
/rosout
/second/nd
ps:
使用环境变量也可以设置命名空间,启动节点前在终端键入如下命令:
export ROS_NAMESPACE=xxxx
yuan@yuan-Legion-Y9000P-IAH7H:~$ rosnode list
/demo/turtlesim
/rosout
介绍 launch 文件的使用语法时,在 node 标签中有两个属性: name 和 ns,二者分别是用于实现名称重映射与命名空间设置的。使用launch文件设置命名空间与名称重映射也比较简单。
launch文件示例如下
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="t1" />
<node pkg="turtlesim" type="turtlesim_node" name="t2" />
<node pkg="turtlesim" type="turtlesim_node" name="t1" ns="hello"/>
launch>
在 node 标签中,name 属性是必须的,ns 可选。
rosnode list
查看节点信息,显示结果:
/t1
/t2
/t1/hello
如果自定义节点实现,那么可以更灵活的设置命名空间与重映射实现。
其实在之前的课程中,已经有过相关实践了,见【ROS】—— ROS通信机制进阶(七)中的初始化函数.这里再将其简单的罗列:
C++
核心代码:ros::init(argc,argv,"zhangsan",ros::init_options::AnonymousName);
python
核心代码:rospy.init_node("lisi",anonymous=True)
核心代码
std::map map;
map["__ns"] = "xxxx";
ros::init(map,"wangqiang");
在ROS中节点名称可能出现重名的情况,同理话题名称也可能重名。
在实际应用中,按照逻辑,有些时候可能需要将相同的话题名称设置为不同,也有可能将不同的话题名设置为相同。在ROS中给出的解决策略与节点名称重命类似,也是使用名称重映射或为名称添加前缀。根据前缀不同,有全局、相对、和私有三种类型之分。
启动乌龟节点
rosrun turtlesim turtlesim_node
启动键盘节点
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
会出现以下结果
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel
查看rostopic list
yuan@yuan-Legion-Y9000P-IAH7H:~$ rostopic list
/cmd_vel
/rosout
/rosout_agg
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose
很明显,这是由于话题名称不一致导致的.对此,我们可以将turtlesim的/cmd_vel改为/turtle1/cmd_vel,或者将teleop_twist_keyboard 的/turtle1/cmd_vel改为/cmd_vel.
# 方案1
rosrun turtlesim turtlesim_node
rosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/turtle1/cmd_vel
#方案2
rosrun turtlesim turtlesim_node /turtle1/cmd_vel:=/cmd_vel
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
remap的应用在【ROS】—— ROS运行管理 ——元功能包与launch文件(八)中的remap函数中已经讲述过,这里再简单说明一下.
launch 文件设置话题重映射语法:
实现teleop_twist_keyboard与乌龟显示节点通信方案有两种:
1.方案1
将 teleop_twist_keyboard 节点的话题设置为/turtle1/cmd_vel
<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>
2.方案2
将乌龟显示节点的话题设置为 /cmd_vel
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="t1">
<remap from="/turtle1/cmd_vel" to="/cmd_vel" />
node>
<node pkg="teleop_twist_keyboard" type="teleop_twist_keyboard.py" name="key" />
launch>
话题的名称与节点的命名空间、节点的名称是有一定关系的,话题名称大致可以分为三种类型(之间的关系可以参考下图):
演示准备:
1.初始化节点设置一个节点名称
ros::init(argc,argv,"hello")
2.设置不同类型的话题
3.启动节点时,传递一个 __ns:= xxx
4.节点启动后,使用 rostopic 查看话题信息
全局
格式:以/开头的名称,和节点名称无关
示例1
ros::Publisher pub= nh.advertise<std_msgs::String>("/chatter",1000);
结果
/chatter
/rosout
/rosout_agg
示例2
ros::Publisher pub= nh.advertise<std_msgs::String>("/yuan/chatter",1000);
结果
/rosout
/rosout_agg
/yuan/chatter
相对
格式:非/开头的名称,参考命名空间(与节点名称平级)来确定话题名称
示例1
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
xxx/chatter
示例2
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money",1000);
xxx/chatter/money
私有名称
示例1
ros::NodeHandle nh("~");
ros::Publisher pub= nh.advertise<std_msgs::String>("chatter",1000);
/topic/xxx/chatter
示例2
ros::NodeHandle nh("~");
ros::Publisher pub= nh.advertise<std_msgs::String>("yuan/chatter",1000);
/topic/xxx/yuan/chatter
示例3
PS:当使用~,而话题名称有时/开头时,那么话题名称是绝对的
ros::NodeHandle nh("~");
ros::Publisher pub= nh.advertise<std_msgs::String>("/chatter",1000);
/chatter
全局名称
格式:以/开头的名称,和节点名称无关
比如:/xxx/yyy/zzz
示例1
ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter",1000);
/chatter
示例2:
ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money",1000);
/chatter/money
相对名称
格式:非/开头的名称,参考命名空间(与节点名称平级)来确定话题名称
示例1:
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
/xxx/chatter
示例2:
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money",1000);
/xxx/chatter/money
私有名称
格式:以~开头的名称
示例1:
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
/xxx/hello/chatter
示例2:
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money",1000);
/xxx/hello/chatter/money
在ROS中节点名称话题名称可能出现重名的情况,同理参数名称也可能重名。
关于参数重名的处理,没有重映射实现,为了尽量的避免参数重名,都是使用为参数名添加前缀的方式,实现类似于话题名称,有全局、相对、和私有三种类型之分。
rosrun 在启动节点时,也可以设置参数:
语法: rosrun 包名 节点名称 _参数名:=参数值
示例1
rosrun turtlesim turtlesim_node _A:=100
rosparam list
查看节点信息,显示结果:
/turtlesim/A
/turtlesim/background_b
/turtlesim/background_g
/turtlesim/background_r
该方式在【ROS】—— ROS运行管理 ——元功能包与launch文件(八)中的param中已经讲述过,这里再简单说明一下.
以 param 标签为例,设置参数
<launch>
<param name="p1" value="100" />
<node pkg="turtlesim" type="turtlesim_node" name="t1">
<param name="p2" value="100" />
node>
launch>
rosparam list
查看节点信息,显示结果:
/p1
/t1/p1
【ROS】—— ROS通信机制——参数服务器(四)也涉及了相关参数的知识.
1. ros::param设置参数
#include "ros/ros.h"
int main(int argc, char *argv[])
{
ros::init(argc,argv,"param_name");
ros::NodeHandle nh;
/*
使用 ros::param 设置参数
*/
//全局
ros::param::set("/A",100);
//相对
ros::param::set("B",100);
//私有
ros::param::set("~C",100);
return 0;
}
rosrun rename01 param_name __ns:=param
/A
/param/B
/param/param_name/C
2. ros::NodeHandle设置参数
#include "ros/ros.h"
int main(int argc, char *argv[])
{
ros::init(argc,argv,"param_name");
ros::NodeHandle nh;
/*
使用 ros::param 设置参数
*/
//全局
ros::param::set("/A",100);
//相对
ros::param::set("B",100);
//私有
ros::param::set("~C",100);
/*
使用 ros::NodeHandle 设置参数
*/
//全局
nh.setParam("/D",1000);
//相对
nh.setParam("E",1000);
//私有
ros::NodeHandle nh_private("~");
nh_private.setParam("F",1000);
return 0;
}
rosrun rename01 param_name __ns:=param
/A
/D
/param/B
/param/E
/param/F
/param/param_name/C
/param/param_name/F
python 中关于参数设置的语法实现比 C++ 简洁一些,调用的API时 rospy.set_param,该函数中,参数1传入参数名称,参数2是传入参数值,参数1中参数名称设置时,如果以 / 开头,那么就是全局参数,如果以 ~ 开头,那么就是私有参数,既不以 / 也不以 ~ 开头,那么就是相对参数。代码示例:
rospy.set_param("/py_A",100) #全局,和命名空间以及节点名称无关
rospy.set_param("py_B",100) #相对,参考命名空间
rospy.set_param("~py_C",100) #私有,参考命名空间与节点名称
运行时,假设设置的 namespace 为 xxx,节点名称为 yyy,使用 rosparam list 查看:
/py_A
/xxx/py_B
/xxx/yyy/py_C