“既喜欢 谈何坚持!”
一、将节点压入给定默认命名空间
第五章中提到了相对名称这个概念。假如同时运行两只小乌龟,他们都属于绝对空间下,那么一定有一个会报错。此时就可以引入node的命名空间概念:
对应代码如下(对应subpose包下的use_ns.launch文件):
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim" ns="sim1" />
<node pkg="turtlesim" type="turtle_teleop_key" required="true"
name="teleop_key" launch-prefix="xterm -e" ns="sim1" />
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim" ns="sim2" />
<node pkg="subpose" type="subb" name="pose_subcriber"
output="screen" ns="sim2" />
</launch>
- ns:将节点压入默认命名空间sim1中。
运行rqt_graph可以看到如下图:
由上得知,两个流被分别压入了sim1空间和sim2空间。
- 这种变动的原因是在turtlesim_node节点创建publisher和subscriber时使用了像turtle/pose这样的相对名称,可以搭配ns进行解析,节点也是如此。(而不是/turtle/pose这种全局名称);
- 这种相对名称的做法是比较提倡的。
二、名称重映射(remapping)
基于替换的思想:每个重映射包含一个原始名称和一个新名称,每当节点使用到原始名称时,ROS就会将其替换成新名称。
I、命令行启动
原始名称:=新名称
例如运行一只小乌龟,把姿态数据发布到/tim而不是/turtle1/pose。(源码是发布到后者,不改动源码)
rosrun turtlesim turtlesim_node /turtle1/pose:=tim
此时查看rostopic列表,会发现原来pose信息已经“消失”,取而代之的是/tim,如下:
II、launch文件启动
<remap from="原始名称" to="新名称" />
- 如果作为launch元素的子元素,表示后续所有节点都进行重映射;
- 一般作为一个节点元素的子元素出现,如下
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim" >
<remap from="turtle1/pose" to="tim" />
</node>
效果与上述命令行实现一样。
在ROS应用任何重映射之前,所有名称都要先解析为全剧名称,包括重映射中的原始名称和新名称。因此可以在名称解析完毕后,所有重映射都可以查找某个节点的原始名称。
III、反向海龟的例子
创建一个新的包reverse,并对应添加依赖。修改配置:
C++源码如下:
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
ros::Publisher *pubPtr ;
void commandVelReceived(const geometry_msgs::Twist& msgIn)
{
geometry_msgs::Twist msgOut ;
msgOut.linear.x = -msgIn.linear.x ;
msgOut.angular.z = - msgIn.angular.z ;
pubPtr->publish(msgOut) ;
return ;
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "reverse_vel") ;
ros::NodeHandle nh ;
pubPtr = new ros::Publisher(nh.advertise<geometry_msgs::Twist>("turtle1/cmd_vel_reversed", 1000)) ;
ros::Subscriber sub = nh.subscribe("turtle1/cmd_vel", 1000, &commandVelReceived) ;
ros::spin() ;
delete pubPtr ;
return 0 ;
}
对应的launch文件源码如下:
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim" >
<remap from="turtle1/cmd_vel" to="turtle1/cmd_vel_reversed" />
</node>
<node pkg="turtlesim" type="turtle_teleop_key" required="true"
name="teleop_key" launch-prefix="xterm -e" />
<node pkg="reverse" type="reverse_vel" name="reverse_node" />
</launch>
运行后的rqt_graph如下:
它的作用原理是:
- C++源码:我们编写的节点,订阅键盘输入的消息,然后更改数据内容,再向我们自定义的平台发布消息;
- Launch文件:对小海龟节点进行更改–>>重映射将/tultle1/cmd_vel映射为/turtle1/cmd_vel_reversed上,这样小海龟原本订阅的节点就订阅到了我们新的节点上。
三、包含(include)其他文件
如果想包含其他启动文件内容,就可以用到include.
话不多说直接上源码(reverse包根目录下的include_launch.launch):
<launch>
<include file="$(find subpose)/use_ns.launch" />
</launch>
其实就是执行一遍roslaunch subpose use_ns.launch文件。
<include file="launch文件的完整路径">
即可包含,它的简化版如下:<include file="$(launch文件所在包名)/launch文件名.launch">
该操作同样支持压入命名空间属性。这种应该是普遍的,因为程序要求松耦合。
四、启动参数(launch arguments)
功能有点像可执行程序中的局部变量。通过设置参数来描述节点在不同会话中运行时可能需要改变的一小部分,从而避免代码重复。
在ROS中arg和prarmeter是不同的。
I、arg和parameter
- prarameter是运行中的ROS使用的数值,储存在参数服务器中,可以被节点通过ros::param::get直接获取
- arg只有在启动文件中才有意义,它们的值是不能被节点直接获取的。
II、对arg的操作
- 声明参数(定义一个参数)
<arg name="参数名">
不是必须的,但是能使读者清楚启动文件需要哪些参数。
- 参数赋值
先来看看命令行中的参数赋值吧:
roslaunch 包名 launch文件 参数名:=参数值
同理在launch文件中:
<arg name="参数名" default="参数值" />
<arg name="参数名" value="参数值" />
命令行参数赋值可以覆盖默认值default,但是不能覆盖value
- 获取参数值
一旦参数值被声明,就可以在需要使用该值的地方使用这句来使用了:
$(arg 参数名)
在每个地方,ROS都将该参数名替换成参数值。
- 向include的启动文件中发送参数值
arg像局部变量,仅仅定义在当前launch文件中,而不能被包含的启动文件“继承”。解决方案如下(在include元素中,输入参数,且该处需要value属性):
<include file="launch文件路径">
<arg name="参数名" value="参数值" />
...
</include>
五、组(group)元素
为了更方便的管理节点,引入group元素,有如下两个功能:
- 将若干节点放入同一命名空间内:
<group ns="命名空间">
...
...
...
</group>
组内的所有节点都从给定的默认命名空间启动。如果元素已有默认命名空间,则和组元素的命名空间嵌套。
2. 组可以有条件的使能或禁用一个节点。
<group if="0 or 1">
...
</group>
- 如果if属性的值为1,则标签内元素被正常包含,否则忽略。
或者有一相反的,但是作用类似:
<group unless="1 or 0">
...
</group>
结合参数赋值使用。不能为组元素设置output=“screen”。
六、展望
下一讲–参数。