ROS机器人操作系统 基础知识 节点 话题 消息 服务 行动 参数 日志 TF变换 目标结构 启动文件

ROS机器人操作系统 基础知识 节点 话题 消息 服务 行动 参数 日志 TF变换 目标结构 启动文件

1. 安装

官方安装指北

博文github

a 添加 sources.list

 sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'

b 添加 keys

 sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116

c 安装ros

 sudo apt-get update
 sudo apt-get install ros-indigo-desktop-full

d 初始化 rosdep

sudo rosdep init
rosdep update

e 环境变量设置

注意以后如果找不到 roscore啥的情况 重新设置一下环境变量
一劳永逸
echo "source /opt/ros/indigo/setup.bash" >> ~/.bashrc
source ~/.bashrc

单次
source /opt/ros/indigo/setup.bash

f 安装软件包 rosinstall

它可以方便让你通过一条命令就可以给某个ROS软件包下载很多源码树。 
sudo apt-get install python-rosinstall

g 查找错误

roswtf

2、常用快捷命令

a 查看Lniux版本

lsb_release  -a
 查看内核版本    cat /proc/version       uname  -a

b 查看ros版本

roscore

c 查看环境变量

printenv | grep ROS

export | grep ROS   
导出所有环境变量 查找 包含ROS的

d 文件目录

ll   ls -al 

e 删除文件夹

rm -rf 文件夹/

f 返回上级

cd ..

g 进入文件

cd ~/文件夹/
    创建文件夹  mkdir -p ~/catkin_ws/src    /home下创建/catkin_ws/src
		      -p  parents  如果没有父目录/catkin_ws 则自动创建

h 查找软件包package

rospack find roscpp
  rospy
  rosmsg
  rosnode
  rostopic
  rosservice
  std_msgs
  sensor_msgs

rospack find turtlesim
/opt/ros/indigo/share/turtlesim$ ls
cd images/
eog box-turtle.png     #使用 eye of  gnome图像查看器查看图片

查找支持的命令
package -h

i 创建功能包

 # catkin_create_pkg 包名字 依赖1 依赖2 依赖3 ...
 catkin_create_pkg beginner_tutorials std_msgs rospy roscpp

j 查找依赖关系

rospack depends1 beginner_tutorials    //直接依赖关系  少
rospack depends beginner_tutorials     //间接依赖关系   多

rospack depends1 beginner_tutorials   ->  std_msgs rospy roscpp

k 进入文件路径下

roscd只能切换到那些路径已经包含在ROS_PACKAGE_PATH环境变量中的软件包
roscd  roscpp
pwd   查看当前路径
roscd turtlesim/
roscd log  # 切换到ROS保存日记文件的目录下

l 查看路径下包含的文件

rosls
ls  
rosls turtlesim /images
ls 文件

m 打开文件

cat package.xml    cat  文件名

n 编辑文件
gedit package.xml gedit 文件名

o 运行程序前要运行的命令
roscore

p 列出活跃的节点

rosnode list

清理已经失活的节点
rosnode cleanup
>>>>   Unable to contact the following nodes:
   .....
 Cleanup will purge all information about these nodes from the master.
 Please type y or n to continue:
 y
 Unregistering   ...
 done

q 查询节点的信息

rosnode info 节点名字
rosnode info /rosout
rosnode info /turtlesim

------>>>>>
Node [/turtlesim]
Publications:   # 发布的话题
 * /turtle1/color_sensor [turtlesim/Color]
 * /rosout [rosgraph_msgs/Log]
 * /turtle1/pose [turtlesim/Pose]

Subscriptions:  # 订阅的话题
 * /turtle1/cmd_vel [unknown type]

Services:       # 节点提供的服务
 * /turtle1/teleport_absolute
 * /turtlesim/get_loggers
 * /turtlesim/set_logger_level
 * /reset
 * /spawn
 * /clear
 * /turtle1/set_pen
 * /turtle1/teleport_relative
 * /kill
    
contacting node http://ewenwan-Lenovo-G480:41932/ ...
Pid: 2907
Connections:
 * topic: /rosout
    * to: /rosout
    * direction: outbound
    * transport: TCPROS

r 运行一个新的节点

rosrun 包名 节点名

rosrun turtlesim turtlesim_node    //小乌龟

rosrun turtlesim turtlesim_node __name:=my_turtle   //重新命名节点

rosrun package-name executable-name
rosrun 包名字       可执行文件的名字  (节点的编译文件)

s 清除rosnode列表 (更新rosnode list)

rosnode cleanup
用于终止节点时,节点未从节点节点管理器中清除,而更新节点管理器列表

rosnode kill node-name
rosnode kill turtlesim_node 
还可以用 Ctrl-C 命令终止节点。但使用这种方法时可能不
会在节点管理器中注销该节点,因此会导致已终止的节点
仍然在 rosnode 列表中。

    rosnode ping my_turtle      //节点活跃性测试

3 、键盘控制小乌龟

roscore                               master 节点

rosrun turtlesim turtlesim_node       小乌龟 节点

rosrun turtlesim turtle_teleop_key    键盘遥控节点 
                                      teleop 是 teleoperation(遥控操作)

turtlesim_node节点和turtle_teleop_key节点之间是通过一个ROS话题来互相通信的。
urtle_teleop_key在一个话题上发布按键输入消息,而turtlesim则订阅该话题以接收该消息。


rosnode list       #查看运行的节点
	/ rosout   #rosout 节点是一个特殊的节点,通过 roscore 自动启动
		       #其作用有点类似于控制台程序中使用的标准输出(即 std:: cout)。
		       #/rosout前面的反斜杠“/”表明该节点名称属于全局命名空间。
	/ teleop_turtle  #节点名并不一定与对应可执行文件名称相同
	/ turtlesim

更改节点名称    用__name 指定节点名称
rosrun package-name executable-name __name:=node-name

ROS节点之间进行通信所利用的最重要的机制就是消息传递。
在ROS中,消息有组织地存放在话题里。

消息传递的理念是:
当一个节点想要分享信息时,它就会发布(publish)消息到对应的
一个或者多个话题;

当一个节点想要接收信息时,它就会订阅(subscribe)它所需要的一个或者多个话题。
ROS节点管理器负责确保发布节点和订阅节点能找到对方;

而且消息是直接地从发布节传递到订阅节点,中间并不经过节点管理器转交。

4、查看系统运行情况 可视化信息窗口

a. 节点关系拓扑

    rosrun rqt_grap rqt_grap
rqt_graph 能够创建一个显示当前系统运行情况的动态图形  Graphical user interface

r 代表 ROS,qt 指的是用来实现这个可视化程序的 Qt 图形界面(GUI)工具包。
在该图中, 椭圆形表示节点, 有向边表示其两端节点间的发布-订阅关系。
计算图告诉我们, /teleop_turtle 节点向话题/turtle1/cmd_vel 发布消息,
而/turtlesim 节点订阅了这些消息(“cmd_vel”
是“command velocity”  命令的速度的缩写。)。

b. 话题数据可视化查看

    rosrun rqt_plot rqt_plot
rqt_plot  可以实时显示一个发布到某个话题上的数据变化图形

c. 系统问题检查

roswtf    what  the fuck  搞什么呀  fault 错误
这条命令会进行全面而深入的检测,包括检测你的环境变量、
安装的文件以及运行的节点。例如,roswtf 将会检测在安装过程
中 rosdep 初始化是否完成,任何节点是否出现了意外的挂起或者
终止,以及活跃的节点是否正确地和其他节点相连接等。

5、话题 消息

a ROS话题的信息查询

rostopic

rostopic -h

rostopic list
rostopic list -h  //查看 help

rostopic list -v    //查看全部话题消息类型。。。
rostopic list -p    //发布的消息
rostopic list -s    //订阅的消息

rostopic hz topic-name  #测量消息发布频率   输出每秒发布的消息数量
rostopic bw topic-name  #测量消息占用宽带 Kb/s 输出每秒发布消息所占的字节量

即使你一点都不关心这个特定的频率,但是这些命令对调
试很有帮助,因为它们提供了一种简单的方法来验证这些
消息确实有规律地在向这些特定的话题发布

b 查看话题上发布的数据

rostopic echo [topic]       持续查看更新话题上发布的数据
rostopic echo /turtle1/cmd_vel

查询话题  信息参数
rostopic info topic-name  
# 利用 rostopic info 命令, 可以获取更多关于话题信息类型等参数:

rostopic info /turtle1/color_sensor
输出中最重要的部分是第一行,给出了该话题的消息类型。
因此 , 在/turtle1/color_sensor 话题中发布 订阅的消 息类 型是
/turtlesim/Color。Type 在文本输出中表示数据类型

话题上发布消息类型的命名 :   
消息类型名总会包含一个斜杠,斜杠前面的名字是包含它的包:
package-name/type-name
1 避免命名冲突。
2 依赖关系看上去更明朗
3 有助于猜测它的含义

    查看消息数据类型:
rosmsg show  /turtlesim/Color    
uint8 r
uint8 g
uint8 b

上述输出的格式是域(field)的列表,每行一个元素。每一
个域由基本数据类型(例如 int8、bool、或者 string)以及域名称
定义。上述输出告诉我们 turtlesim/Color 包含三个无符号 8 位整型变量 r,g 和 b。
任何话题的消息只要是 turtlesim/Color 类型,
都由上述三个域的值定义。

rostopic info /turtle1/cmd_vel  话题上发布的消息类型
Type: geometry_msgs/Twist

rosmsg show geometry_msgs/Twist 消息 的具体数据格式
geometry_msgs/Vector3 linear    # 线速度  沿x y z轴的直线数度
float64 x
float64 y
float64 z
geometry_msgs/Vector3 angular   # 角速度  沿 x y z轴的旋转叫速度 俯仰 偏行 滚转
float64 x
float64 y
float64 z

该 例 中 , linear 和 angular 都 是 复 合 域 , 其 数 据 类 型 是
geometry_msgs/Vector3。缩进格式表示命名为 x,y 和 z 的域是对
应的上级两个域之一的成员。也就是说,geometry_msgs/Twist 消
息包含六个成员,并且以两个向量的形式组织,分别为 linear 和angular。
其中每个数值都是基本数据类型 float64,即每个数值都是 64 位浮点型数据。

rosmsg show 命令在显示消息类型时自动向下展
开复合域直到基本类型为止,同时使用缩进的形式来展示这种嵌套结构

c 查看所发布话题的消息类型

rostopic type [topic]

rostopic type /turtle1/cmd_vel

rosmsg show 消息类型
rosmsg show message-type-name

rosmsg show geometry_msgs/Twist

rostopic type /turtle1/cmd_vel | rosmsg show   管道命令

d 把数据发布到当前某个正在广播的话题上

大多数时候,发布消息的工作是由特定的程
序完成的 * 。但是,你会发现有时候手动发布消息是很实用的。
要实现该功能,利用rostopic命令行工具

rostopic pub –r rate-in-hz topic-name message-type message-content
	          频率       话题名字    消息类型    消息的参数值

此处展示的命令 利用了 -r 来指定话题以频率 rate 模式发布消息,
即以一定的时间周期发布消息。

这条命令同样支持一次性发布的模式(-1“虚线后为数字 1”)和
特别的锁存模式(-l“虚线后为字母 L 的小写”),
锁存模式虽然也只是发布一次消息,
但是会确保该话题的新订阅者也会收到消息。实际上,锁存模式是默认的模式。

同样也可以从文件中读取消息(利用-f 参数)
或者从标准的输入(把-f 参数和消息的内容从命令中都删掉)中读取。
两种情况下,输入应该符合 rostopic echo 的输出格式。

1次/1秒频率发布   位移线速度为2 直线行走 前进
rostopic pub -r 1 /turtle1/cmd_vel geometry_msgs/Twist ’[2,0,0]’ ’[0,0,0]’

1次/1秒频率发布   位移线速度为2 直线行走 倒退
rostopic pub -r 1 /turtle1/cmd_vel geometry_msgs/Twist ’[-2,0,0]’ ’[0,0,0]’

1次/1秒频率发布   角速度为2 逆时针旋转  逆正顺负
rostopic pub -r 1 /turtle1/cmd_vel geometry_msgs/Twist ’[0,0,0]’ ’[0,0,2]’

1次/1秒频率发布   角速度为2 顺时针旋转
rostopic pub -r 1 /turtle1/cmd_vel geometry_msgs/Twist ’[0,0,0]’ ’[0,0,-2]’

事实上,上述两个例子中的非零域——位移 linear.x  ’[-2,0,0]’ 以及角
度 angular.z   ’[0,0,2]’  ——是 turtlesim 重点关注的两个变量,
而这两个变量是 geometry_msgs/Twist 消息中仅有的两个变量。
因为其他四个变量表示的运动对于该二维仿真来说是不可能发生的,
所以 turtlesim 程序忽略了这些变量。

rostopic pub [topic] [msg_type] [args]
rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'        //转一圈

rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'        //一直转

话题通信的 多对多机制:
rosrun turtlesim turtlesim_node __name:=A
rosrun turtlesim turtlesim_node __name:=B
rosrun turtlesim turtle_teleop_key __name:=C
rosrun turtlesim turtle_teleop_key __name:=D
遥控节点之一发送按键命令时,两个海龟都会有同样的运动来响应

节点之间的松耦合关系:
“生产”消息的程序(例如 turtle_teleop_key)只管发布该消息,
而不用关心该消息是如何被“消费”的。
“消费”消息的程序(例如 turtlesim_node)只管订阅该话题或
者它所需要消息的所有话题,而不用关心这些消息数据是如何“生产”的。

6、节点服务

此外,ROS 为更加直接的一对一通信提供了一种称为服务(services)的机制。
第二种通信机制更为少见,但是也有其应用价值.

服务(services)是节点之间通讯的另一种方式。
服务允许节点发送请求(request) 并获得一个响应(response)。
rosservice可以很轻松的使用 ROS 客户端/服务器框架提供的服务。
rosservice提供了很多可以在topic上使用的命令。

使用方法:
列出所有服务,
	rosservice list         输出可用服务的信息
	可以获取目前活跃的所有服务.
	rosservice list 命令的输出是所有服务的全局名称。

	rosservice call         调用带参数的服务
	rosservice type         输出服务类型
	rosservice find         依据类型寻找服务find services by service type
	rosservice uri          输出服务的ROSRPC uri

服务调用(service calls)。服务调用与消息的区别主要体现在两个方面。
1 服务调用是双向的,一个节点给另一个节点发送信息并等待
  响应,因此信息流是双向的。作为对比,当消息发布后,并
  没有响应的概念,甚至不能保证系统内有节点订阅了这些消息。

2 服务调用实现的是一对一通信。每一个服务由一个节点发起,
  对这个服务的响应返回同一个节点。另一方面,每一个消息
  都和一个话题相关,这个话题可能有很多的发布者和订阅者。

客户端(client)节点发送一些称为请求(request)
的数据到一个服务器(server)节点,并且等待回应。
服务器节点接收到请求后,采取一些行动(计算、配置软件或硬件、改变
自身行为等),然后发送一些称为响应(response)的数据给客户端节点。

[client] ------> request-------->[server]--------->response------->[client]
客户端         发送请求           服务端            响应              客户端
请求和响应数据携带的特定内容由服务数据类型(servicedata type)来决定,
它与决定消息内容的消息类型是类似的

同消息类型一样,服务数据类型也是由一系列域构成的。
唯一的区别就在于服务数据类型分为两部分,分别表示
请求  (客户端节点提供给服务器节点)和
响应  (服务其节点反馈给客户端节点)。

服务的类型
1) 特定节点的服务。一些服务,例如上表中 
  get_loggers 和 set_logger_level(roscore节点提供)
  是关于节点日志消息的服务,是用来从特定的节点获取或者向其传递信息的。
  这类服务通常将节点名用作命名空间来防止命名冲突,
  并且允许节点通过 私 有 名 称 来 提 供 服 务 , 
  例 如 ~get_loggers 或 者~set_logger_level 
  (参照 4.5 了解 loggers 和 logger level 的详情)。
  
2) 通用节点服务。其他服务表示更一般的不针对某些特定节点的服务。
  例如,名 为 /spawn 的 服 务 用 于 生 成 一 个 新 的 仿 真 海 龟 , 是 由
  turtlesim 节点提供的。但是在不同的系统中,这个服务完全可能由其他节点提供;
  当我们调用/spawn 时,我们只关心是
  否有一个新的海龟出现,而不关心具体哪个节点起作用这种细 节 。 
  上 表 列 出 的 所 有 服 务 , 除 了 get_loggers 和
  set_logger_level,都可以归入此类。这类服务都有特定的名称来描述
  它们的功能,却不会涉及任何特定节点。

7、查看服务类型和服务的参数信息

a. 查看节点提供的服务

查看某个节点的服务类型 要查看一个特定节点提供的服务,使用 rosnode info 命令:
  rosnode info node-name

下面的例子给出了这条命令在 turtlesim 节点上输出结果的相应部分:
roscore                               master 节点
rosrun turtlesim turtlesim_node       小乌龟 节点
rosnode info /turtlesim
>>>>>>>>>

Services:
*/turtle1/teleport_absolute
*/turtlesim/get_loggers
*/turtlesim/set_logger_level
*/reset
*/spawn
*/clear
*/turtle1/set_pen
*/turtle1/teleport_relative
*/kill

b. 查看服务类型 与 其数据详情

查找提供服务的节点 要完成这个反向查询,即查找提供给定服务的节点,可以使用这条指令:
rosservice node service-name

rosservice type service-name     服务数据类型
rosservice type /clear    -------->>>>  std_srvs/Empty

rosservice type  /spawn   -------->>>> turtlesim/Spawn 

   rosservice info service-name  参数信息
   rosservice info /spawn
   --->>
    -------->>>>>

Node: /turtlesim
URI: rosrpc://ewenwan-Lenovo-G480:33246
Type: turtlesim/Spawn                      #服务的数据类型
Args: x y theta name

turtlesim + Spawn ⇒ turtlesim/Spawn
功能包名     类型名    服务数据类型

查看服务数据类型 详情
当服务的数据类型已知时,我们可以使用rossrv 指令来获得此服务数据类型的详情:
rossrv show service-data-type-name
      查看服务数据类型
      rossrv  同   rostopic 的消息类型查看  rosmsg show 一样

      rossrv show service-data-type-name    

      rossrv show turtlesim/Spawn

      rosservice type  /spawn | rossrv show   管道命令
---->>>>>>
float32 x    #短横线(---)之前的数据是请求项,这是客户节点发送到服务节点的信息。
float32 y        #request    新生成 小海龟的位置   x,y
float32 theta    朝向角度
string name      名字
---
string name     #response
#短横线之后的所有字段是响应项,或者说是服务节点完成请求后发送回请求节点的信息

		     Topics           Services
active things        rostopic         rosservice 查询话题/服务用   
data types           rosmsg           rossrv     查询消息/服务数据类型用

8、调用服务

a 从命令行查看和调用服务

rosservice call service-name request-content
rosservice call clear             //该服务不需要输入参数

rosservice call /spawn 2 2 0.2 my_turtle  
//需要参数的服务   x y 朝向角度(弧度制)  名字    (命名空间)

rosservice call /spawn  3 3 0 Mikey2     位置(3,3) 朝向0
rosservice call /spawn 5 5 1.57 Mikey3   位置(5,5) 朝向90度
rosservice call /spawn 5 5 3.14 Mikey4   位置(7,7) 朝向180度
rosservice call /spawn 5 5 6.28 Mikey4   位置(11,11) 朝向360度    最上角

响应应该是: name: Mikey2...   #命名空间


 这些新的海龟有它自己的资源集,
 包括 cmd_vel、pose、color_sensor 话题、
 set_pen 服务、
 teleport_absolute 服务、
 teleport_relative 服务。
 在本例中,这些新的资源在一个名为 Mikey 的命名空间中。
 这些资源在常用的 turtle1 命名空间之外,
 也符合其他节点想要独立控制这些海龟的需求。
 这一点很好地阐明了命名空间可以有效地阻止命名冲突。


   查看需要参数的服务 的参数
   rosservice type spawn | rossrv show

b 程序调用服务

  服务  之 client 客户端 编写
  1) 声明请求 和 响应的类型   
     #include <package_name/type_name.h> // 包名/服务类型名

     就像消息类型一样(参照 3.3.1 节),每一种服务数据类型都对应一个我们必须包含的相关 C++头文件
     #include <turtlesim/Spawn.h>
     来包含名为 turtlesim::Spawn 类的定义,
     这个类定义了我们想要调用的服务的数据类型,包括全部请求 和 响应字段。

  2)创建客户端对象
      在按照节点要求初始化后(通过调用 ros::init 和创建一个 NodeHandle 对象),
      我们的程序必须创建一个类型为
      ros::ServiceClient 的对象,实际上是这个对象完成服务调用。
      按照如下声明一个 ros::ServiceClient 对象:

      ros::ServiceClient client = node_handle.serviceClient<service_type>(service_name);

      这条指令有三个重要部分。
	 a)  node_handle  是常用的 ros::NodeHandle 对象,这里我们将调用它的 serviceClient 方法。
	 b)  service_type 是我们在上面头文件中定义的服务对象的数据类型,
	                   在本例中,类型为 turtlesim::Spawn。
	 c)  service_name 是一个字符串, 说明了我们想要调用的服务名称。
	      再次强调, 这应当是一个相对名称, 虽然也可以声明为全局名称。
	      例子中使用的是相对名称“spawn”。

      一般来讲,创建这个对象的过程并不占用多少计算量,
      因为除了存放之后想调用的服务详情外,并不用做太多工作。
      值得注意的是,与创建类似的 ros::Publisher 对象相比,
      创建 ros::ServiceClient 对象不需要队列大小。
      之所以会造成这种差异,是因为服务调用 直到响应抵达后 才会返回。
      由于客户端要等待服务调用完成,
      因此没有必要维持一个 发送服务调用 的 队列。

   3) 创建请求和响应对象
      我们上面包含的头文件中分别定义了请求和响应的类, 
      命名为 Request 和 Response。
      这些类必须通过功能包名称和服务类型来引用,
  
  如下所示:
// 调用 创建新的 小乌龟的 服务=============
#include <ros/ros.h>
#include <turtlesim/Spawn.h>
int main( int argc , char ** argv )
{
    ros::init ( argc , argv , "spawn_turtle") ; // ros初始化
    ros::NodeHandle nh ;// 节点句柄
    // 创建服务客户端(调用服务)
    ros::ServiceClient spawnClient = nh.serviceClient <turtlesim::Spawn>("spawn") ;
    //Rros:: ServiceClient client =node_handle.advertise<service_type>(service_name, true);
    // 可以创建一个持续的服务客户端 不推荐
    
    // Create the request and response objects.
    turtlesim::Spawn::Request req ;     // 请求
    turtlesim::Spawn::Response resp ;   /// 响应
    
    // Fill in the request data members. 请求需要传递的一些参数
    req.x = 2; // 生成的位置
    req.y = 3;
    req.theta = M_PI/2; // 角度朝向
    req.name = "my_spa_turtle" ;// 名字
    
    // 调用服务,等待并获取响应
    bool success = spawnClient.call( req , resp ) ;
    //调用服务 一旦拥有了一个 ServiceClient、一个完整的 Request 以及 Response,我们就可以调用服务了
    //这个方法实际上完成了定位服务器节点、传输请求数据、等待响应和存储响应数据等一系列工作。
    //这个调用方法返回一个布尔值来表明服务调用是否成功完成。
    //若调用失败通常意味着另一只有着被请求名称的海龟已经存在。

    // Check for success and use the response .
    if ( success ) 
    {
        ROS_INFO_STREAM("Spawned a turtle named: "
        << resp.name) ;
     } 
    else 
    {
        ROS_ERROR_STREAM("Failed  to  spawn.") ;
        //当服务调用失败的时候,可以调用 ROS_ERROR_STREAM 输出错误信息。
     }  
}
在默认情况下,查找和连接服务器节点的过程是在调用方法内部发生的。
这个连接 仅在 此次服务调用期间有效,在调用返回后将被关闭。
ROS 也支持持续服务客户端的概念, ros::ServiceClient 
构造函数建立与服务器节点的连接,然后可以被这个客户端对象的所有后续调用重用。

通过传递 true 值给构造函数的第二个参数,
可以创建一个持续的服务客户端(在前面的例子中使用了缺省值 false):

Ros:: ServiceClient client =node_handle.advertise<service_type>(service_name, true);

在线文档中对持续客户端的应用是轻微抵制的 ,因为这么做并不能提升多少系统性能,作者的非正式实验表明,
性能只提升了约 10%,然而其代价是导致系统在重启和修改服务器节点时的鲁棒性变差。

!!!!!!!!!!!!!!!!!!!!!
声明依赖
编译服务cpp需要 修改 CMakeLists.txt 和清单文件 packag.xml
为 了 编 译 例 子 中 的 程 序 , 我 们 必 须 保 证

CMakeLists.txt 中的 find_package 行涉及了 turtlesim 功能包,如下所示:
find_package(catkin REQUIRED COMPONENTS roscpp turtlesim)

在 package.xml 中,我们应当确保 
build_depend 和 
run_depend
元素中存在相同名称的包,即:
<build_depend>turtlesim</build_depend>
<run_depend>turtlesim</run_depend>
完成这些修改后,多次使用的 catkin_make 命令应该就能完
成程序的编译了。

c 程序创建服务器

示例提供名为 toggle_forward 服务,同时具备控制 turtlesim 机器人运动的功能,
每次服务调用时将在前进和转向之间转换。
可以看到,服务器端的代码 与 订阅话题的代码非常相似。
除了名称上的区别,
我们必须创建一个 ros::ServiceServer 来代替ros::Subscriber,

唯一的区别在于服务器端可以通过一个 响应对象 和一个表明 成功与否 的布尔变量给客户端回传数据。

编写服务的回调函数 如同订阅一样,节点提供的每一个服务必须关联一个回调函数,服务的回调函数原型如下:
bool function_name(
package_name::service_type::Request &req), //请求
package_name::service_type::Response &resp)// 响应
) 
{
...
}
节点每次接收到一个服务请求,ROS 就执行一次回调函数。
参数 Request 中包含了来自于客户端的数据。
回调函数的工作是给 Response 对象的数据成员赋值。 
Request 和 Response 数据类型与上面在客户端使用的一致,
因此需要相同的头文件和相同的包依赖关系来编译。
回调函数返回 true 表明成功,返回 false 则表示失败。

在该例中, Request 和 Response 都使用了 std_srvs/Empty (空字符串)
作为其数据类型,因此无需针对这两个对象做任何数据处理。
回调函数的唯一工作是切换一个称为 forward 的全局布尔变量,用于管理在主函数中发布的速度消息。
 // 提供一个 切换旋转与前进的 服务=====================
  #include <ros/ros.h>
  #include <std_srvs/Empty.h>      // 服务数据类型           
  #include <geometry_msgs/Twist.h> // 发布小乌龟速度消息类型  
 
  bool forward = true ;
  bool toggleForward (
                      std_srvs::Empty::Request &req ,
                      std_srvs::Empty::Response &resp ) {
     forward = !forward ; //改变 前进 旋转 标志 在主循环中判断
     ROS_INFO_STREAM("Now sending " << ( forward ?"forward" : " rotate ") << "  commands.") ;
     return true ;
   }
  
   int main( int argc , char** argv ) 
   {
   ros::init ( argc , argv , "change_forward_rotate") ;// ros初始化
   ros::NodeHandle nh ;// 节点句柄
  
   // 服务客户端,被调用者
   ros::ServiceServer server = nh.advertiseService ("change_forward_rotate" , &toggleForward ) ;
  
   // Publish commands, using the latest value for forward ,
   // until the node shuts down.
   // 话题发布速度命令
   ros::Publisher pub = nh.advertise <geometry_msgs::Twist>("turtle1/cmd_vel" , 1000) ;
   ros::Rate rate (2) ;
   while ( ros::ok () ) 
   {
	   geometry_msgs::Twist msg ;
	   msg.linear.x = forward ? 1.0 : 0.0 ;//前进
	   msg.angular.z = forward ? 0.0 : 1.0 ;//旋转
	   pub.publish (msg) ;
	   ros::spinOnce () ;
	   rate.sleep () ;
    }
 }
rosrun agitros changeForwardRotate    运行节点
rosservice list  查看是否出现相应的服务
rosservice call /change_forward_rotate   调用相应的服务


由于在私有名称的解析上存在一些理解上的歧义,
ros::NodeHandle::advertiseService
拒绝接受私有名称(即那些以~开始的名称)。
这种约束的解决方案是基于以下事实—一种目前我们还没用使用过的节点创建方法,

即可以利用节点自身的缺省命名空间来创建 ros::NodeHandle 对象。
举个例子,我们可以按照以下方式来创建
ros::NodeHandle:
ros::NodeHandle nhPrivate("~");

这种情况下,发送给这个节点句柄的任意局部名称的缺省命名空间同节点名称一致。

特别指出的是,这意味着如果我们用这个句柄和相对名称来发布一个服务,效果将与使用私有名称相同。

例如在一个名为/foo/bar 的节点,像如下广播一个名为/foo/bar/baz 的服务:

ros::ServiceServer server =nhPrivate.advertiseService("baz", Callback);

如果句柄愿意接受私有名称的话,这段代码的效果与使用通常的 NodeHandle 广播名为~baz 的服务效果相同。


复制服务:
roscp [package_name] [file_to_copy_path] [copy_path]
roscp rospy_tutorials Add

9、参数服务器

除 了 前 面 介 绍 过 的 消 息 传 递 , ROS 还 提 供 另 一 种 参 数
( parameters )机制用于获取节点的信息。其主要思想是使用集中
参数服务器( parameter server )维护一个变量集的值,包括整数、
浮点数、字符串以及其他数据类型,每一个变量用一个较短的字
符串标识 1,2 。由于允许节点主动查询其感兴趣的参数的值,它们
适用于配置那些不会随时间频繁变更的信息。

 使得我们能够存储并操作ROS 参数服务器(Parameter Server)上的数据。
 参数服务器能够存储整型、浮点、布尔、字符串、字典和列表等数据类型。
 rosparam使用YAML标记语言的语法。一般而言,YAML的表述很自然:
 1 是整型, 1.0 是浮点型, one是字符串, true是布尔, 
 [1, 2, 3]是整型列表, {a: b, c: d}是字典. 
 rosparam有很多指令可以用来操作参数,

a 查看参数列表

rosparam  list   仅运行 roscore
>>>>                         
/rosdistro         ros版本
/roslaunch/uris/host_ewenwan_lenovo_g480__40299
/rosversion
/run_id

在当前版本的 ROS 中,参数服务器是节点管理器的一部分,
因此,它总是通过 roscore 或者 roslaunch 自动启动。在所
有情况下,参数服务器都能在后台正常工作,因此没有必
要去花心思考虑它。然而,需要铭记的是,所有的参数都
属于参数服务器而不是任何特定的节点。这意味着参数—
—即使是由节点创建的——在节点终止时仍将继续存在。

b 查询参数

rosparam get            获取参数  查询参数 
rosparam get parameter_name
rosparam get /rosdistro
>>> indigo

除此以外,还可以检索给定命名空间中的每一个参数的值,其命
令为:rosparam get namespace
例如,通过查询全局命名空间,我们可以一次性看到所有参数的值:
rosparam get /

c 设置参数

rosparam set           
rosparam set  parameter_name  parameter_value

该命令可以修改已有参数的值或者创建一个新的参数。例如,
以下命令可以创建一系列字符串参数,用以存储一组卡通鸭子的
颜色偏好:
rosparam set /duck_colors/huey red
rosparam set /duck_colors/dewey blue
rosparam set /duck_colors/louie green
rosparam set /duck_colors/webby pink

另外,我们可以同时设置同一命名空间中的几个参数:
rosparam set  namespace  values

这里要以 YAML 字典的形式表示参数和对应值的映射关系。
下面的例子和前面四行命令具有同样的作用:  (冒号后有空格)
rosparam set /duck_colors "huey: red
dewey: blue
louie: green
webby: pink"

查看是否设置成功
rosparam get /duck_colors/
>>>>
{dewey: blue, huey: red, louie: green, webby: pink}


需要注意的是,这种语法需要在命令中使用换行符。
当然,这并不会造成什么问题,因为第一个引号告诉 bash 命令尚
未完成。当在引号内按下回车时,终端将插入一个换行符而不是执行命令。
冒号后的空格是非常重要的,以确保 rosparam 将其作为一个/duck_colors 
命名空间内的参数集,而不是全局命名空间中的单个字符串参数 duck_colors。


在launch文件中设置:
这是所有node节点的全局 相对名称

<group ns="duck_colors">
<param name="huey" value="red" />
<param name="dewey" value="blue" />
<param name="louie" value="green" />
<param name="webby" value="pink" />
</group>

也可在各自的node节点内设置参数
<node ... >
<param name="param-name" value="param-value" />
. . .
</node>

在该结构下,参数名将被当做该节点的私有名称。
这是 ROS 命名规则的一个例外。作为节点元素的子集时,
param 元素中给出的参数名总是被当做私有名称解析,无论它们是否以~或者/开始。

从文件中读取参数:
最后,启动文件也支持 与rosparam load等价的命令,可以一次性从文件中加载多个参数:
<rosparam command="load" file="path-to-param-file" />
这里列出的参数文件通常是通过 rosparam dump 命令创建的。
<rosparam
command="load"
file="$(find package-name)/param-file"
/>
与 rosparam load 一样,这个功能有助于测试,因为它允许用户重现在过去的某个时间有效的参数。

d 存储 参数

为了以 YAML 文件的形式 存储 命名空间 中的所有参数,可以使用
rosparam dump 命令
rosparam dump filename namespace

e 加载参数文件

rosparam load           从文件读取参数
它从一个文件中读取参数,并将它们添加到参数服务器:
rosparam load filename namespace

对于这些命令,命名空间参数是可选的,默认值为全局命名空间(/)。
存储和加载的组合可以用来测试,因为它提供了一种

快捷方式获取一定时间内的参数“快照”,并且可以进行场景复现。

f 例子 turtlesim 中的参数

作为一个参数的具体例子,让我们来看看在 turtlesim 中如何使用它们的。
如果你启动 roscore 和 turtlesim_node,
然后查询rosparam 列表,会看到像下面这样的输出:

rosparam  list
>>>>
/background_b
/background_g
/background_r
/rosdistro
/roslaunch/uris/host_ewenwan_lenovo_g480__37268
/rosversion
/run_id

会多出 背景颜色的 三个分量,它们指定在 turtlesim 窗口中使用背景色,
分别为红色、绿色和蓝色通道,一个好的策略,也是演示真正的 ROS 节点
如何工作的更好案例,是 turtlesim 首先测试这些参数是否存在,当且仅当
这些参数不存在时,才指定默认的蓝色。

g 获取背景颜色值

可以使用 rosparam get 命令获取背景参数的值:
rosparam get /background_r
rosparam get /background_g
rosparam get /background_b
这些命令的返回值分别是 69、86 和 255。由于这些值是比较小的整数,让人容易联想到,
而且确实是,每个通道是一个 8 位整数,范围从 0 到 255。
因此,turtlesim 默认其背景颜色为(69,86,255),这正是我们平时所称的深蓝色。

h 设置背景颜色值

//设置为红色
rosparam set background_r 250
rosparam set background_g 0
rosparam set background_b 0

    rosparam deleat  参数名  删除参数

然而,即使我们设置完参数后,背景颜色仍然是原来的颜色。
这是为什么呢?这是因为只有当 turtlesim_node 的/clear 服务被调
用时, 它才会从参数服务器读取这些参数的值。调用这个服务可
以使用以下命令:
rosservice call /clear

对一个节点来说,可以请求参数服务器在参数更新后将新参数值发给它,
这个功能可以通过 ros::param::getCached
命令替换 ros::param::get 来实现。
然而,这种方法只是为了提高效率,并没有消除节点检查参数值的需要。

i 使用 C++获取参数

使用ROS参数的C++接口是相当简单的 :
void ros::param::set(parameter_name, input_value);
bool ros::param::get(parameter_name, output_value);

在这两种情况下,参数名是一个字符串,它可以是全局的、相对的或者是私有的。set 函数中的输入值 input_value
可以是std::string、bool、int 或 double 类型;
get 函数的输出值应该是上述某个类型的变量(通过引用传递)。

如果参数值读取成功,则 get 函数返回 true;如果出现了问题,通常表示所请求的参数还没
有被指定一个值,则该函数返回 false。为了查看函数的行为,让 我们来看看下面两个实例。

ros::param::set 功能 cpp 文件

  // This program waits for a turtlesim to start up , and
  // changes its background color .
  #include <ros/ros.h>
  #include <std_srvs/Empty.h>//服务
 
  int main( int argc , char** argv )
  {
  ros::init ( argc , argv , "set_bg_color") ;
  ros::NodeHandle nh ;

  // Wait until the clear service is available , which
  // indicates that turtlesim has started up , and has
  // set the background color parameters .
   ros::service::waitForService ("clear") ;//等待 /clear服务可用 即turtlesim 节点已经启动
 
  // Set the background color for turtlesim ,
  // overriding the default blue color .
   ros::param::set ("background_r" , 255) ;// set 设置参数
   ros::param::set ("background_g" , 255) ;
   ros::param::set ("background_b" , 0) ;
 
 // Get turtlesim to pick up the new parameter values.
 //rosservice call /clear
   ros::ServiceClient clearClient = nh.serviceClient <std_srvs::Empty>("/clear") ;
   std_srvs::Empty srv ;
   clearClient.call(srv); //更新
 }
说明了 ros::param::set 的功能,它为 turtlesim 背景颜
色三个参数赋值。本程序的代码在启动 turtlesim 节点前等待
/clear 服务调用结束,从而确保 turtlesim 不会覆盖这里设置
的值。并且通过调用/clear 服务强制 turtlesim 读取我们设置
的参数值。
 //最大线速度从参数读取 产生随机数0~1
 //
 // This program publishes random velocity commands, using
 // a maximum linear velocity read from a parameter.
 #include <ros/ros.h>
 #include <geometry_msgs/Twist.h>//速度和姿态
 #include <stdlib.h>

 int main( int argc , char** argv ) 
 {
 ros::init ( argc , argv , "publish_velocity") ;
 ros::NodeHandle nh ;
//创建发布者 发布在话题  turtle1/cmd_vel 上队列大小1000
 ros::Publisher pub = nh.advertise<geometry_msgs::Twist>( "turtle1/cmd_vel", 1000) ;
 //生成产生随机数的种子点
 srand ( time (0) ) ; //初始化随机数 种子

// Get the maximum velocity parameter .
  const std::string PARAM_NAME = "~max_vel;
  double maxVel;
  bool ok = ros::param::get (PARAM_NAME, maxVel) ;// get 读取参数
  if (!ok) {
  ROS_FATAL_STREAM("Could not get parameter:"
  << PARAM_NAME) ;
  exit (1) ;
 }

  ros::Rate rate (2) ;
  while ( ros::ok () ) 
  {
 // Create and send a random velocity command.
   geometry_msgs::Twist msg ;// 速度消息类型
   msg.linear.x = maxVel*double ( rand () )/double (RAND_MAX) ;  //线速度  为 0 到 1 之间的某个值 * maxVel
   msg.angular.z = 2*double ( rand () )/double (RAND_MAX) -1;    //角速度  弧度制 为-1 到 1 之间的某个值
   //发布消息
   pub.publish(msg) ;
  // Wait untilit's time for another iteration .
  rate.sleep () ;
   }
 }
是命令 ros::param::get 的例程。
作为原 pubvel 例程(表3.4)的拓展,这里读取私有浮点参数 max_vel 的值,
并使用这个参数值来调整随机生成线速度的大小

rosparam set /publish_velocity/max_vel 0.1

如果这个参数不存在,那么,该程序将会报错并且终止。

通过在命令行中使用类似重映射的语法为节点的私有参数赋值在技术上是可行的(但是有些混乱),
可以通过给参数名添加下划线前缀实现,如下所示:

_param-name:=param-value
这种命令行参数将被转换为 ros::param::set 的调用,通过
ros::init 将“_”替换为“~”构成私有参数的形式。
例如,我们可以使用下面的命令成功启动 pubvel_with_max 节点:

rosrun agitr pubvel_with_max _max_vel:=1
rosrun agitros pubVelWMVel _max_vel:=6

rosparam delete         删除参数

rosparam get background_g 
rosparam get /              //得到全部参数数据

rosparam dump params.yaml   //将所有的参数写入params.yaml文件

rosparam load params.yaml copy   //将yaml文件重载入新的命名空间,比如说copy空间
rosparam get copy/background_b   

10、日志操作

a 可视化日志

rqt_console

属于ROS日志框架(logging framework)的一部分,用来显示节点的输出信息。
rqt_logger_level

 允许我们修改节点运行时输出信息的日志等级(logger levels)
(包括 DEBUG、WARN、INFO和ERROR)。

b 录制消息/数据 与回放

mkdir ~/bagfiles   //建立一个用于录制的临时目录

cd ~/bagfiles      //进入

rosbag record -a   //录制所有

rosbag info <your bag file>   显示记录内容

rosbag play <your bag file>    回放 节点动态最后一个可能比较有趣的

参数选项是-r选项,它允许你通过设定一个参数来改变消息发布速率。 

rosbag play -r 2 <your bag file>   2倍速度回放

//录制指定话题数据
rosbag record -O subset /turtle1/command_velocity /turtle1/pose


设计精良的 ROS 系统的一个重要特征便是系统中信息的消费者不应该关心信息的生产者。
这种体系架构最明显的体现是 ROS主要使用的消息发布 - 订阅通信模型。
在这个模型下,不论什么时刻,只要有消息被发布,其订阅节点就应该正常工作,而不管
是哪个或是哪些节点正在发布这些消息。
本章将介绍一个体现这种灵活性的具体例子,即一种被称为rosbag 的工具。通过 rosbag,
我们能够将发布在一个或者多个话题上的消息录制到一个包文件中,然后可以回放这些消息,
重现相似的运行过程。将这两种能力结合,便形成了测试某些机器人软件的有效方式:
我们可以偶尔运行机器人,运行过程中录制关注的话题,然后多次回放与这些话题相关的消息,
同时使用处理这些数据的软件进行实验。

1) 录制与回放包文件
术语包文件(bag files)是指用于存储带时间戳的 ROS 消息的特殊格式文件。
rosbag 命令行工具可以用来录制和回放包文件。
录制包文件
要创建一个包文件,使用 rosbag 指令的如下形式: 
rosbag record -O filename.bag topic-names

如果你不指定文件名,rosbag 将基于当前的日期和时间自动生成一个。此外,还有一些命令行选项或许对使用 rosbag record
进行消息录制有用。
除了像上面一样指定具体的话题外,你还可以使用

rosbag record -a   记录当前发布的所有话题的消息。

对于本书中的种种小规模系统,完全可以其录制所有话题。然而,在许多真实的机器人系统中,这是一个令人沮丧的坏主意。
例如,大部分搭载摄像头的机器人系统中存在多个节点发布与图像相关的话题,其中的图像经历了不同阶
段的处理和不同级别的压缩。此时如果记录所有的话题,将迅速创建惊人的巨大包文件。因此,使用-a 选项前要三
思,或至少应该在录制过程中注意包文件的大小。

你可以使用 rosbag record -j 启用包文件的压缩。与其他文件压缩一样,这里也需要折衷考虑:通常较小文件的代价是更
长的读写时间。作者认为,对于包文件来说,压缩通常是个好主意。
当完成包文件录制时,使用 Ctrl-C 停止 rosbag。

2)  回放包文件 要回放一个包文件,可以使用如下命令:
rosbag play filename.bag
存储在包文件中的消息将被回放,而且回放时会保持与其原始发布时同样的顺序和时间间隔。

3)  检查文件包
rosbag info 指令可以提供某个包文件中的丰富信息:
rosbag info filename.bag

示例:正方形运动轨迹的包文件      
绘制正方形轨迹(Drawing squares) 首先,启动 roscore 和turtlesim_node 两个节点。
然后从 turtlesim 功能包中启动一个 draw_square 节点(系统自带):
录制:
mkdir bagfiles   //创建存放包文件的文件夹
cd bagfiles
roscore
rosrun turtlesim  turtlesim_node
rosrun turtlesim draw_square
//                 包文件名      话题1           话题2
rosbag record -O square.bag /turtle1/cmd_vel /turtle1/pose

^C 结束录制  并结束 draw_square 节点

回放:
rosbag play square.bag
请注意,海龟将恢复运动。这是因为rosbag创建了名为play...的节点,
且这个节点正在发布话题/turtle1/cmd_vel的消息,


始的 draw_square 和 rosbag play 把海龟发送到了不同的地方,
即使这个包文件里面有来自/ turtle1/pose 话题的数据。
这又是为什么呢?很简单,因为在这个例子中,
除 rosbag record 外没有其他节点订阅/ turtle1/pose 话题。
系统中某个或某些节点(这里是 rosbag play)发布海龟所在的位置,
和实际上海龟所在的位置是可以不同的。
本例中,包文件里面的位姿数据被忽略了。

      
    launch文件中启用包文件:

更重要的是,通过这两个可执行文件可以很容易地将包文件
作为启动文件的一部分,方法是包含适当的节点元素。例如,一
个录制节点可能是这样的:
<node
pkg="rosbag"
name="record"
type="record"
args="-O filename.bag topic-names"
/>
与此类似,一个回放节点可能如下所示:
<node
pkg="rosbag"
name="play"
type="play"
args="filename.bag"
/>

  检查文件包
  rosbag info 指令可以提供某个包文件中的丰富信息:
  rosbag info filename.bag
  
   rosbag info square.bag.active

>>>>>>
path:        square.bag.active
version:     2.0
duration:    1:18s (78s)
start:       Mar 13 2017 16:44:21.96 (1489394661.96)
end:         Mar 13 2017 16:45:40.72 (1489394740.72)
size:        888.5 KB
messages:    9821
compression: none [1/1 chunks]
types:       geometry_msgs/Twist [9f195f881246fdfa2798d1d3eebca84a]
	     turtlesim/Pose      [863b248d5016ca62ea2e895ae5265cf9]
topics:      /turtle1/cmd_vel   4898 msgs    : geometry_msgs/Twist
	     /turtle1/pose      4923 msgs    : turtlesim/Pose

11、arduino 连接

linux 管理员权限打开:

arduino idesudo /home/wyw/SoftWare/arduino-1.6.7/arduino
sudo  arduino安装路径

获取权限 :
/dev/ttyS2sudo usermod -a -G tty wyw            //wyw  用户名
sudo usermod -a -G dialout wyw


与arduino通信:
    rosrun rosserial_python serial_node.py /dev/ttyS2

12、运行tf(坐标变换)小乌龟仿真

demo安装
sudo apt-get install ros-jade-ros-tutorials ros-jade-geometry-tutorials ros-jade-rviz ros-jade-rosbash ros-jade-rqt-tf-tree

运行
roslaunch turtle_tf turtle_tf_demo.launch

查看坐标变化图
rosrun tf view_framesevince frames.pdf

查看node话题发布订阅图标
rosrun rqt_tf_tree rqt_tf_tree

展现坐标系变化
rosrun tf tf_echo [reference_frame] [target_frame]
rosrun tf tf_echo turtle1 turtle2rviz

坐标变换可视化工具
rosrun rviz rviz -d `rospack find turtle_tf`/rviz/turtle_rviz.rviz

二、 编写ROS程序

1、创建工作区和功能包

a. 创建文件夹  mkdir -p ~/catkin_ws/src    /home下创建/catkin_ws/src
   -p  parents  如果没有父目录/catkin_ws 则自动创建
b. 初始化工作空间
   cd ~/catkin_ws/src 
   catkin_init_workspace
c. 编译空的工作目录 生成 依赖库
    cd ~/catkin_ws   catkin_make

d. 创建包   
    cd ~/catkin_ws/src
    catkin_create_pkg vision_system std_msgs sensor_msgs rospy roscpp
			  包名字          依赖包

e. 仅编译单个包:
    cd ~/catkin_ws
    catkin_make --pkg package_name

f. 编译成功后  需要 source ~/catkin_ws/devel/setup.bash
    导入  新编译的环境
    否则 ros 认不出 新编译的包和节点
    或则直接把~/catkin_ws/devel/setup.bash 加入到系统启动脚本里~/.bashrc
    echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc

2、src目录

a. 目录列表
  workspace_folder/        -- WORKSPACE
 1 src/                   -- SOURCE SPACE
   1.1 CMakeLists.txt       -- 'Toplevel' CMake file, provided by catkin

   1.2 package_1/  包1  例如 beginner_tutorials
       CMakeLists.txt     -- CMakeLists.txt file for package_1  编译规则
       package.xml        -- Package manifest for package_1     提供有关程序包的元信息
       src                -- 存放 源文件 cpp   py 。。。。


       ...

   1.n package_n/  包n
       CMakeLists.txt     -- CMakeLists.txt file for package_n
       package.xml        -- Package manifest for package_n        清单文件
       src                -- 存放 源文件 cpp   py 。。。。

 2 devel/

     依赖库
 3 build/
     编译文件  



b. 清单文件编写规则  package.xml
<package>
  <!-- package name 软件包名字 -->
  <name>agitros</name>
  <!-- package version number  软件包版本-->
  <version>0.0.1</version>                                               
  <!-- package description 软件包描述信息 -->
  <description>
      The Examples from A Gentle Information to ROS package
  </description>
  <!--owner‘s email  软件包所有者邮件-->
  <maintainer email="[email protected]">ewenwan</maintainer>
  <!-- license type  软件包协议-->
  <license>TODO</license>
  <!-- <url type="website">http://wiki.ros.org/agitros</url> -->
  <!-- <author email="[email protected]">Jane Doe</author> -->
  <!-- build tools  depends -->
  <buildtool_depend>catkin</buildtool_depend> 
  
  <!-- build depends 软件包编译依赖 -->
  <build_depend>roscpp </build_depend>
  <build_depend>geometry_msgs</build_depend>      <!-- geometry  messags build depends -->
  <build_depend>turtlesim </build_depend>
  
  <!-- run depends 软件包运行依赖 -->
  <run_depend>roscpp</run_depend> 
  <run_depend>geometry_msgs</run_depend>          <!-- run depends -->
  <run_depend>turtlesim </run_depend>
  
  <!-- The export tag contains other, unspecified, tags -->
  <export>
    <!-- Other tools can request additional information be placed here -->
  </export>
  
</package>

c. 编译规则 CMakeLists.txt 编写规则

   cmake_minimum_required(VERSION 2.8.3)              #cmke编译器最低版本
   project(agitros)                                   #包名字 工程名
   find_package(catkin REQUIRED COMPONENTS roscpp)    #编译库依赖     
   catkin_package()                                   #声明包类型  catkin package
   add_executable ( helloROS src/HelloWorld.cpp )     
   #指定节点名字(用于运行时的名字 就是编译生成的可执行文件名字) 以及对应的源文件位置和名称

   #(rosrun 包名 节点名(代称) 主程序初始化ros系统时的节点名为实际节点名 ros::init() )
   target_link_libraries(helloROS ${catkin_LIBRARIES})#指定节点的编译链接库

d. 可执行文件 源文件 helloROS.cpp

//ROS Hello,world
#include<ros/ros.h>    //包含头文件

int main(int argc, char** argv){
    ros::init(argc, argv, "hello_ros");    //初始化 ROS 系统  最后一个参数为节点默认名的字符  一般为CMakeLists.txt指定节点名称
    ros::NodeHandle nh;                    //在节点管理器中注册该cpp文件的节点
    ROS_INFO_STREAM("Hello, ROS!");        //ROS_INFO_STREAM 宏将生成一条消息,且这一消息被发送到不同的位置,包括控制台窗口
}

3、发布者 程序节点编写实例

2.1 包含消息类型声明
每一个 ROS 话题都与一个消息类型相关联。
每一个消息类型都有一个相对应 C++头文件。
你需要在你的程序中为每一个用到消息类型包含这个头文件,代码如下所示:

#include <package_name/type_name.h>

//这里需要注意的是,功能包名应该是定义消息类型的包的名称,而不一定是你自己的包的名称。
在 pubvel 程序中,我们想发布一条类型为 geometry_msgs/Twist 的消息(名为 geometry_msgs 的包
所拥有的类型为 Twist 的消息),我们应该这样:
#include <geometry_msgs/Twist.h>
这个头文件的目的是定义一个 C++类,此类和给定的消息类型含
有相同的数据类型成员。这个类定义在以包名命名的域名空间中。
这样命名的实际影响是当引用 C++代码中的消息类时,你将会使
用双分号(::)来区分开包名和类型名,双分号也称为   范围解析运算符 。
在我们的 pubvel 例程中,头文件定义了一个名为
geometry_msgs::Twist 的类。
<ros/ros.h>               //ROS 库文件
#include <geometry_msgs/Twist.h>   //发布给小乌龟(turtlesim仿真器)的消息类型头文件库
#include <stdlib.h>                //标准库 产生rand()随机数

int main( int argc , char** argv ) {

ros::init( argc , argv , "publish_velocity" );  //初始化ROS系统  定义默认节点名字
ros::NodeHandle nh ;                            //在节点管理器中注册节点
//在节点上建立一个发布者 pub         发布的消息类型   发布的消息到话题(无前\ 相对名称)   消息序列的大小数据缓存区大小
//ros::Publisher pub = node_handle.advertise<message_type>(topic_name, queue_size);
ros::Publisher pub = nh.advertise<geometry_msgs::Twist>( "turtle1/cmd_vel" , 1000 ) ;  //话题不能搞错了 多加空格也不行

//如果你想从同一个节点发布关于多个话题的消息,你需要为个话题创建一个独立的 ros::Publisher 对象。
//生成产生随机数的种子点
srand(time(0)) ;       //Seed the random number generator
ros::Rate rate (2);    //发布频率   控制 消息发布 频率   这个对象控制循环运行速度
while ( ros::ok() ) {  //ros::ok() 节点是否停止工作的检查  rosnode kill/终止信号(Ctrl-C)
     //定义消息变量 并以随机数赋值
     //若消息类型为数组类型(在rosmsg中用方括号示)在C++代码中是通过STL向量实现的。
     geometry_msgs::Twist msg;  
     msg.linear.x =      double( rand() ) / double(RAND_MAX);        //线速度  为 0 到 1 之间的某个值
     msg.angular.z = 2 * double( rand() ) / double(RAND_MAX) - 1;    //角速度  弧度制 为-1 到 1 之间的某个值
     //发布消息
     pub.publish( msg ) ;
     //在控制台显示发布的内容
     //演示了在输出中除了插入字符串还可以插入其他数据的能力
     ROS_INFO_STREAM( "Sending random velocity command : "            //ROS  information stream流
     << " linear speed = " << msg.linear.x << " angular speed = " << msg.angular.z);
     //休眠等待
     rate.sleep() ;
    }

4、订阅者

4.1编写回调函数 —— 类似中断函数===
发布和订阅消息的一个重要的区别是订阅者节点
无法知道消息什么时候到达。为了应对这一事实,我们必须把响
应收到消息事件的代码放到回调函数里,ROS 每接收到一个新的
消息将调用一次这个函数。
订阅者的回调函数类似于:
    订阅话题的包名和类型
void function_name(const package_name::type_name &msg)
{. . .}
其中参数 package_name 和 type_name 和发布消息时的相同,它
们指明了我们想订阅的话题的消息类。

回调函数的主体有权限访接收到消息的所有域,并以它认为合适的方式存储、使用或丢
弃接收到的数据。与往常一样,我们必须包含定义该类的头文件

本例程中,回调函数接收类型为 turtlesim::Pose 的消息,所以我们需要的头文件是 turtlesim/Pose.h。
注意订阅者的回调函数的返回值类型为 void。其实这样安排
是合理的,因为调用此函数是 ROS 的工作,返回值也要交给 ROS,
所以我们的程序无法获得返回值,当然非 void 的返回值类型也就
没有意义了。

4.2 创建订阅者对象

为了订阅一个话题,我们需要创建一个ros::Subscriber对象                         函数指针
ros::Subscriber sub = node_handle.subscribe(topic_name,queue_size, pointer_to_callback_function);

这个构造函数有三个形参,其中大部分与 ros::Publisher 声明中的类似:
node_handle 与我们之前多次见到的节点句柄对象是相同的。
topic_name 是我们想要订阅的话题的名称,以字符串的形式示。

本例程中是"turtle1/pose"。再次强调,我们忽略了前斜线使其成为相对名称。

queue_size 是本订阅者接收消息的队列大小,是一个整数。
通常,你可以使用一个较大的整数,例如 1000,而不用太多关心队列处理过程。
最后一个参数是指向回调函数的指针,当有消息到达时要通过这个指针找到回调函数。
在 C++中,你可以通过对函数名
使用符号运算符(&,“取址”)来获得函数的指针。

在我们的实例中,其方法如下:
&poseMessageReceived
不要在函数名后面犯添加括号()甚至(msg)这样常见的错误。这些括号和参数只有在你想调用函数的时候才需要,
而不是你想获得函数指针时。ROS 在它调用你的回调函数时会提供合适的参数

4.3给ROS控制权
实际上有两个略微不同的方式
ros::spinOnce();这个代码要求 ROS 去执行所有挂起的回调函数,然后将控制权限回给我们。 
仅一次ros::spin();这个方法要求 ROS 等待并且执行回调函数,直到这个节点关机。

换句话说,ros::spin()大体等于这样一个循环:
while(ros::ok( ))
{ ros::spinOnce(); }

使用 ros::spinOnce()还是使用 ros::spin()的建议如下:你的程
序除了响应回调函数,还有其他重复性工作要做吗?如果答案是 “ 否”,那么使用 ros::spin();
否则,合理的选择是写一个循环,做他需要做的事情,并且周期性地调用 ros::spinOnce()来处理回调。
SubPose.cpp 用 ros::spin(),因为程序唯一的工作就是接收和打印接收到的位姿消息。

例如可以这样:

ros::Rate rate (1); //每秒订阅 1次 消息
while ( ros::ok() ) {
ros::spinOnce();  //给ROS控制权  可以调用一次回调函数
rate.sleep();
}


订阅者程序中常见的一个错误是不小心忽略了调用ros::spinOnce 和 ros::spin。在这种情况下,ROS 永远没有机
会去执行你的回调函数。忽略 ros::spin 会导致你的程序在
开始运行后不久就退出。忽略 ros::spinOnce 使程序表现的好像没有接收到任何消息。

三、 日志消息

ROS_INFO_STREAM     (information 信息 stream  流)
的宏给用户展示了丰富的消息。这些消息就是  志消息的例子。
ROS 提供了一个包括 ROS_INFO_STREAM 在内、 具有很多其他特性的日志系统。

严重级别
与大多数通用软件日志系统类似,ROS日志系统的核心思想,
就是使程序生成一些简短的文本字符流,这些字符流便是日志消息。
ROS中,日志消息分为五个不同的严重级别,也可简称为严
重性或者级别。按照严重性程度递增,
这些级别有:

类型      示例
DEBUG   reading header from buffer                          (调试信息经常出现)
INFO    Waiting for all connections to establish             (正常的通知消息)
WARN    Less than 5GB of space free on disk                   (警告消息)
ERROR   Publisher header did not have required element: type   (错误消息)
FATAL    You must call ros::init() before creating the firstNodeHandle 
(致命性的错误消息  程序有问题的提示消息)


我们将学习如何通过定义允许的严重级来过滤或者强调一些消息。然而,这些级别本身并不包含任何
内在的含义:生成一个 FATAL 消息并不会终止你的程序;同样地,生成一个 DEBUG 消息并不会调试你的程序。

使用turtlesim 就 可 以 达 到 这 个 目 的 , 因 为 在 适 当 的 条 件 下 ,
turtlesim_node 可以生成除 FATAL 以外各个级别的日志消息。
虽然不能生成所有严重级别的日志消息,但是从学习的目的出发,
使用这样一个能够按要求生成可预测严重程度日志消息的程序非常方便。
//发布一次消息
 // Don't do this directly. Use ROS_..._STREAM_ONCE instead.
{
static bool first_time = true ;
if (first_time) {
ROS_INFO_STREAM( "Here's some important information"
<<" that will only appear once.");
first_time = false;
  }
}
    
 #include <ros/ros.h>
int main ( int argc , char **argv ) {
ros::init(argc, argv, "log_once");
ros::NodeHandle nh ;
while( ros::ok( )){
  ROS_DEBUG_STREAM_ONCE( "This appears only once. " );
  ROS_INFO_STREAM_ONCE( "This appears only once. " );
  ROS_WARN_STREAM_ONCE( "This appears only once. " );
  ROS_ERROR_STREAM_ONCE( "This appears only once. " );
  ROS_FATAL_STREAM_ONCE( "This appears only once. " );
}
  }
  
  //轮询定时发送消息 (耗费 cpu 不推荐)
  ROS_ERROR_STREAM_THROTTLE(1.0, "This appears every 1.0 seconds.") ;
实际上,日志消息有三个不同的目的地:每一个日志消息可以在控制台输出(屏幕),
可以是 rosout 话题的消息,也可写入到日志文件中。

【1】控制台输出

具体而言,DEBUG和INFO消息被打印至标准输出(standard output),
而WARN、ERROR和FATAL消息将被送至标准错误(standard error)

标准输出和标准错误之间的区别是无关紧要的,除非你想要将其中一个或者这两个流输出重定向到一个文件
或管道,这种情况比较复杂,通常的文件重定向技术
    command > file   只重定向标准输出,不包括标准错误。
为了将所有日志消息重定向到同一个文件,可以使用类似如下命令:
command &> file


然而,需要注意的是,由于这两种流的缓存方式不同, 可能导致消息不按照顺序出现——在结果中 DEBUG 和
INFO 消息可能出现得比你预想的靠后。你可以使用 stdbuf   命令使标准输出采用行缓存方式,从而强制让消息按照正
常顺序输出:
stdbuf -oL command &> file
最后,注意 ROS 会在输出中插入人类和软件无法理解的 ANSI 颜色编码,这些编码类似^[[0m],即使这些输出不是
定向到终端中。查看一个包含这些编码的文件,可以使用
如下命令:
less -r file    repaint the screen

格式化控制台消息
可以通过设置 ROSCONSOLE_FORMAT 环境变 量来调整日志消息打印到控制台的格式。该环境变量通常包含一
个或多个域名,每一个域名由一个美元符号$和一对大括号{}来表,用来指出日志消息数据应该在何处插入。

默认的格式是:
ROSCONSOLE_FORMAT =   [${severity}] [${time}]: ${message}
		消息严重类型     时间       消息
DEBUG INFO WARN ERROR FATAL 中的一个

这个格式可能适合大部分的应用,但是还有一些其他的域也是很 有用的 :
为了插入生成日志消息的源代码位置,可以使用${file}、 ${line} 和${function}域的组合形式。
为了插入生成日志消息的节点名称,可以使用${node}域。

roslaunch 工具(将在第 6 章介绍)默认并不会将标准输出
和标准错误从其生成的节点导入至自己的输出流。为了查
看使用 roslaunch 启动节点的输出,你必须显式地使用 output=”screen”属性,
或者对 roslaunch 命令使用—screen
参数来强制使所有节点应用这个属性,

【2】 rosout 上的消息

rostopic echo /rosout   显示话题上的时时消息
rostopic info /rosout

Type: rosgraph_msgs/Log     //消息类型

Publishers: 
* /rostopic_4299_1488883490830 (http://ewenwan-Lenovo-G480:44305/)
* /rqt_gui_py_node_4533 (http://ewenwan-Lenovo-G480:34204/)
* /count_and_log (http://ewenwan-Lenovo-G480:45080/)

Subscribers: 
* /rosout (http://ewenwan-Lenovo-G480:39512/)
* /rostopic_4299_1488883490830 (http://ewenwan-Lenovo-G480:44305/)


rosmsg show rosgraph_msgs/Log

byte DEBUG=1
byte INFO=2
byte WARN=4
byte ERROR=8
byte FATAL=16
std_msgs/Header header
uint32 seq
time stamp
string frame_id
byte level
string name
string msg
string file
string function
uint32 line
string[] topics


rqt_console      日志消息的图形界面显示  控制台 console

【3】 日志文件

日志消息的第三个,也是最后一个目的地,是由 rosout 节点
生成的日志文件。作为/rosout 话题回调函数的一部分,该节点可
以将日志消息作为一行写入到一个日志文件,文件名类似于:
~/.ros/log/run_id/rosout.log

less  ~/.ros/log/76944ed4-0322-11e7-9ede-95eac9800164/rosout.log
这里的 rosout.log 日志文件是纯文本文件。它可以利用 less、 head
或者 tail 等命令行工具、或者是你喜欢的文本编辑器查看。运行
标识码(run_id)是一个通用唯一识别码(UUID),它在节点管理
器开始运行时基于你计算机的 MAC 地址和当前的时间生成。下面
是一个 run_id 的例子:
57aa1860-d765-11e2-a830-f0def1e189cc
使用这类唯一标识码,可以区分来自不同 ROS 会话的日志文件。

查找运行标识码:====================
至少有两种简单方法可以查看当前会话的    run_id

1)   通过检查 roscore 生成的输出,在靠近输出末端的位置,可以看到与下面内容类似的一行
     setting /run_id to run_id
2)   通过以下命令向节点管理器询问当前的 run_id
     rosparam get /run_id
  由于 run_id 存放在参数服务器上,因此该命令是有效的。
  rosparam get /run_id

 76944ed4-0322-11e7-9ede-95eac9800164

检查和清除日志文件:=========================
这些日志文件将随着时间累积。如果你长
期在一个对存储空间有着严格限制(由于账户配额或者是硬件限制)的系统上运行ROS,就会出现问题。
roscore和roslaunch运行
时都会检查和监测已经存在的日志的大小,并会在日志文件大小
超过 1GB时提醒你,但是并不会采取任何的措施来减小日志文件
大小。也可以使用下面这条命令来查看当前账户中被ROS日志消耗的硬盘空间  :

rosclean check
如果日志正在消耗过多的硬盘空间,可以通过下面的命令删除所
有已经存在的日志:

rosclean purge
如果你愿意,也可以手动删除这些日志文件。


启用和禁用日志消息:==================================
这是一个关于日志级别的具体例子,对每一个节点都指明了
最低的严重级别。默认的日志级别是 INFO,这就解释了在我们的
示例程序中为什么没有生成 DEBUG 级别的消息。日志级别背后通
常的思路是在运行时提供调整每个节点日志细节程度的能力。

设置日志级别类似于 rqt_consolt 中的日志级别过滤选项。
不同的是,改变日志级别将阻止日志消息的源头生成相应
的消息,而 rqt_console 会接收任何输入的日志消息,其过
滤选项只是选择性地显示其中的一部分。除了系统开销以
外,效果是类似的。

    设置某个节点的日志级别有几种方法:==================

1) 通过命令行设置日志级别=======
为了通过命令行设置一个节点的日志级别,可以使用与以下类似的命令:
rosservice call /node-name/set_logger_level ros.package-name level
命令调用 set_logger_level 服务,该服务由各个节点自动提供

node-name 是你期望设置日志级别的节点名称
package-name 正如你猜测的一样,是拥有这个节点的功能包的名称
level 参数是 DEBUG、INFO、WARN、ERROR、FATAL 中的一个字符串,即为节点设置的日志级别

例如:

rosservice call /count_and_log/set_logger_level ros.agitros DEBUG   //启用DEBUG以上的日志消息提示
由于这条命令直接与节点进行交互,我们不能应在节点启动后使用它。

2)通过图形界面设置日志级别===========
rqt_logger_level


3)通过 C++代码设置日志级别===========

节点改变自身的日志级别也是可能
的。最直接的方式是调用 ROS 来实现日志功能的 log4cxx 提供的接口,代码如下:

#include <log4cxx/logger.h>
. . .
log4cxx::Logger::getLogger(ROSCONSOLE_DEFAULT_NAME)->setLevel(
ros::console::g_level_lookup[ros::console::levels::Debug]);
ros::console::notifyLoggerLevelsChanged();

除了必要的语法理解,这些代码应该很容易识别是将日志级别设置为 DEBUG。
Debug 这个标识当然可以替换为 Info、Warn、Error 或者 Fatal。
调用 ros::console::notifyLoggerLevelsChanged()是有必要的,
因为每个日志的启用或者禁用是缓存了的。如果你在生成任何日志消息之前就已经设置了日志级别,那么上述调用就可以省略。

三、ROS 计算图 源名称(grap resource name) ROS的命令结构

 1、 全局名称
     节点、话题、服务和参数统称为计算图源,而每个计算图源由一个叫
     计算图 源名称( graph resource name)的短字符串标识。
  
/teleop_turtle
/turtlesim
/turtle1/cmd_vel
/turtle1/pose
/run_id
/count_and_log/set_logger_level

这些计算图源名称都属于全局名称,之所以叫做全局名称因为它们在任何地方(
包括代码、命令行工具、图形界面工具等的任何地方)都可以使用。
以下是一个全局名称的几个组成部分:

   1) 前斜杠“/”表明这个名称为全局名称。
   2) 由斜杠分开的一系列命名空间(namespace),每个斜杠代表一级命名空间。你可能已经猜到了,命名空间用于将相关的
      计算图源归类在一起。上述名称例子包含了两个显式的命名 空间,分别为 turtle1 和 count_and_log。ROS 允许多层次的命
      名空间,所以下面这个包含了 11 个嵌套名称空间的名称也是 有效的全局名称,虽然看起来不太可能有人这么用。
     /a/b/c/d/e/f/g/h/i/j/k/l
     如果没有显式提及所属的命名空间,包括上述三个例子在内,则对应的计算图源名称是归在全局命名空间中的。
   3)描述资源本身的基本名称(base name)。
     上述例子中的基本名 称 分 别 为 :
     teleop_turle 、 cmd_vel 、 pose 、 run_id 和 set_logger_level。   
     我们必须意识到,如果在任何地方都使用全局名称,除了可能使人更容易追踪变量外,并不能从使用复杂的命名空间中受益
     多少。这种命名系统的真正优势来自于相对名称和私有名称。
 
2、 相对名称   relative name

我们刚刚已经看到,使用全局名称时,为了指明一个计算图源,
需要完整列出其所属的命名空间,尤其是有时候命名空间层
次比较多,这可能会让你抓狂。这时,一个主要替代方案是让 ROS 
为计算图源提供一个默认的命名空间,具有此特征的名称叫做相
对计算图源名称(ralative graph resource name),
或简称为相对 名称(relative name)。
相对名称的典型特征是它缺少全局名称带有的前斜杠“/”。
下面是一些相对名称的例子:
teleop_turtle
turtlesim
cmd_vel
turtle1/pose
run_id
count_and_log/set_logger_level

3、 解析相对名称
将相对名称转化为全局名称的过程相当简单。
ROS 将当前默认的命名空间的名称加在相对名称的前面,
从而将相对名解析为全局名称。比如,如果我们在默认命名空间为/turtle1
的地方使用相对名称 cmd_vel,那么 ROS 通过下面的组合方法得 到全局名称:

/turtle1   +    cmd_vel    ⇒  /turtle1/cmd_vel
默认命名空间      相对名称           全局名称

相对名称也可以以一系列的命名空间开始,这些命名空间被 看作是默认命名空间中的嵌套空间。
举个例子,如果我们在默认
命名空间为/a/b/c/d/e/f 的地方使用相对空间 g/h/i/j/k,
ROS 将会将其进行下面的组合:
/a/b/c/d/e/f   +    g/h/i/j/k   ⇒  /a/b/c/d/e/f/g/h/i/j/k/l
默认命名空间           相对名称          全局名称 

然后,得到的全局名称就可以用于确定一个特定的计算图源,就 像前面介绍全局名称的使用时一样。

4、 设置默认命名空间  default-namespace

默认的命名空间是单独地为每个节点设置的,而不是在系统范围进行。如果你不采取下面介绍的步骤来设置默
认命名空间,那么 ROS 将会如你所期望的那样,使用全局命名空间(/)作为此节点的默认命名空间。

为节点选择一个不同的默认命 名空间的最好也是最常用的方法是在启动文件中使用命名空间 (ns)属性

1) 大部分 ROS 程序,包括调用 ros::init 的所有 C++程序,接 受叫做__ns 的命令行参数,此参数将为程序指定一个默认命 名空间。
__ns:=default-namespace
2) 还可以利用环境变量为在 shell 内执行的 ROS 程序设置默 认命名空间。
Export ROS_NAMESPACE=default-namespace
请注意,只有当没有其他由 __ns 参数指定的默认命名空间时,这个环境变量才有效。


5、  私有名称(private name) 类似 ~ 代表home目录一样
私有名称,以一个波浪字符(~)开始,是第三类也是最后一 类计算图源名称。
和相对名称一样,私有名称并不能完全确定它
们自身所在的命名空间,而是需要 ROS 客户端库将这个名称解析 为一个全局名称。
与相对名称的主要差别在于,私有名称不是用 当前默认命名空间,而是用的它们  节点名称  作为命名空间。
例如,有一个节点,它的全局名称是 /sim1/pubvel,ROS 将其私有名称 ∼max_vel  换至如下全局名称:

/sim1/pubvel  +   ∼max_vel    ⇒   /sim1/pubvel/max_vel
节点名称           私有名称            全局名称

这种命名方式基于如下事实,每个节点内部都有这样一些资源, 这些资源只与本节点有关,而不会与其他节点打交道,
这些资源就可以使用私有名称。私有名称常常用于参数——roslaunch 有专 门的功能用于设置私有名称可以访问的的参数,

将话题命名为私有名 称是个常见错误,因为如果我们想要保持节点的松耦合性,
那么 没有一个话题是被任意某个特定节点所“拥有的”。

私有名称的关键字“private”仅仅表示其他节点不会使用它 们所在的命名空间,也就是仅在命名空间层面上有意义。
对于其他节点来讲,只要知道私有名称解析后的全局名称, 都可以通过其全局名称访问这些计算图源。

这和 C++等其 他类似编程语言中的关键字“private”是不同的,在这些编 程语言中,
系统中的其他部分是不能访问某个类的私有成员变量的。
  
6、 匿名名称(Anonymous names)   可以同时启动多个 同样的拥有匿名名称的节点

除了以上三种基本的命名类型,ROS 还提供了另一种被称为 匿名名称的命名机制,一般用于为节点命名
(译者注:这里的匿 名并不是指没有名字,而是指非用户指定而又没有语义信息的名 ).
匿名名称的目的是使节点的命名更容易遵守唯一性的规则。
其思路是,当节点调用 ros::init 方法时可以请求一个自动分配的 唯一名称。

为 了 请 求 一 个 匿 名 名 称 , 节 点 需 要 将
ros::init_options::Anonymous-Name 作 为 第 四 个 参 数 传 递 给
ros::init 方法:         节点名字
ros::init(argc, argv, base_name, ros::init_options::AnonymousName);

虽然 ROS 追加了什么特定额外文本对于用户来讲并不是那 么重要,但作者觉得有必要指出 ros::init 使用处理器时间
(wall clock time)生成匿名名称。
// This program starts with an anonymous name, which
// allows multiple copies to execute at the same time ,
// without needing to manually create distinct names
// for each of them.
#include <ros/ros.h>
int main ( int argc, char **argv ) {
ros::init ( argc, argv, "anon",
ros::init_options::AnonymousName );
ros::NodeHandle nh ;
ros::Rate rate (1) ;
while (ros::ok( )) {
  ROS_INFO_STREAM("This message is from :"
     << ros::this_node::getName ( ));
    rate.sleep( );
   }
 }
这个程序的行为本身并没有什么特别。但是,正因为它请求了一个匿名名称,所以我们可以同时运行任意多的以上程序的副
本。这很容易理解,每个程序开始运行时,它就会得到一个具备唯一性的名字。

四 启动文件 launch file

可一次性启动多个 不同的节点

利用启动文件一次性配置和运行多个节点。 如果到目前为止你已经走通了本书中所有的例程,那么现在
你也许会有些沮丧,因为除了搞定 roscore 外,你还需要在这么 多不同的终端启动这么多不同的节点。幸运的是,ROS 提供了一
个同时启动节点管理器(master)和多个节点的途径,即使用
启动文件(launch file)。

事实上,在 ROS 功能包中,启动文件的使 用是非常普遍的。
任何包含两个或两个以上节点的系统都可以利用启动文件来指定和配置需要使用的节点。


使用启动文件
首先,我们来看一下roslanch是如何同时启动多个节点的。其 基本思想是在一个
XML格式的文件内将需要同时启动的一组节点 罗列出来 1 。

示例同时启动一个  turtlesim仿真器节点和一个遥控节点,这两个节点是我们在第二
章遇到过的,还有一个是我们在第三章写的订阅节点。这个启动 文件保存在agtr包的根目录下,文件名为example.launch。在详细
介绍启动文件的结构之前,我们先看一下这些文件是如何使用的。

launch
	在ROS应用中,每个节点通常有许多参数需要设置,为了方便高效操作多个节点,
	可以编写launch文件,然后用roslaunch命令运行

	roslaunch: roslaunch [options] [package] <filename> [arg_name:=value...]
			    roslaunch [options] <filename> [<filename>...] [arg_name:=value...]

执行启动文件 想要运行一个启动文件,可以像下面这样使用

roslaunch命令 2 :

	roslaunch package-name launch-file-name

请求详细信息(Requesting verbosity) 像很多命令行工具一
样,roslaunch 有一个可以请求输出详细信息的选项:
	roslaunch –v package-name launch-file-name

因此,可以使用如下指令来运行该示例启动文件:
	roslaunch agitrs example.launch

launch文件的一般格式,参数:
	<launch>
		<node .../>         //节点相关
		<rosparam ..../>    //ros参数 yaml文件
		<param .../>        //参数服务器参数
		<include .../>      //包含其他launch文件
		<env .../>          //环境变量
		<remap .../>        //重映射
		<arg.../>           //局部变量
	</launch>

例子

   <launch>
   <node
    pkg="turtlesim "
    type="turtlesim_node"
    name="turtlesim "
    respawn="true "
   />
   <node
    pkg="turtlesim "
    type="turtle_teleop_key"
    name="teleop_key"
    required="true"
    launch-prefix="xterm -e"
   />
   <node
   pkg="agitr "
   type="subpose"
   name="pose_subscriber "
   output="screen "
   />
   </launch>

############################### 
<node>要启动的node参数
pkg=''mypackage''     节点所属包名字
type=''nodetype''     c++ 文件编译生成的 可执行文件名字  .o  在cmake.list 内
		  add_executable(reverseCmdVel src/ReverseCmdVel.cpp)
		  target_link_libraries(reverseCmdVel  ${catkin_LIBRARIES})
name="nodename"      节点名字 会覆盖 cpp文件 内 ros::init 初始化的节点名 
arg="arg1 ...."(可选)
respawn="ture"(可选)如果节点停止,自动重启节点
required="true"  (可选)如果节点停止 会停止launch文件启动的所有节点 与respawn不可同时设置
ns='foo"(可选)在foo命名空间启动节点   设置节点的命名空间 所属名字空间域
output="log|screen"(可选) 日志消息输出 到窗口屏幕
launch-prefix="xterm -e"(可选 重新打开一个窗口)
</node>
###########################
<rosparam>操作yaml文件参数
command="load|dump|delete"(默认load)
file="$(find pkg-name)/path/foo.yaml"(load或dump命令)yaml文件的名字
param="param-name"参数名

##################################

<param> 定义一个设置在参数服务器的参数,它可以添加到<node>中
name="namespace/name"
type=''str|int|double|boot''(可选)指定参数的类型
value="value"(可选)如果省略这个参数,则应指定一个文件(binfile/textfile)或命令
 textfile="$(find pkg-name)/path/file"(可选)
binfile="$(find pkg-name)/path/file"()
 command="(find pkg-name)/exe '$(find pkg-name)/arg.txt'"(可选)
    exe是可执行文件(cpp、py),arg.txt是参数文件

####################################
<include>在当前launch文件中调用另一个launch文件
file="$(find pkg-name)/path/launch-file.launch"
包含元素同样支持命名空间属性,可以将内容压入一个指定的命名空间中去:
<include file=”...” ns=”namespace”/>
应该说该设置是十分普遍的,尤其是当被包含进来的

###################################

<env> 设置节点的环境变量
name="environment-variable-name"
value="environment-variable-value"
######################################

<remap> 将一个参数名映射为另一个名字  即改变订阅的话题名 或发布的话题名等
from="original-name"
to="new-name"
###########################

<arg> 定义一个局部参数,该参数只能在一个launch文件中使用
<arg name="foo"/>声明一个参数foo,后面需要给它赋值
<arg name="foo" default="1"/>声明一个参数foo,如不赋值取默认值
<arg name="foo" value="bar"/>声明一常量foo,它的值不能修改


启动文件的基本元素
最简单的启动文件由一个包含若干节点元素(node elements) 的根元素(root element)组成。
启动文件是 XML 文件,每个 XML 文件都必须要包插入根元素
含一个根元素。对于 ROS 启动文件,
根元素由一对 launch 标签定义:

	<launch>
	...
	</launch>
	
每个启动文件的其他元素都应该包含在这两个标签之内。

启动节点
任何启动文件的核心都是一系列的节点元素,每个节点元素指向一个需要启动的节点。
节点元素的形式为:

<node
pkg=”package-name”
type=”executable-name”
name=”node-name”
/>

在节点标签末尾的斜杠“/”是很容易忘记的,但是它很重要。
它表明不必再等待结束标签(“</node>”),并且该节
点元素是完整的。XML 语法分析器对于这样的细节要求得 非常严格。
如果你忽略了这个斜线,将会出现这样的错误信息:
Invalid roslaunch XML syntax: mismatched tag
你也可以这样显式地给出结束标签: <node pkg=”...” type=”...” name=”...”></node>
事实上,如果该节点有子节点,例如 remap 或者 param 元素,
那么该显式结束标签是必不缺少的。


 1、pkg  和 type  节点元素 包名和可执行文件别名 属性   
    每个节点元素有如下三个必需的属性。pkg 和 type 属性定义了 ROS 应该运行哪个程序来启动这个节
    点。这些和 rosrun 的两个命令行参数的作用是一致的,即给出功能包名和可执行文件的名称。
 2、 name 节点名称属性   
     name 属性给节点指派了名称,它将覆盖任何通过调用 ros::int 来赋予节点的名称.        
     该属性可以覆盖代码中提供给ros::int的命名信息,包括节点可能发出的匿名请求。如果要在启动文件
     内使用一个匿名名称,可以使用一个anon关键字替换名称属性,例如:
      name="$(anon base_name)"
      请注意,对同一个基本名称的重复利用将会产生相同的匿名名称。这说明(1)我们可以在启动文件的其他位置使用该
      名称,(2)对于每一个想要匿名的节点,都要确保使用不同的基本名称。
 
 3、output   日志输出属性     
查看节点日志文件使用roslaunch启动一组节点与使用rosrun
单独启动每个节点的一个重要不同点是,在默认状态下,从启动文件启动节点的标准输出被重定向到一个日志文件中,
而不是在控制台显示 * 。该日志文件的名称是:
~/.ros/log/run_id/node_name-number-stout.log
其中,run_id 是节点管理器启动时生成的一个唯一标示符。
rosparam get /run_id
           
在控制台中输出信息 对于某个单独的节点,只需在节点元素中配置 output 属性就可以达到该目的:
output="screen"

配置了该属性的节点会将标准输出显示在屏幕上而不是记录 到之前讨论的日志文档。
示例程序对 subpose 节点配置了该属性,
这就是为什么该节点的 INFO 信息会出现在控制台中,
同时也说明了为什么该节点没有出现在之前提到的日志文件列表中。
除了影响单个节点输出信息的 output 属性之外,我们还可以 
使用--screen 命令行选项来令 roslaunch 在控制台中显示所有节点的输出:
roslaunch –screen package-name launch-file-name

如果一个从 roslaunch 中启动的程序没有产生预期的输出, 
你 应 该 检 查 一 下 是 否 为 该 节 点 设 置 了 output 属 性
(output="screen")。

   
   4、 respawn  请求复位属性   重生

请求复位 在启动了启动文件中所有的请求节点之后,roslaunch 会监视每一个节点,
记录哪一个节点是活跃的。对于每个节点,
我们可以设置 respawn 属性为真,这样当节点停止的时候, roslaunch 会重新启动该节点。
respawn=”true”
这个功能是有意义的,比如在某个节点因为软件崩溃或硬件故障
以及其他原因导致过早退出系统的时候会起到作用。
在我们的示例中,respawn 属性实际上是不需要的,因为三个程序都很可靠。
但是我们在 turtlesim_node 中包含了该属性,
用来展示该属性如何工作。当我们关闭 turtlesim 窗口的时候,相应的节点也会停止,
ROS 很快就会识别到这样的变化。由于该节
点设置了复位属性,一个新的 turtlesim 节点(有其对应的窗口)将会运行来代替之前的对应节点。



  5、required  必要节点属性  关闭属性
必要节点(requiring node) 复位之外的另一种策略是将一个节点声明为必要节点。
required="true"

当一个必要节点终止的时候,roslaunch 会终止所有其他活跃节点并退出。
当出现
(1)某个节点非常重要,此节点的故障会导致整个 ROS 会话的瘫痪;
(2)节点即便设置了复位属性也不能很好地重启的时候,该需求属性的作用就会体现出来。
示例中对 turtle_teleop_key 节点配置了 required 属性。如果你关闭了该远程控制节点所在的窗口,
roslaunch 将会终止另外两个节点,然后退出。

由于 respawn 和 required 二者的作用是相互矛盾的,因此,如果对一个节点同时配置了这两种属性, roslaunch 会报错。

6、launch-prefix    启动前缀   命令行前缀   独立窗口属性

为节点维护独立的窗口 使用 roslaunch 的一个缺点是所有的节点共享一个终端,
而使用 rosrun 的时候,每个节点都有一个独立的
终端。这对于不接收控制台输入,只生成日志消息的节点是可行的(通常也是有帮助的),
但是对于依赖控制台输入的节点来说,
例如 turtle_teleop_key,更倾向于为节点保留独立的终端。

幸运的是,roslaunch 提供了一个实现该目的的简洁方法——对节点元素使用启动前缀(launch-prefix)属性:
launch-prefix=“command-prefix” 事实上,roslaunch 在启动节点时的内部工作原理是调用相应
的命令行工具,即 rosrun。启动前缀的主要思想是在其命令行前面添加给出的命令行前缀。
在我们的例子中,遥操作节点使用了这个功能:
launch-prefix="xterm -e"
在这个属性的作用下,节点元素和在命令行中输入下面的命 令基本上是等价的:
xterm -e rosrun turtlesim turtle_teleop_key

    execute  terminal     
正如你所知,xterm 命令将打开一个简单终端窗口。参数-e 告诉 xterm 
在新打开的终端中执行该命令行的剩余部分(这里是
rosrun turtle_teleop_key ), 运 行 的 结 果 是 基 于 纯 文 本 的 程 序
turtle_teleop_key 出现在一个图形窗口中。
当然,启动前缀属性并不只局限于xtrem。该属性也可以用
来调试(通过gdb或valgrind)或降低一个进程的优先级(通过nice) 。



7、在命名空间内启动节点 命名空间属性  ns="sim1"

对一个节点设置默认命名空间——这个过程通常叫做压入(pushing down)命名空间——的通常方法是使用
一个启动文件,并对其节点元素配置命名空间(ns)属性:
ns=”namespace”

  
<launch>
       <node
	name="turtlesim_node"
	pkg="turtlesim"
	type="turtlesim_node"
	ns="sim1">
       </node>

       <node
	pkg="turtlesim"
	type="turtle_teleop_key"
	name="teleop_key"
	required="true"
	launch-prefix="xterm -e"
	ns="sim1" >
       </node>

       <node
	name="turtlesim_node"
	pkg="turtlesim"
	type="turtlesim_node"
	ns="sim2" >
       </node>

       <node
	pkg="agitros"
	type="pubVel"
	name="velocity_publisher"
	ns="sim2" >
       </node>
 </launch>


一个仿真中海龟由随机生成的速度指令控制,另一个仿真中海龟是远程控制的。
rqt_graph
从图中可以看到,前面我们多次见到的 turtlesim 节点相关的话题名称 ( turtle1/cmd_vel 、 turtle1/color_sensor 以 及
turtle1/pose)已经从全局命名空间被移动到了两个独立的命 名 子 空 间 /sim1 和 /sim2 。 
这 种 变 动 产 生 的 原 因 是
turtlesim_node 节 点 的 代 码 在 创 建 ros::Pbulisher 和ros::Subccriber 
对象时使用了像 turtle1/pose 这样的相对名称
(而不是/turtle1/pose 这样的全局名称)。
同样,启动文件中的节点名称是相对名称。在该例子中,两 个节点有相同的相对名称——turtlesim_node。
完全一样的相对名称并不会造成问题,因为他们对应的
全局名称即
/sim1/turtlesim_node 和/sim2/turtlesim_node 并不相同。


事实上 roslaunch 要求启动文件中的节点名称是基名称,即 不涉及任何命名空间的相对名称。
如果节点元素的名称属性中出现了全局名称,roslaunch 将会报错.

在 2.8 节,我们只改变了节点名称,并把所有节点放在同一 个命名空间。
因此,两个 turtlesim 节点向同一个话题订阅和发布
信息,没有办法单独同两个仿真中的一个进行交互。而在表 6.3 的例子中,
我们将每一个节点都压入自己的命名空间中去,这样
使两个模拟器真正地独立起来,从而可以给每个节点发送不同的速度指令。

在该例中,由 ns 属性定义的命名空间本身是相对名称。
也就是说,我们在一个默认命名空间是全局命名空间“/”的启
动文件内使用了 sim1 和 sim2 这两个名称。因此,两个节点的默认命名空间就解析到/sim1 和/sim2。
从技术层面看,完全可以做到为这个属性设置一个全局名称:然而,这是一个非常糟糕的想法。
因为本质上,这和
在节点内使用全局名称的做法如出一辙。这样做将会阻止启动文件被压入到其他命名空间中,
例如被另一个启动文件包含的时候。



 8、 名称重映射(Remapping names)

除 了 相 对 名 称 和 私 有 名 称 , ROS 节 点 还 支 持 重 映 射(remapping),它可以从更精细的层面控制对所用节点名称的修
改 。重映射是基于替换的思想:每个重映射包含一个原始名称和一个新名称。每当节点使用重映射中的原始名称时,ROS客户
端库就会将它默默地替换成其对应的新名称。

当启动一个节点的时候,有两种方法来创建重映射。
1)当使用命令行启动节点时,分别给出原始名称和新名称,中间由一个:=分开,如下所示:
    original-name := new-name
例如,运行一个 turtlesime 的实例,如果想要把海龟的姿态数据发布到话题/tim 而不是/turtle1/pose,就可以使用如下命令:
    rosrun turtlesim turtlesim_node turtle1/pose:=tim

2) 通过启动文件的方式,只需在启动文件内使用重映射(remap)元素即可:
 <remap from="original-name"to"new-name"/>
 如果该属性在顶层,即作为 launch 元素的子元素出现,重映射将会应用到所有的后续节点。这些重映射元素也可以作为
 一个节点元素的子元素,如:
    <node node-attributes>
      <remap from=”original-name”to ”new-name”/>
    </node>
在这种情况下,给出的重映射只应用于其所在的节点。例如,1)中的命令行本质上等价于如下结构的启动文件:
<node pkg=”turtlesim” type=”turtlesim_node”
      name=”turtlesim_node”>
      <remap from=”turtle1/pose”to ”tim”/>
</node>

关于重映射的使用,有一点需要牢记:在 ROS 应用任何重映射之前,所有的名称都要先解析为全局名称,包括重映射中的原
始名称和新名称。因此,虽然在重映射中出现的名称通常是相对名称,但当名称解析完成后,重映射就可以通过直接的字符串比
较来实现,即在所有重映射中查找某个节点使用的原始名称。


  8.1利用重映射 来反向海龟(Reversing a turtle)
  
下面我们举一个具体的例子来说明这些重映射是如何起作用的。
考虑这样一个方案,我们还是使用 turtle_teleop_key 来控制
turltesim 中海龟的运动,但是让每一个方向键的含义都翻转过来。
也就是说,我们让左键控制顺时针旋转,右键控
制逆时针旋转,上键控制它倒退,下键控制它前进。
这么做看起来也许有些不合情理,但是它却能够代表一类问题,
即一个节点发布的消息一定要转化为另一个节点所期望的格式。

一个显而易见的方案是复制 turtle_teleop_key 的源代码,并对其进行修改,
以达到我们想要的效果。但是这个方案令人非常
不满,因为它要求用户首先理解代码,更糟糕的是,有可能造成 代码重复。
另一种方案是编写一个新的程序,利用它来把遥操作 节点发送的速度指令进行反转。

//订阅键盘发送消息的话题,反转消息发布到一个新的话题 ,利用launch的重映射改变turtlesim_node 订阅的话题

  // This program subscribes to turtle1/cmd_vel and
   // republishes on turtle1/cmd_vel_reversed ,
   // with the signs inverted .
   #include <ros/ros.h>
   #include <geometry_msgs/Twist.h> //消息类型头文
   ros::Publisher *pubPtr;
   
  //订阅turtle1/cmd_vel话题的回调函数
   void commandVelocityReceived (const geometry_msgs::Twist &msgIn ) {
   geometry_msgs::Twist msgOut;
   msgOut.linear.x = -msgIn.linear.x;
   msgOut.angular.z = -msgIn.angular.z;
   pubPtr->publish(msgOut);
   }
   
   int main( int argc , char** argv ) {
   ros::init ( argc , argv , "reverse_velocity");
   ros::NodeHandle nh;
   
     //把反转的消息发布到turtle1/cmd_vel_reversed 新话题上
   pubPtr = new ros::Publisher ( nh.advertise <geometry_msgs::Twist>("turtle1/cmd_vel_reversed",1000) ) ;
    //订阅turtle1/cmd_vel话题上的消息(键盘控制发来的)
   ros::Subscriber sub = nh.subscribe ("turtle1/cmd_vel" , 1000 ,&commandVelocityReceived ) ;
 
   ros::spin () ;
 
   delete pubPtr ;
  }
  
<launch>
       <node
	pkg="turtlesim"
	type="turtlesim_node"
	name="turtlesim_node">
	<remap
	 from="turtle1/cmd_vel"
	 to="turtle1/cmd_vel_reversed"/>
       </node>

       <node
	pkg="turtlesim"
	type="turtle_teleop_key"
	name="teleop_key"
	required="true"
	launch-prefix="xterm -e">
       </node>

       <node
	pkg="agitros"
	type="reverseCmdVel"
	name="reverse_velocity2">
       </node>
 </launch>




9、launch文件的其他参数

1)包含其他文件  include 元素
<include file="path-to-launch-file">
此处 file 属性的值应该是我们想包含的文件的完整路径。
由于直接输入路径信息很繁琐且容易出错,大多数包含元素都使用
查找(find)命令搜索功能包的位置来替代直接输入路径: $(变量名)
<include file="$(find package-name)/launch-file-name">
eg: <include file ="$( find agitr )/doubleTurtle.launch"
包含元素同样支持命名空间属性,可以将内容压入一个指定的命名空间中去:
<include file=”...” ns=”namespace”/>
应该说该设置是十分普遍的,尤其是当被包含进来的启动文件属于
另一个功能包的时候,设置的操作应当独立于其他节点。

roslaunch 命令 将会在程序包(package)的子目录里搜索launch文件。
include 元素必须要指定文件的特定路径,
你可以使用 find 来找到这个程序包,但是却不能在这个程序包目录里面自动的找到某个子目录里有launch文件。
举例:
这样做是正确的:<include file = "find learning_tutrols"/launch/start_demo.launch" / >
这样做是错误的:<include file = "find learning_tutrols"/start_demo.launch" />



2) 启动参数 launch arguments   局部变量
为了使启动文件便于配置,roslaunch还支持启动参数,有时也简称为参数甚至args,
其功能有点像可执行程序中的局部变量
<arg   name="use_sim3"   default ="0" />
这样做的优点是通过设置参数来描述节点在不同ROS会话中运行时可能需要改变的一小部分,从而避免代码重复。
为了说明这一点,示例文件中使用了名为use_sim3 的参数,用来确定启动三个还是两个turtlesim节点。

尽 管 在 计 算 机 领 域 术 语 中 很 多 情 况 下 prarmeter 和argument 都是可以互换的,
但是在 ROS 中,二者的含义是
非常不同的(译者注:在中文中一般都称为参数,更没有明显的区分,
因此后面还是使用它们的英文原文说明问题)。
a) parameter 是运行中的 ROS 系统使用的数值,存储在参数服务器(parameter server)中,
每个活跃的节点都可
以通过 ros::param::get 函数来获取 parameter 的值,用户也可以通过 rosparam 来获得 parameter 的值(参见第 7 章)。
b) argument 只在启动文件内才有意义;他们的值是不能被节点直接获取的。

<launch>
   <include file ="$( find agitros )/launch/doubleTurtle.launch"/>

   <!-- 参数名:use_sim3 默认值为:0 -->
   <arg   name="use_sim3"   default ="0" />

   <group ns="sim3" if="$( arg use_sim3 )" >
     <node
      name="turtlesim_node"
      pkg="turtlesim"
      type="turtlesim_node"/>
     <node
      pkg="turtlesim"
      type="turtle_teleop_key"
      name="teleop_key"
      required="true"
      launch-prefix="xterm -e"/>
   </group>
</launch>



3) 参数赋值 启动文件中的每一个参数都要进行赋值。
赋值有很多种方法,例如可以像下面在 roslaunch 命
a) 令行中提供该值:
roslaunch package-name launch-file-name arg-name:=arg-value

b) 除此之外,你也可以使用以下两种语法,将参数值作为 arg声明的一部分:

<arg name="arg-name" default="arg-def-value"/>
<arg name="arg-name" value="arg-value"/>

两者的唯一区别在于命令行参数可以覆盖默认值 default,但是不能覆盖参数值 value。
在示例中,use_sim3 有默认值 0,但是可以用如下命令行对参数值进行修改:
roslaunch agitros tripleTurtle.launch use_sim3:=1
如果我们想修改启动文件,用 value 替换 default,那么该命令会产生错误,因为由 value 设置的参数值是不能更改的.

4) 获取参数值
一旦参数值被声明并且被赋值,你就可以利用下面的arg 替换(arg substitution)语法来使用该参数值了:
$(arg arg-name)
每个该替换出现的地方,roslaunch 都将它替换成参数值。在示例中,我们在 group 元素中的 if 属性使用了一次 use_sim3 参数

5)传递参数到 包含的启动文件
单独参数
向包括的启动文件中发送参数值 目前已介绍的参数设定技巧的局限在于,
它并未提供任何方法将参数传递到通过包含元素导入
的次级启动文件中去。这一点很重要,因为像局部变量一样,
参数仅定义在对其进行声明的启动文件中,而不能被包含的启动文件“继承”。

该问题的解决方案就是将 arg 元素作为一个包含元素的子元素,如下所示:
<incluce file="path–to-launch-file">
<arg name="arg-name" value="arg-value"/>
...
</include>

请注意,该 arg 元素的用法和之前我们看到的 arg 声明是不同的。
在 include 标签之间的参数是属于被包含文件的,而不是它
们出现的启动文件。因为目的是为被包括的启动文件设置响应的参数值,所以该处需要使用 value 属性。

共同参数
一种常见的情况是两个启动文件(包含文件和被包含文件)有一些共同的参数,
在这种情况下,我们希望参数在传递的时候
不会改变。这样的元素在两个地方使用相同的参数名:
<arg name="arg-name" value="$(arg arg-name)">
在该例子中,第一个参数名字通常是指被包含文件中的参数,第二个参数名是指当前文件中的参数。
这样做之后,给定参数在两个启动文件中有相同的值。

6 ) 创建组类似打包 (Creating groups)  
这里介绍启动文件的最后一个特征,即组(group)元素,
它提供了一种在大型启动文件内管理节点的便捷方式,具体来讲组元素可以完成如下两种任务:     

组可以把若干个节点放入同一个命名空间内。
<group ns=”namespace”/>
...
</group>
组内的每个节点都从给定的默认命名空间启动。

如果一个组元素内的某个节点有它自己的命名空间属性,并且其名称是(原则上也应该是)相对名称,那么该节点
将会在一个默认命名空间内启动,这个默认的命名空间是将此节点命名空间嵌入到组元素命名空间的结果。这个规
则和前面讲到的名称解析是相符的,并且这个规则也适用于组元素的嵌套。

<group if=”0 or 1”/>     // <group ns="sim3" if="$( arg use_sim3 )" >
...
</group>
如果 if 属性的值为 1,则组元素标签内的元素被正常包含。 反之,如果 if 属性的值为 0,则标签内元素被忽略。
还有一个 unless属性的用法是相似的,只是意思相反。
<group unless=”1 or 0”/>
...
</group>


我们的示例中使用一个单独的组演示了这两种使用目的。该组既使用了命名空间属性将组内的两个节点放入命名空间
sim3,又使用了 if 属性实现了基于 use_sim3 参数值使能或禁用第三个仿真器。

注意,组元素并不是严格必须的。如果不使用组,我们也可以对可能包括到组内的每个元素单独设置 ns、 if 和 unless
等属性。然而,使用组可以减少代码重复——命名空间和条件设置仅出现一次——并且使启动文件的结构更加清晰。

不幸的是,只有前面提到的三个属性可以通过组进行传递。例如,尽管我们很想,但不能为组元素设置
output=”screen”,必须直接为每一个我们想要应用它的节点设置该属性。
   
9、param 标签   设置参数服务器上的数据
定义了一个参数服务器上的参数,属性有
name : 参数名字
type :参数的类型,”str|int|double|bool”
value:参数的值,除了value还可以使用以下三种
textfile= $(find pkg-name)/path/file.txt,
该文件的内容将被读取并存储为一个字符串,此文件必须是本地的,可访问的。
所以强烈推荐使用$(find pkg-name) 形式来。避免在另一台机器上找不到文件路径运行失败。
binfile="$(find pkg-name)/path/file"该文件的内容将被读取并存储为Base64编码的XML-RPC二进制对象
command="$(find pkg-name)/exe '$(find pkg-name)/arg.txt'"

parameter server 参数除了上述launch param标签方法修改外,可以通过以下方式设置
1) 命令行 rosparam set 设置  /    获取   rosparam get  
2) 代码中
roscpp: 设置  ros::param::set /   获取 ros::param::get
rospy:  设置  set_param       /   获取 get_param

10、rosparam 标签
rosparam :可以使用从rosparam YAML文件加载load,删除delete,存储dump ,ROS参数服务器参数
rosparam 参数:
command="load|dump|delete" (optional, default=load)
file="$(find pkg-name)/path/foo.yaml" (load or dump commands)
param="param-name"
ns="namespace"                        (optional)
subst_value=true|false (optional) 是否允许替换yaml文本中的参数


<rosparam command="load"   file="$(find rosparam)/example.yaml" />          //加载文件中的参数
<rosparam command="delete" param="my/param" />                              //删除参数
<arg name="whitelist" default="[3, 2]"/>
<rosparam param="whitelist" subst_value="True">$(arg whitelist) </rosparam> //替换
<rosparam param="a_list">[1, 2, 3, 4] </rosparam>
<rosparam>
a: 1
b: 2
</rosparam>

五、 坐标变换 tf

1、demo运行环境

sudo apt-get install ros-indigo-ros-tutorials ros-indigo-geometry-tutorials ros-indigo-rviz ros-indigo-rosbash ros-indigo-rqt-tf-tree

2、运行demo

roslaunch turtle_tf turtle_tf_demo.launch

内容:

 <launch>
  <!-- Turtlesim Node-->
  <node pkg="turtlesim" type="turtlesim_node" name="sim"/>   节点名为sim的小乌龟
  <node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/>  键盘控制
  <!-- Axes -->
  <param name="scale_linear" value="2" type="double"/>       全局参数
  <param name="scale_angular" value="2" type="double"/>
  <node name="turtle1_tf_broadcaster" pkg="turtle_tf" type="turtle_tf_broadcaster.py" respawn="false" output="screen" >
    <param name="turtle" type="string" value="turtle1" />     坐标广播1
  </node>
  <node name="turtle2_tf_broadcaster" pkg="turtle_tf" type="turtle_tf_broadcaster.py" respawn="false" output="screen" >
    <param name="turtle" type="string" value="turtle2" />     坐标广播2
  </node>
  <node name="turtle_pointer" pkg="turtle_tf" type="turtle_tf_listener.py" respawn="false" output="screen" >
  </node>                                                    坐标监听
</launch>

3、可视化

节点关系图
rqt_graph
坐标变换图静态
rosrun tf view_frames
会生成一个pdf文件
显示    pdf文件
evince frames.pdf

Therefore an helpful shortcut to add in your .bashrc is:
alias tf='cd /var/tmp && rosrun tf view_frames && evince frames.pdf &'


坐标变换图实时显示
rosrun rqt_tf_tree rqt_tf_tree   或者 rqt & (貌似不行)

4、显示坐标转换信息 tf_echo

rosrun tf tf_echo [reference_frame] [target_frame]

rosrun tf tf_echo turtle1 turtle2

5、显示动态坐标 rviz

加载官方配置文件
rviz -d `rospack find turtle_tf`/rviz/turtle_rviz.rviz
或者自己打开
rviz  刚打开没有显示 需要添加显示的物体 以及配置参考系
界面左下角
>>> 点击ADD按钮 添加 要显示的物体  TF坐标系
界面左上角
>>> 选择Global Options(全局选项) 下的Fixed Frame(参考坐标系)  从map 改为 world(其实也就是坐标原点为参考系)
>>> TF下还有 
 坐标系名字(show name)、轴(show axes)以及坐标变换连接箭头(show arrows)
 注意 TF下的Frame(坐标系)要全部使能 enable

界面右上角
有三维视角(照相机视角)选项

6、编写 坐标 广播和监听

创建tf学习包:

  cd %YOUR_CATKIN_WORKSPACE_HOME%/src
  catkin_create_pkg learning_tf tf roscpp rospy turtlesim

cd catkin_ws/src/
catkin_create_pkg learning_tf tf roscpp rospy turtlesim
		   包名        依赖
//turtle_tf_broadcaster.cpp
#include <ros/ros.h> //ros系统库文件
#include <tf/transform_broadcaster.h>//坐标广播类型头文件
#include <turtlesim/Pose.h>          //广播的消息类型 小乌龟的位置

std::string turtle_name;             //需要被广播坐标的坐标系名字变量

void poseCallback(const turtlesim::PoseConstPtr& msg){
  //定义一个 坐标变换广播
  static tf::TransformBroadcaster br; //对应 #include <tf/transform_broadcaster.h>
  //定义一个 坐标变换 变量
  tf::Transform transform;
  //初始化为坐标原点
  transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );
  //坐标变换中间变量 四元素
  tf::Quaternion q;
  q.setRPY(0, 0, msg->theta);//row pitch yaw(绕z旋转)
  //得到坐标变换内容
  transform.setRotation(q);
  //发送坐标变换                             变换内容      时间戳    父坐标系(参考坐标系)   子坐标系
  br.sendTransform(       //带有时间戳类型的坐标变换内容类型
                       tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name)
                   );
}

int main(int argc, char** argv){
  //初始化ros  注册节点  名字 my_tf_broadcaster_node
  ros::init(argc, argv, "my_tf_broadcaster_node");
  //argument count 至有一个 坐标系 名字
  if (argc != 2){
    ROS_ERROR("need turtle name as argument");
    return -1;
    };
  
  turtle_name = argv[1];
  //节点句柄
  ros::NodeHandle nh;
  //创建一个订阅者 订阅被广播者的 位置话题信息                 列队大小  订阅者的回调函数
  ros::Subscriber sub = nh.subscribe(turtle_name+"/pose", 10, &poseCallback);

  ros::Rate rate (10); //每秒订阅 10次 消息
 //当nh节点没有结束   或者 nh.ok()
 while ( ros::ok() ) {
    ros::spinOnce();  //给ROS控制权  可以调用一次回调函数
    rate.sleep();
 }
 // ros::spin();  //给ros控制权 即使能中断 可以无限次调用回调函数
  return 0;
};

//turtle_tf_listener.cpp

#include <ros/ros.h>//ros系统库文件
#include <tf/transform_listener.h>//坐标监听类型头文件
#include <geometry_msgs/Twist.h>  //发布到小乌龟2 的 几何坐标速度等的话题上
#include <turtlesim/Spawn.h>      //新生成 一个小乌龟所调用的服务文件库

int main(int argc, char** argv){
  //初始化ros  注册节点  名字 my_tf_listener_node
  ros::init(argc, argv, "my_tf_listener_node");
  //节点句柄
  ros::NodeHandle nh;
  //等待 spawn(重生)服务可用 
  ros::service::waitForService("spawn");
  //通过调用服务spawn  添加一个小乌龟  默认为 turtle2 命名空间
  ros::ServiceClient add_turtle = nh.serviceClient<turtlesim::Spawn>("spawn");
  turtlesim::Spawn srv;
  add_turtle.call(srv);
  //创建发布者                                 消息类型               话题名        队列大小
  ros::Publisher turtle_vel =nh.advertise<geometry_msgs::Twist>("turtle2/cmd_vel", 10);
  //创建坐标系变换监听
  tf::TransformListener listener;
  //监听频率
  ros::Rate rate(10.0);
  //当nh节点没有结束   或者 ros::ok()
  while (nh.ok()){
    // 创建带有时间戳类型的坐标变换内容 变量 与 Broadcaster 发布的对应
    tf::StampedTransform transform;
    try{ //监听两个小乌龟的坐标 得到两坐标差别  /turtle2紧跟/turtle1 列队中最新可用变换 時間
      //listener.lookupTransform("/turtle2", "/turtle1",ros::Time(0), transform);
                                                            //会出错 waitForTransform 
      //listener.lookupTransform("/turtle2", "/turtle1",ros::Time::now(), transform);
      
      //监听/turtle2小乌龟 和/turtle1小乌龟旁2米的子坐标系/carrot1 得到两坐标差别  /turtle2紧跟/turtle1
      //listener.lookupTransform("/turtle2", "/carrot1",ros::Time(0), transform);
      
      //等待变换
      /*
      ros::Time now = ros::Time::now();                    //最长等待時間 3s
    listener.waitForTransform("/turtle2", "/turtle1", now, ros::Duration(3.0));
    listener.lookupTransform("/turtle2", "/turtle1", now, transform);
                             */
        // 过去時間     有问题
        /*
     ros::Time past = ros::Time::now() - ros::Duration(5.0);
    listener.waitForTransform("/turtle2", "/turtle1", past, ros::Duration(1.0));
    listener.lookupTransform("/turtle2", "/turtle1", past, transform);
    */
        //
    ros::Time now = ros::Time::now();
    ros::Time past = now - ros::Duration(5.0);
    listener.waitForTransform("/turtle2", now,
                              "/turtle1", past,
                              "/world", ros::Duration(1.0));
    listener.lookupTransform("/turtle2", now,
                             "/turtle1", past,
                             "/world", transform);
                             
    }
    catch (tf::TransformException &ex) {
      ROS_ERROR("%s",ex.what());
      ros::Duration(1.0).sleep();
      continue;
    }
  //turtle2/cmd_vel 上的消息类型  #include <geometry_msgs/Twist.h> 
    geometry_msgs::Twist vel_msg;
  // 角速度
    vel_msg.angular.z = 4* atan2(transform.getOrigin().y(),
                                    transform.getOrigin().x());
  //线速度
    vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
                                  pow(transform.getOrigin().y(), 2));
  //发布消息
    turtle_vel.publish(vel_msg);
  //空闲时间休息 可以做其它事情
    rate.sleep();
  }
  return 0;
};

//add_frame_tf_broadcaster.cpp

#include <ros/ros.h> //ros系统库文件
 #include <tf/transform_broadcaster.h>//坐标广播类型头文件

 int main(int argc, char** argv){
    
//初始化ros  注册节点  名字 my_tf_broadcaster_node
 ros::init(argc, argv, "add_frame_tf_broadcaster");
 // 节点句柄
 ros::NodeHandle nh;
 //定义一个 坐标变换广播
 tf::TransformBroadcaster br;
 //定义一个 坐标变换 变量
 tf::Transform transform;
 
 //每秒订阅 10次 消息
 ros::Rate rate(10.0);
 
 while (nh.ok()){
 //初始化坐标变换 变量    与父坐标系的坐标差别固  carrot1 在 turtle1 左边2 米(垂直小乌龟肚子)
// transform.setOrigin( tf::Vector3(0.0, 2.0, 0.0) );
 //transform.setRotation( tf::Quaternion(0, 0, 0, 1) );
 //初始化坐标变换 变量与父坐标系的坐标差别
  transform.setOrigin( tf::Vector3(2.0*sin(ros::Time::now().toSec()), 2.0*cos(ros::Time::now().toSec()), 0.0) );
  transform.setRotation( tf::Quaternion(0, 0, 0, 1) );
 
 //发送坐标变换                          变换内容      时间戳    父坐标系(参考坐标系)   子坐标系
 br.sendTransform( // //带有时间戳类型的坐标变换内容类型
                  tf::StampedTransform(transform, ros::Time::now(), "turtle1", "carrot1")
                  );
 rate.sleep();
 }
 return 0;
};

6 tf_monitor 坐标变换监视器

rosrun tf tf_monitor

tf_monitor <source_frame> <target_target>
rosrun tf tf_monitor /base_footprint /odom

7 tf_echo显示消息

tf_echo <source_frame> <target_frame>
rosrun tf tf_echo /map /odom

8 static_transform_publisher 坐标变换发布器

8.1  static_transform_publisher x y z yaw pitch roll frame_id child_frame_id period_in_ms

Publish a static coordinate transform to tf using an x/y/z offset in meters and yaw/pitch/roll in radians.
(yaw is rotation about Z, pitch is rotation about Y, and roll is rotation about X).
The period, in milliseconds, specifies how often to send a transform. 100ms (10hz) is a good value.

8.2  static_transform_publisher x y z qx qy qz qw frame_id child_frame_id  period_in_ms

Publish a static coordinate transform to tf using an x/y/z offset in meters and quaternion.
The period, in milliseconds, specifies how often to send a transform. 100ms (10hz) is a good value.

命令行和launch 文件 都可调用
static_transform_publisher is designed both as a command-line tool for manual use,
as well as for use within roslaunch files for setting static transforms.
For example:
<launch>
<node
pkg="tf"
type="static_transform_publisher"
name="link1_broadcaster"
args="1 0 0 0 0 0 1 link1_parent link1 100" />  //x y z qx qy qz qw frame_id child_frame_id  period_in_ms
</launch>

六、动作消息类型 action actionlib

创建包
cd catkin_ws/src
catkin_create_pkg actionlib_lutorials actionlib message_generation roscpp rospy std_msgs actionlib_msgs

1、运动服务器的编写 Action Server

斐波纳契 序列 运动效果的产生   
Fibonacci sequence
目标  序列的顺序      the order of the sequence,
反馈  计算好的序列    the sequence as it is computed,
结果  最终生成的序列  the final sequence.

 1) 编写运动的消息文件 Fibonacci.action 
    文件为运动定义了 目标、结果和反馈话题的类型 
   actionlib_tutorials/action/Fibonacci.action

## 会在 /catkin_ws/devel/include/actionlib_lutorials/下生成一系列 以文件名+Action为开头的库文件
## FibonacciAction.h  FibonacciActionFeedback.h   FibonacciActionGoal.h FibonacciActionResult.h

# 以及以文件名为开头的库文件
#FibonacciFeedback.h   FibonacciGoal.h FibonacciResult.h
#goal definition   定 义 目标 话题 类型 序列的顺序
int32 order
---
#result definition 定义结果 话题类型 最终生成的序列 数组类型
int32[] sequence
---
#feedback          定义反馈 话题类型 计算好的序列  组类型
int32[] sequence


2) 编辑 CMakeLists.txt 文件

a)  确保 包依赖有 actionlib_msgs
 find_package(catkin REQUIRED COMPONENTS actionlib_msgs)
b)   添加系统依赖
 find_package(Boost REQUIRED COMPONENTS system)
c)  添加 action files 文件
 add_action_files(
 DIRECTORY action
 FILES Fibonacci.action)
d)   调用消息生成镜像 生成actionlib消息头文件
 generate_messages(DEPENDENCIES actionlib_msgs std_msgs )
e)   添加 编译包镜像
catkin_package(CATKIN_DEPENDS actionlib_msgs)

f)   添加包含路径
 include_directories(
 include
 ${catkin_INCLUDE_DIRS}
 ${Boost_INCLUDE_DIRS} )
g) 可执行文件等

# 斐波纳契 序列 运动效果生成 序列  服务器
add_executable(fibonacciServer src/fibonacciServer.cpp)
target_link_libraries(fibonacciServer  ${catkin_LIBRARIES} )
add_dependencies(fibonacciServer ${actionlib_lutorials_EXPORTED_TARGETS})

# 斐波纳契 序列 运动效果生成 客户端 发送 目标序列
#                  编译生存的可执行文件名   源文件名
add_executable(fibonacciClient src/fibonacciClient.cpp)
# 链接库
target_link_libraries(fibonacciClient  ${catkin_LIBRARIES} )
# 依赖库
add_dependencies(fibonacciClient ${actionlib_lutorials_EXPORTED_TARGETS})



3) 进行编译 自动生成 运动 消息文件 库 类似 自定义的消息文件一样(会生成头文件)

cd catkin_ws/
catkin_make
在 catkin_ws/devel/include/actionlib_lutorials 下生成一系列头文件
比如运动 目标、结果、反馈的头文件
#include <actionlib_lutorials/FibonacciActionGoal.h>
#include <actionlib_lutorials/FibonacciActionResult.h>
#include <actionlib_lutorials/FibonacciActionFeedback.h>
还在FibonacciAction.h中生成了 actionlib_lutorials 包的类

// fibonacciServer.cpp

#include <ros/ros.h>                               //ros系统文件
#include <actionlib/server/simple_action_server.h>
//系统本身的 简单运动服务器库 actionlib::SimpleActionServer
#include <actionlib_lutorials/FibonacciAction.h>   
//添加actionlib_lutorials/FibonacciAction.h 文件 catkin_make 生成的库文件

//using namespace actionlib_lutorials;  
//使用  包名定义的actionlib_lutorials名字空间 下面一些类可省去 名字空间
//使用名字 空间 可能会造成 类调用不明确(来源不明确)
//actionlib::SimpleActionServer 来自<actionlib/server/simple_action_server.h>
//actionlib_lutorials::FibonacciAction 来自<actionlib_lutorials/FibonacciAction.h>
typedef actionlib::SimpleActionServer<actionlib_lutorials::FibonacciAction> Server; //定义运动服务器

//自定义类
class FibonacciAction{
  //受保护的 自定义类型
protected:
  ros::NodeHandle nh_;  //节点句柄  自定义类变量末尾带下划线 而调用时则不用
  Server as_;           //运动服务器
  std::string action_name_;//运动 名字
 actionlib_lutorials::FibonacciFeedback feedback_; //反馈
 actionlib_lutorials::FibonacciResult result_;     //结果

public:
  //定义运动服务器 函数结构   服务器名字
  FibonacciAction(std::string name):
    //服务器 节点 名字                  可执行函数
     as_(nh_,name,boost::bind(&FibonacciAction::executeCB,this,_1),false),action_name_(name){
    as_.start();
  }
   //析构函数
   ~FibonacciAction(void)
    {}
  //可执行函数                                 目标
  void executeCB(const actionlib_lutorials::FibonacciGoalConstPtr &goal){
    ros::Rate rate(1);   //频率
   bool success=true; //标志
   feedback_.sequence.clear(); //反馈序列初始化 清零
   feedback_.sequence.push_back(0); //反馈序列初始化 加入种子点
   feedback_.sequence.push_back(1);
   ROS_INFO("%s: Executing, creating fibonacci sequence of order %i with seeds %i, %i", 
             action_name_.c_str(), goal->order, feedback_.sequence[0], feedback_.sequence[1]);
   //生成序列
   for(int i=0;i<=goal->order;i++){
    //确保Preempt没被 客户端请求
     if(as_.isPreemptRequested()||!ros::ok()){
       ROS_INFO("%s: Preempted", action_name_.c_str());
       as_.setPreempted();
       success=false;
       break;
     }
     //
     feedback_.sequence.push_back(feedback_.sequence[i]+feedback_.sequence[i-1]);
     as_.publishFeedback(feedback_);

     rate.sleep();
   }
   
   if(success){
     ROS_INFO("%s: Succeeded", action_name_.c_str());
     result_.sequence=feedback_.sequence;
     //同志客户端 生成 序列成功
     as_.setSucceeded(result_);
   }

  }
};

int main(int argc, char** argv)
{
  ros::init(argc, argv, "fibonacci");//初始化节点
  // 服务器函数                     服务器名字
  FibonacciAction fibonacci(ros::this_node::getName());
  //若 using namespace actionlib_lutorials; 可能会造成调用不明确
  ros::spin();
  return 0;
}
/*
 * fibonacciClient.cpp
 *
 *  Created on: March 16, 2017
 *      Author: EwenWan
 */
#include <ros/ros.h>  //ros系统文件
#include <actionlib/client/simple_action_client.h> //系统本身的 简单运动服务器库actionlib::SimpleActionClient
#include <actionlib/client/terminal_state.h>       ////系统本身的 终端状态库
#include <actionlib_lutorials/FibonacciAction.h>   //添加actionlib_lutorials/FibonacciAction.h 文件 catkin_make 生成的库文件
using namespace actionlib_lutorials;//使用  包名定义的actionlib_lutorials名字空间 下面一些类可省去 名字空间
//actionlib::SimpleActionServer 来自<actionlib/server/simple_action_server.h>
//actionlib_lutorials::FibonacciAction 来自<actionlib_lutorials/FibonacciAction.h>
typedef actionlib::SimpleActionClient<FibonacciAction> Client; //定义运动客户端 请求

int main (int argc, char **argv)
{
ros::init(argc, argv, "test_fibonacci");//初始化ros系统 注册节点
// 定义客户端  链接的服务器名字   自动开始线程      
Client ac("fibonacci",true);
ROS_INFO("wait action server to start");
ac.waitForServer();
ROS_INFO("action server started,sending goal");

FibonacciGoal goal;
goal.order=20;
//发送 序列 目标 次序
ac.sendGoal(goal);
//完成标志
bool finish_before_timeout=ac.waitForResult(ros::Duration(30));

//完成了
if(finish_before_timeout){
  actionlib::SimpleClientGoalState state=ac.getState();
  ROS_INFO("action finished :%s",state.toString().c_str());

}
//未完成
else{
  ROS_INFO("action did not finished");
}
 return 0;
}

2、可视化 marker with rviz

cd catkin_ws/src
catkin_create_pkg using_markers_rviz roscpp visualization_msgs
创建一些发布立体形状的发布者  rviz来订阅  在rviz中显示

基本形状显示   立方体形状   sphere 球体   arrow 箭头  cylinder 圆柱体

//basicShapes.cpp

 #include <ros/ros.h>                    //系统文件 
#include <visualization_msgs/Marker.h>  // 可视化物体 marker类型 visualization_msgs::Marker

int main( int argc, char** argv )
{                        //节点名称
  ros::init(argc, argv, "basic_shapes"); //初始化ros系统 注册节点
  ros::NodeHandle nh;                    //在节点管理器中注册节点
  ros::Rate rate(2);                     //发布频率(控制 立体形状变换速度)
  //
  ros::Publisher marker_pub;             //创建发布者       话题名字             序列大小
  marker_pub = nh.advertise<visualization_msgs::Marker>("visualization_marker", 1);
  //立方体形状  shape
  uint32_t shape = visualization_msgs::Marker::CUBE;//立方体形状 

  while (ros::ok())
  {
    //可视化形状 消息 
    visualization_msgs::Marker marker;
    // Set the frame ID and timestamp.  See the TF tutorials for information on these.
    //坐标系
    marker.header.frame_id = "/my_frame";//立体形状的参考系   在rviz中 Fixed Frame 需要选择为/my_frame
    //时间戳
    marker.header.stamp = ros::Time::now();
// %Tag(NS_ID)%
    //           命名空间
    marker.ns = "basic_shapes";
    //标志号
    marker.id = 0;
//形状 初始 为//立方体形状   sphere 球体   arrow 箭头  cylinder 圆柱体
    marker.type = shape;  //形状标志变量
    //操作 添加ADD 去除DELETE   去除全部 DELETEALL
    marker.action = visualization_msgs::Marker::ADD;
    //姿态    位置position   方向orientation
    marker.pose.position.x = 0;
    marker.pose.position.y = 0;
    marker.pose.position.z = 0;
    marker.pose.orientation.x = 0.0;
    marker.pose.orientation.y = 0.0;
    marker.pose.orientation.z = 0.0;
    marker.pose.orientation.w = 1.0;
//大小   单位米
    marker.scale.x = 1.0;
    marker.scale.y = 1.0;
    marker.scale.z = 1.0;
//颜色 红绿蓝 透明度
    marker.color.r = 0.0f;
    marker.color.g = 1.0f;
    marker.color.b = 0.0f;
    marker.color.a = 1.0;
    //有效时间  ros::Duration();不会自动删除
    marker.lifetime = ros::Duration();
    // 发布 形状
    while (marker_pub.getNumSubscribers() < 1)
    {  //没有订阅者
      if (!ros::ok())
      {
        return 0;
      }
      ROS_WARN_ONCE("Please create a subscriber to the marker");
      sleep(1);
    }
    marker_pub.publish(marker);

    switch (shape)
    {
    case visualization_msgs::Marker::CUBE://立方体
      shape = visualization_msgs::Marker::SPHERE;//球体
      break;
    case visualization_msgs::Marker::SPHERE://球体
      shape = visualization_msgs::Marker::ARROW;//箭头
      break;
    case visualization_msgs::Marker::ARROW://箭头
      shape = visualization_msgs::Marker::CYLINDER;//圆柱体
      break;
    case visualization_msgs::Marker::CYLINDER://圆柱体
      shape = visualization_msgs::Marker::CUBE;//立方体
      break;
    }
    rate.sleep();
  }
}

//pointsAlines.cpp

//点        线带(条)   线表(线段 排列)
#include <ros/ros.h>                    //系统文件 
#include <visualization_msgs/Marker.h>  // 可视化物体 marker类型 visualization_msgs::Marker
#include <cmath>                        //数学库
int main( int argc, char** argv )
{
  ros::init(argc, argv, "pointsAndLines"); //初始化ros系统 注册节点
  ros::NodeHandle nh;                      //在节点管理器中注册节点
  ros::Publisher marker_pub;               //创建发布者       话题名字           序列大小
  marker_pub = nh.advertise<visualization_msgs::Marker>("visualization_marker", 10);
  ros::Rate rate(30);     //发布频率 控制线条摆动速度
  float f = 0.0;          //浮点变量
  while (ros::ok())
  {
// %Tag(MARKER_INIT)%           点        线带(条)   线表(线段 排列)
    visualization_msgs::Marker points, line_strip, line_list;
    //参考坐标系
    points.header.frame_id = line_strip.header.frame_id = line_list.header.frame_id = "/my_frame";
    //时间戳
    points.header.stamp = line_strip.header.stamp = line_list.header.stamp = ros::Time::now();
    //命名空间
    points.ns = line_strip.ns = line_list.ns = "points_and_lines";
    //操作 添加ADD 去除DELETE   去除全部 DELETEALL
    points.action = line_strip.action = line_list.action = visualization_msgs::Marker::ADD;
    //方向w
    points.pose.orientation.w = line_strip.pose.orientation.w = line_list.pose.orientation.w = 1.0;
    // 标志符号 身份标志
    points.id = 0;
    line_strip.id = 1;
    line_list.id = 2;
    //形状
    points.type = visualization_msgs::Marker::POINTS;
    line_strip.type = visualization_msgs::Marker::LINE_STRIP;
    line_list.type = visualization_msgs::Marker::LINE_LIST;
    // 点尺寸 类似直径大小
    points.scale.x = 0.3;
    points.scale.y = 0.3;
    //线带尺寸 线条
    line_strip.scale.x = 0.1;
    //线表尺寸
    line_list.scale.x = 0.1;
    //点颜色
    points.color.g = 1.0f; //绿色
    points.color.a = 1.0;  //透明度
    //线带颜色
    line_strip.color.b = 1.0; //蓝色
    line_strip.color.a = 1.0;
    //线表颜色
    line_list.color.r = 1.0;  //红色
    line_list.color.a = 1.0;

    // Create the vertices for the points and lines
    for (uint32_t i = 0; i < 200; ++i)  //i 越大 线越长
    {
      float y = 5 * sin(f + i / 100.0f * 2 * M_PI);
      float z = 5 * cos(f + i / 100.0f * 2 * M_PI);

      geometry_msgs::Point p;
      p.x = (int32_t)i - 50;//线性增长  
      p.y = y;
      p.z = z;

      points.points.push_back(p);
      line_strip.points.push_back(p);

      // 两点确定一条直线  线段序列
      line_list.points.push_back(p);
      p.z += 3.0;//改变线段长度
      line_list.points.push_back(p);
    }
// %EndTag(HELIX)%

    while (marker_pub.getNumSubscribers() < 1)
    {  //没有订阅者
      if (!ros::ok())
      {
        return 0;
      }
      ROS_WARN_ONCE("Please create a subscriber to the marker");
      sleep(1);
    }
    marker_pub.publish(points);
    marker_pub.publish(line_strip);
    marker_pub.publish(line_list);

    rate.sleep();
    f += 0.04;
  }
}

2、交互式 marker 可交互操作的 立体形状

rviz 和 交互式marker服务器 订阅 node发布的消息,
当有鼠标等交互式操作时,交互式marker服务器更新消息至话题
允许系统自带 节点
rosrun interactive_marker_tutorials basic_controls
rviz
1)、Globai Options 中的Fixed Frame 可选 参考坐标系有:
bsse_link(固定参考系) moving_frame(上下移动参考系) rotating_frame(旋转参考系)

2)、ADD 添加 Interactive Markers 显示物体
3)、在出现的Interactive Markers 下设置 Update Topic  参数

设置为 /basic_controls/update

系统自带的除了basic_controls 还有:cube menu point_cloud  pong selection  simple_marker 等

在线更新参数服务器的值

打开参数配置面板
rosrun rqt_reconfigure rqt_reconfigure
或者 rosrun rqt_gui rqt_gui(所有配置面板) >>>plugins(插件)>>>Configuation>>>Dynamic reconfigure
只有节点使用了rqt_reconfigure API 才支持在 rqt_reconfigure GUI 窗口中更改参数
如 Navigation包支持 turtlesim 不支持

编写带有rqt_reconfigure API的节点  让节点支持在线更新参数

1、创建配置文件 .cfg file
catkin_create_pkg --rosdistro ROSDISTRO dynamic_tutorials rospy roscpp dynamic_reconfigure
ROSDISTRO  是ros发行版本
		      ros版本      包名               依赖
catkin_create_pkg --rosdistro indigo dynamic_tutorials rospy roscpp dynamic_reconfigure
 cd dynamic_tutorials
 
 mkdir cfg
#Tutorials.cfg       
#!/usr/bin/env python
#20170318
#auther:wanyouwen ewenwan
PACKAGE_NAME = "dynamic_tutorials"#package name  initialize ros system
NODE_NAME = "dynamic_tutorials"
GENERATE_FILE_PREFIX_NAME = "Tutorials" # e.g. "<name>Config.h" for c++, or "<name>Config.py" for python. 
from dynamic_reconfigure.parameter_generator_catkin import *  # import the parameter generator support
gen = ParameterGenerator() #define a parameter generator
#The add function adds a parameter to the list of parameters
#type - defines the type of value stored, and can be any of int_t, double_t, str_t, or bool_t 
         # name         type    level  description  default value  min_value  max_value 
gen.add("int_param",    int_t,    0, "An Integer parameter", 50,  0, 100)
gen.add("double_param", double_t, 0, "A double parameter",    .5, 0,   1)
gen.add("str_param",    str_t,    0, "A string parameter",  "Hello World")
gen.add("bool_param",   bool_t,   0, "A Boolean parameter",  True)
#define an integer whose value is set by an enum(mei ju)
#                        constant  name         type  level   discription  
size_enum = gen.enum([ gen.const("Small",      int_t, 0, "A small constant"),
                       gen.const("Medium",     int_t, 1, "A medium constant"),
                       gen.const("Large",      int_t, 2, "A large constant"),
                       gen.const("ExtraLarge", int_t, 3, "An extra large constant")],
                       "An enum to set size") #discription
#The add function adds a parameter to the list of parameters
      # name    type level  description          default value  min_value  max_value  edit_method(drop-down box selection)
gen.add("size", int_t, 0, "A size parameter which is edited via an enum", 1, 0, 3, edit_method=size_enum)
#tells the generator to generate the necessary files and exit the program.
#                   packagename   node name    prefix name 
exit(gen.generate(PACKAGE_NAME , NODE_NAME , GENERATE_FILE_PREFIX_NAME))
为文件添加可执行权限  a:所有的用户  + 添加  x:execute 执行权限
chmod a+x cfg/Tutorials.cfg

修改 CMakeLists.txt 文件

#add dynamic reconfigure api
#find_package(catkin REQUIRED dynamic_reconfigure)
generate_dynamic_reconfigure_options(
  cfg/Tutorials.cfg
  #...
)
# make sure configure headers are built before any node using them
add_dependencies(example_node ${PROJECT_NAME}_gencfg)
  注意  example_node为需要使用 动态配置文件的节点名

建立一个节点

//dynamic_config_server.cpp

#include <ros/ros.h>//系统文件
#include <dynamic_reconfigure/server.h> //参数动态更新服务器
#include <dynamic_param_reconfig/TutorialsConfig.h> //.cfg文件 生成的头文件
// 参数一经更改 会调用 该毁掉函数修改相应参数
void callback(dynamic_tutorials::TutorialsConfig &config, uint32_t level) {
  //鼠标选定点击滑动等操作改变参数松开后 调用此函数想控制台发送信息
  ROS_INFO("Reconfigure Request: %d %f %s %s %d", 
            config.int_param, config.double_param, //整形参数 浮点型参数
            config.str_param.c_str(),              //字符串型参数
            config.bool_param?"True":"False",      //布尔型参数
            config.size); }                        //大小

int main(int argc, char **argv) {
   //初始化ros系统 注册节点  节点名 
   ros::init(argc, argv, "dynamic_tutorials");
   //定义动态参数调整服务器
   dynamic_reconfigure::Server<dynamic_tutorials::TutorialsConfig> server;
   //定义动态参数调节回调函数 重载回调函数
   dynamic_reconfigure::Server<dynamic_tutorials::TutorialsConfig>::CallbackType f;
   f = boost::bind(&callback, _1, _2);
   server.setCallback(f);//设置回调函数
   ROS_INFO("Spinning node"); //开始主循环
   ros::spin();
   return 0;
  }

七、网络通信 机器人之间 机器人与主机之间

    1、同步时间
      主机和机器人两边安装chrony 
      chrony 会同步成网络服务器的时间
      sudo apt-get install chrony   //同步网络时间
      chrony 安装后会自动启动
    2、局域网下  依据主机名(hostname)通信 Zeroconf
      hostname
      >>> ewenwan-Lenovo-G480
      Zeroconf hostname 即为: ewenwan-Lenovo-G480.local
      机器人那一端 my_robot.local
      尝试链接
      机器人端  ping ewenwan-Lenovo-G480.local
      主机端    ping my_robot.local
    
     若出现 "unknown host" 错误   重启 avahi-daemon 进程
     sudo service avahi-daemon restart
    
    3、ros主从机设置方法
    ros网络只允许一个 roscore 出现 即ros主机 的roscore进程
    其他都只能作为 从机 设置 ROS_MASTER_URI 环境变量 指向 ros 主机
    一般设置机器人为 ros主机
    设置为ros主机方法
    机器人端(ros主机):
     export ROS_HOSTNAME=my_robot.local   //设置 ROS_HOSTNAME 环境变量为相应的主机名
     roscore                              //运行roscore        
     pc端ros从机:
     export ROS_HOSTNAME=my_desktop.local  ////设置 ROS_HOSTNAME 环境变量为相应的主机名
     export ROS_MASTER_URI=http://my_robot.local:11311    //设置 ROS_MASTER_URI 环境变量 指向 ros 主机
     
     4、同步時間
     sudo ntpdate -b my_robot.local
     在pc端ros从机: 查看话题列表
     rostopic list
     >>>>> 
    /rosout             //此话题为 ros主机 roscore 运行 同步得到
    /rosout_agg
    
    
    5、将主从机设置到系统启动项中  ~/.bashrc
      //加入到 ~/.bashrc最后
     机器人端(ros主机):
     export ROS_HOSTNAME=my_robot.local   //设置 ROS_HOSTNAME 环境变量为相应的主机名
     roscore                              //运行roscore        
     pc端ros从机:
     export ROS_HOSTNAME=my_desktop.local  ////设置 ROS_HOSTNAME 环境变量为相应的主机名
     export ROS_MASTER_URI=http://my_robot.local:11311    //设置 ROS_MASTER_URI 环境变量
    
    
    6、联机的多台ros系统 运行的node  都会在 单个 ros系统中查询到
    
    7、 pc端远程登录 机器人端
        ssh my_robot.local
        On the robot (via ssh):
  export ROS_HOSTNAME=my_robot.local   //设置主机
  roscore &                            //运行roscore  注意后面的 & 可以启动launch文件 不用再开一个 ssh登录
  roslaunch my_robot startup.launch    //启动launch文件
        
        8、pc端启动
          export ROS_HOSTNAME=my_desktop.local
          export ROS_MASTER_URI=http://my_robot.local:11311     //设置链接的主机
          rosrun rviz rviz -d `rospack find rbx1_nav`/nav.rviz  //打开3d可视化
          
    9、ros基本概念
       一个节点通常发布传感器数据消息到话题上,也可以为其他节点提供服务(打开/关闭led灯 返回导航计划 返回位置信息)
       例如 物体追踪节点  在一系列video话题上订阅 照片信息 经过处理 发布 移动控制命令 到相应的执行机构订阅的话题上
       ros 数据测量  距离通常以 米 为单位  角度通常一弧度为单位  0~2*pi  代表0~360度

安装云端 软件仓库获取更新工具 SVN, Git, and Mercurial

       sudo apt-get install git subversion mercurial
       1) SVN
       获取软件包 svn checkout
       cd ~/catkin_ws/src
       svn checkout http://repository/svn/package_name
       cd ~/catkin_ws
       catkin_make
       source devel/setup.bash
       rospack profile

      更新 svn update
     cd ~/catkin_ws/src/package_name
     svn update
     cd ~/catkin_ws
     catkin_make
     source devel/setup.bash
2) Git
      获取软件包 git clone
       cd ~/catkin_ws/src
       git clone git://repository/package_name
       cd ~/catkin_ws
       catkin_make
       source devel/setup.bash
       rospack profile
   更新 git pull
   cd ~/catkin_ws/src/package_name
   git pull
   cd ~/catkin_ws
   catkin_make
   source devel/setup.bash
   
   
   3) Mercurial
   获取 软件包 hg clone
   cd ~/catkin_ws/src
   hg clone http://repository/package_name
   cd ~/catkin_ws
   catkin_make
   source devel/setup.bash
   rospack profile
   更新 hg update
   cd ~/catkin_ws/src/package_name
   hg update
   cd ~/catkin_ws
   catkin_make
   source devel/setup.bash
   
   4) 移除软件包
   移除某个
 cd ~/catkin_ws/src
 \rm -rf my_catkin_package
 
 移除catkin 编译以来环境
       cd ~/catkin_ws
   \rm -rf devel build install     //不能加入src(源文件包)
   catkin_make
   source devel/setup.bash
   
   
   ros支持arduino的包
   cd catkin_ws/src
   git clone git://github.com/ros-drivers/rosserial.gitgit
   cd catkin_ws
   catkin_make
   source devel/setup.bash
   rospack profile
   
   人脸识别的包:
   cd catkin_ws/src
   git clone https://github.com/procrob/procrob_functional.git --branch catkin
   cd ~/catkin_ws
   catkin_make
   source ~/catkin_ws/devel/setup.bash
   
   
   训练数据 图像  data
   训练数据 列表   train.text file     train.txt file(特殊格式 可以看懂的)  人序列 名字  照片
   项目会训练一个人脸识别数据库 存放在  facedata.xml 文件里
   
   人脸检测使用 人脸检测数据库 haarcascade_frontalface_alt.xml

八、 人脸识别包 face_recognition

1、   有一个人脸识别服务器   Fserver

roscd face_recognition
rosrun face_recognition Fserver

消息  FaceRecognitionGoal
 消息类型 int order_id   string order_argument
	      模式                名字
 order_id = 0       检测识别第一张人脸 当置信度大于置信度阈值时  向客户端 返回 人名字 和 置信度(可信度)
 order_id = 1       循环检测识别人脸   当置信度大于置信度阈值时  向客户端 返回 人名字 和 置信度(可信度)
 order_id = 2 and order_argument = 'person_name'
		    添加训练照片素材数据  从摄像头捕获照片放置于 /data下  在 train.txt 添加新数据列表
		    当照片素材达到参数add_face_number指定的人脸数量时 结束
 order_id = 3       训练新的 人脸识别 数据库 facedata.xml
 order_id = 4       退出

订阅话题      
/camera/image_raw (standard ROS image transport) 
A video stream    照相机节点 发布数据到 /camera/image_raw  话题上

可配置 参数 列表

置信度阈值 confidence_value  (double, default = 0.88) 
屏幕输出   show_screen_flag (boolean, default = true)
指定 order_id = 2  的采集数量      add_face_number (int, default = 25)

  
  2、人脸识别客户端 Fclient
订阅的话题 fr_order (face_recognition/FRClientGoal) 
FRClientGoal 消息  order_id and an order_argument
指定 Fserver 的工作模式
当 话题fr_order上收到消息后  Fclient 向 Fserver 发送识别模式
Fserver 将识别结果 发送给 Fclient ,Fclient 将结果打印输出至屏幕 (控制台 console)


  3、步骤
   
1、安装 照相机驱动程序 获取视频流 usb_cam
  cd catkin_ws/src
  git clone git://github.com/bosch-ros-pkg/usb_cam.git
  catkin_make
  source ~/catkin_ws/devel/setup.bash

  其他也有 cv_camera
  cd catkin_ws/src
  git clone git://github.com/OTL/cv_camera.git
  catkin_make
  source ~/catkin_ws/devel/setup.bash



  2、运行roscore

  3、启动照相机 获取视频流 发布到 /usb_cam/image_raw 等话题
      包      节点    参数重映射(remap) 特定话题名 Fserver订阅的话题     <usb_cam_height>  <usb_cam_width>
rosrun usb_cam usb_cam_node usb_cam_node/image_raw:=camera/image_raw _image_height:=800 _image_width:=800
上面的命令行好像有问题,用下面的launch文件

或者launch文件      

 <launch>
  <node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen" >
    <param name="video_device" value="/dev/video0" />
    <param name="image_width" value="640" />
    <param name="image_height" value="480" />
    <param name="pixel_format" value="yuyv" />
    <param name="camera_frame_id" value="usb_cam" />
    <param name="io_method" value="mmap"/>
    <remap from="/usb_cam/image_raw" to="camera/image_raw"/>
  </node>
  <node name="image_view" pkg="image_view" type="image_view" respawn="false" output="screen">
    <remap from="image" to="camera/image_raw"/>
    <param name="autosize" value="true" />
  </node>
</launch>

   
   4、运行人脸识别服务器和客户端
   rosrun face_recognition Fserver
   rosrun face_recognition Fclient

    或编写launch文件
    
<launch>
  <node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen" >
    <param name="video_device" value="/dev/video0" />
    <param name="image_width" value="640" />
    <param name="image_height" value="480" />
    <param name="pixel_format" value="yuyv" />
    <param name="camera_frame_id" value="usb_cam" />
    <param name="io_method" value="mmap"/>
    <remap from="/usb_cam/image_raw" to="camera/image_raw"/>
  </node>
  <node name="Fserver" pkg="face_recognition" type="Fserver" output="screen" launch-prefix="xterm -e" >
    <param name="confidence_value" value="0.66" />
    <param name="add_face_number" value="50" />
  </node>
  <node name="Fclient" pkg="face_recognition" type="Fclient" output="screen" launch-prefix="xterm -e"/>
  <node name="image_view" pkg="image_view" type="image_view" respawn="false" output="screen">
    <remap from="image" to="camera/image_raw"/>
    <param name="autosize" value="true" />
  </node>
</launch>

        
   
  5、添加训练数据
   
            发送一次  话题           消息数据               order_id   order_argument
   rostopic pub -1 /fr_order face_recognition/FRClientGoal -- 2 "xiaowen"
   
  6、开始训练 新的 识别器
   rostopic pub -1 /fr_order face_recognition/FRClientGoal -- 3 "none"
   
  7、开始识别 人脸
   rostopic pub -1 /fr_order face_recognition/FRClientGoal -- 1 "none"    //循环识别
   
   
  8、重复 5 6 7 可以识别新的人脸
  9、退出
   rostopic pub -1 /fr_order face_recognition/FRClientGoal -- 4 "none"

猜你喜欢

转载自blog.csdn.net/xiaoxiaowenqiang/article/details/84852500