Bootstrap

ROS Beginner(长文预警!

ROS Beginner

引言

本文是适用于ROS初学者的笔记,包含从基本概念到尝试创建服务端和客户端的内容。

参考网站:http://wiki.ros.org/cn/ROS/Tutorials

1.创建一个catkin工作空间

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/
catkin_make(编译)
source devel/setup.bash

2.文件系统

软件包:Packages,ROS代码的软件组织单元
Mainifests:package.xml清单是对软件包的描述,定义软件包之间的依赖关系和元信息
roscd:允许直接切换目录到某个软件包或者软件包集当中,或者子目录中
roscd log:进入存储ROS日志文件的目录
rosls [locationname[/subdir]]:直接按照软件包的名称执行ls命令

3.创建软件包

软件包的规范:

1)必须有一个package.xml文件,提供有关软件包的元信息

2)必须有一个CMakeLists.txt文件

3)必须有自己的目录(意味着在同一个目录下不能有嵌套的或者多个软件包存在?

cd ~/catkin_ws/src
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
(catkin_create_pkg <package_name> [depend1] [depend2] [depend3] ...)
cd ~/catkin_ws
catkin_make
source ~/catkin_ws/devel/setup.bash(将这个工作空间添加到ROS环境中,似乎只对该终端生效)
rospack查看依赖关系:(rosdep有问题 = =
rospack depends1 beginner_tutorials(查看一级依赖
rospack depends1 rospy(依赖包自己的依赖关系
rospack depends beginner_tutorials(递归的检查出所有依赖关系

4.构建ROS软件包

catkin_make进行构建

cd ~/catkin_ws/
catkin_make(得到的build目录是构建空间的默认位置,devel是开发空间的默认位置,可以存放可执行文件和库

5.理解ROS节点

计算图(Computation Graph)是一个由ROS进程组成的点对点网络,它们能够共同处理数据
节点(Nodes):节点是一个可执行文件,它可以通过ROS(使用客户端库)来与其他节点进行通信。
消息(Messages):订阅或发布话题时所使用的ROS数据类型。
话题(Topics):节点可以将消息发布到话题,或通过订阅话题来接收消息。
主节点(Master):ROS的命名服务,例如帮助节点发现彼此。
rosout:在ROS中相当于stdout/stderr(标准输出/标准错误)
roscore:主节点 + rosout + 参数服务器
rosnode:获取节点信息,rosnode list看当下节点,用rosnode info /rosout查看某节点信息(此处为/rosout
rosrun可以用包名直接运行某节点,如rosrun turtlesim turtlesim_node
重新分配节点名称:rosrun turtlesim turtlesim_node __name:=my_turtle

6.理解ROS话题

首先打开小乌龟和它的键盘控制:

rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key

turtlesim_node节点和turtle_teleop_key节点之间是通过一个ROS话题来相互通信的:turtle_teleop_key在话题上发布键盘按下的消息,turtlesim则订阅该话题以接收消息
rqt_graph查看当前运行的节点和话题:rosrun rqt_graph rqt_graph
rostopic:获取ROS话题的信息
rostopic echo [topic]显示某个话题发布的数据(此时rostopic echo 也订阅了该话题
rostopic list列出当前已被订阅和发布的所有话题
消息:为了使发布者和订阅者进行通信,必须接受和发送相同类型的消息,即话题的类型是由发布在它上面消息的类型决定的

rostopic type [topic](查看话题的消息类型
rosmsg show [消息类型](查看消息的详细信息
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]'
-1表示只发出一条消息,(-r表示循环)后面依次是话题名称,消息类型,YAML语法的参数消息
rostopic hz [topic]:报告数据发布的速率
rqt_plot:在滚动时间图上显示发布到某个话题上的数据(rosrun rqt_plot rqt_plot

7.理解ROS服务和参数

Services服务:节点间通讯的一种方式。节点可以发送一个请求并且获得一个响应
rosservice list:显示活跃服务的信息
rosservice type [service]:查看服务的类型
rosservice call [service] [args]:调用服务
rosservice type /spawn | rossrv show:显示产卵服务的信息(后面加的可以使显示参数的信息?
rosparam:让我们在ROS参数服务器(Parameter Server)上存储和操作数据(使用YAML语法)
数据类型:整型(integer)、浮点型(float)、布尔(boolean)、字典(dictionaries)和列表(list)
rosparam list:列出参数名
rosparam set [param_name]:设置参数
rosparam get [param_name]:获取参数
rosparam load [file_name] [namespace]:从文件中加载参数
rosparam dump [file_name] [namespace]:向文件中转储参数
rosparam delete :删除参数

8.rqt_console和roslaunch

rqt_console连接到了ROS的日志框架,以显示节点的输出信息
rqt_logger_level允许我们在节点运行时改变输出信息的详细级别,包括Debug、Info、Warn和Error

rosrun rqt_console rqt_console
rosrun rqt_logger_level rqt_logger_level
rosrun turtlesim turtlesim_node(启动turulesim

日志记录级别:
Fatal (致命)
Error (错误)
Warn (警告)
Info (信息)
Debug (调试)
使用roslaunch:

roscd beginner_tutorials(切换道之前构建的软件包目录下
mkdir launch
cd launch

p.s.存放launch文件的目录不一定非要命名为launch,事实上都不用非得放在目录中,roslaunch命令会自动查找经过的包并检测可用的启动文件。然而,这种推荐的标准做法被认为是最佳实践
创建一个名为turtlemimic.launch的launch文件并复制粘贴以下内容:

<launch>(标签)

  <group ns="turtlesim1">(ns:namespace)
    <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
  </group>(分组1)

  <group ns="turtlesim2">
    <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
  </group>(分组2)(两个分组节点名相同sim,可以同时启动两个turtlesim模拟器,避免命名冲突

  <node pkg="turtlesim" name="mimic" type="mimic">
    <remap from="input" to="turtlesim1/turtle1"/>
    <remap from="output" to="turtlesim2/turtle1"/>
  </node>(启动模仿节点,让turtlesim2模仿turtlesim1)

</launch>(使得launch文件的XML标签闭合)

运行launch文件:roslaunch beginner_tutorials turtlemimic.launch
rostopic pub /turtlesim1/turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'(给两个turtlesim发布命令,可以看到两个都在转转转)
此时可以通过rqt_graph更好的理解launch文件做的事情(或者运行rqt并在主窗口中选择Plugins > Introspection > Node

9.使用rosed

rosed [package_name] [filename]:直接通过软件包名编辑包中的文件,而无需键入完整路径
更改编辑器:在.bashrc文件中加入export EDITOR='emacs -nw'或者export EDITOR='nano -w'

10.创建消息和服务

msg(消息):msg文件就是文本文件,用于描述ROS消息的字段。它们用于为不同编程语言编写的消息生成源代码,放在软件包的msg目录下
srv(服务):一个srv文件描述一个服务。它由两部分组成:请求(request)和响应(response),中间用------线隔开,存放在软件包的srv目录下。

roscd beginner_tutorials
mkdir msg
echo "int64 num" > msg/Num.msg(创建了一个只有一行的msg文件)

打开package.xml, 确保它包含以下两行且没有被注释

<build_depend>message_generation</build_depend>(构建需要)
<exec_depend>message_runtime</exec_depend>(运行需要)

在CMakeLists.txt文件中,为已经存在里面的find_package调用添加message_generation依赖项:(message_generationmsgsrv都适用)

find_package(catkin REQUIRED COMPONENTS
   roscpp
   rospy
   std_msgs
   message_generation
)

注意:有时即使没有使用全部依赖项调用find_package,项目也可以构建。这是因为catkin把你所有的项目整合在了一起,因此如果之前的项目调用了find_package,你的依赖关系也被配置成了一样的值。但是,忘记调用意味着你的项目在单独构建时很容易崩溃。
确保导出消息时的依赖关系:

catkin_package(
  ...
  CATKIN_DEPENDS message_runtime ...
  ...)

找到代码块:

# add_message_files(

#   FILES

#   Message1.msg

#   Message2.msg

# )

取消注释,并且改成这样:

add_message_files(
  FILES
  Num.msg
)

取消下面几行注释:(确保CMake知道何时需要重新配置项目)

# generate_messages(

#   DEPENDENCIES

#   std_msgs

# )

以上完成了创建消息,现在通过rosmsg show命令看看ROS能否识别它
rosmsg show [message type]
即:rosmsg show beginner_tutorials/Num(包名可省)
创建src

roscd beginner_tutorials
mkdir srv
roscp:将文件从一个包复制到另一个包
roscp [package_name] [file_to_copy_path] [copy_path]
roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv(从rospy_tutorials包中复制一个服务)

注意CMakeLists.txt文件中为find_package调用添加message_generation依赖项

# add_service_files(

#   FILES

#   Service1.srv

#   Service2.srv

# )

变成:

add_service_files(
  FILES
  AddTwoInts.srv
)

看能否识别它:rossrv show <service type>
即:rossrv show beginner_tutorials/AddTwoInts(也可以不指定包命)
重新编译一下软件包:

roscd beginner_tutorials
cd ../..
catkin_make
cd -

注意:msg目录中的任何.msg文件都将生成所有支持语言的代码。

C++消息的头文件将生成在~/catkin_ws/devel/include/beginner_tutorials/。

Python脚本将创建在~/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/msg。

而Lisp文件则出现在~/catkin_ws/devel/share/common-lisp/ros/beginner_tutorials/msg/。

类似地,srv目录中的任何.srv文件都将生成支持语言的代码。对于C++,头文件将生成在消息的头文件的同一目录中。对于Python和Lisp,会在msg目录旁边的srv目录中。

11.编写简单的发布者和订阅者

C++

talker节点(发布者):不断广播消息

roscd beginner_tutorials
mkdir src

创建talker.cpp文件,文本如下:

#include "ros/ros.h"(一个头文件,包含了ROS系统中常见的公共部分所需的头文件)
#include "std_msgs/String.h"(引用了std_msgs中的std_msg/String消息)
#include <sstream>

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

  ros::init(argc, argv, "talker");(初始化ROS,使得ROS可以通过命令行进行名称重映射,给节点指定名称)(名称必须是基本名称,不能有/)

  ros::NodeHandle n;(为这个进程的节点创建句柄。创建的第一个NodeHandle实际上将执行节点的初始化,而最后一个被销毁的NodeHandle将清除节点所使用的任何资源。)

  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);(告诉主节点我们要在chatter的话题上发布一个类型是std_msgs/String的消息,主节点会告诉订阅了chatter1的节点,第二个参数表示缓存队列大小为1000,越界则会丢弃旧消息)
(NodeHandle::advertise()返回一个ros::Publisher对象,它有2个目的:其一,它包含一个publish()方法,可以将消息发布到创建它的话题上;其二,当超出范围时,它将自动取消这一宣告操作)

  ros::Rate loop_rate(10);(指定循环的频率,记录上次调用sleep到现在有多长时间,并且休眠正确的时间)

  int count = 0;
  while (ros::ok()){
(ros:ok返回false的情况:
	收到SIGINT信号(Ctrl+C)
	被另一个同名的节点踢出了网络
	ros::shutdown()被程序的另一部分调用
	所有的ros::NodeHandles都已被销毁 )
    std_msgs::String msg;

    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();

    ROS_INFO("%s", msg.data.c_str());
    chatter_pub.publish(msg);(把这个信息广播给了任何已连接的节点

    ros::spinOnce();(回调)

    loop_rate.sleep();(使用ros::Rate在剩下的时间内睡眠,以让我们达到10Hz的发布速率??)
    ++count;
  }

  return 0;
}

订阅者节点:
创建一个listener.cpp文件,写入以下内容

#include "ros/ros.h"
#include "std_msgs/String.h"

void chatterCallback(const std_msgs::String::ConstPtr& msg){
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}(回调函数,智能指针传递消息)

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

  ros::init(argc, argv, "listener");

  ros::NodeHandle n;

  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);(通过主节点订阅话题,第二个参数是队列大小,该对象被析构时自动取消订阅)

  ros::spin();(启动了一个自循环,它会尽可能快地调用消息回调函数)

  return 0;
}

在CMakeLists.txt文件底部加上这些内容:

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)(为可执行目标添加依赖项到消息生成目标,确保在使用此包之前生成了该包的消息头)

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)

最终长这样:

cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)

## Find catkin and any catkin packages

find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)

## Declare ROS messages and services

add_message_files(FILES Num.msg)
add_service_files(FILES AddTwoInts.srv)

## Generate added messages and services

generate_messages(DEPENDENCIES std_msgs)

## Declare a catkin package

catkin_package()

## Build talker and listener

include_directories(include ${catkin_INCLUDE_DIRS})

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)

进行编译:

cd ~/catkin_ws
catkin_make

12.测试发布者和订阅者

运行发布者:

roscore
cd ~/catkin_ws
source ./devel/setup.bash(请确保调用catkin_make后已经source过工作空间的setup.*sh文件)
rosrun beginner_tutorials talker      # (C++)

运行订阅者:

 rosrun beginner_tutorials listener     # (C++)

13.编写简单的服务和客户端

编写服务节点:
创建简单的服务(Service)节点add_two_ints_server,该节点将接收两个整数,并返回它们的和。

roscd beginner_tutorials

在beginner_tutorials包中创建src/add_two_ints_server.cpp文件并粘贴以下内容进去:

#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"

bool add(beginner_tutorials::AddTwoInts::Request  &req,
beginner_tutorials::AddTwoInts::Response &res){
  res.sum = req.a + req.b;
  ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
  ROS_INFO("sending back response: [%ld]", (long int)res.sum);
  return true;
}(该函数提供了AddTwoInts服务,它接受srv文件中定义的请求(request)和响应(response)类型,并返回一个布尔值)

int main(int argc, char **argv)
{
  ros::init(argc, argv, "add_two_ints_server");
  ros::NodeHandle n;

  ros::ServiceServer service = n.advertiseService("add_two_ints", add);
  ROS_INFO("Ready to add two ints.");
  ros::spin();

  return 0;
}

编写客户端节点:
在beginner_tutorials包中创建src/add_two_ints_client.cpp文件并粘贴以下内容进去:

#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>

int main(int argc, char **argv){
  ros::init(argc, argv, "add_two_ints_client");
  if (argc != 3){
    ROS_INFO("usage: add_two_ints_client X Y");
    return 1;
  }

  ros::NodeHandle n;

  ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");(为add_two_ints服务创建一个客户端。ros::ServiceClient对象的作用是在稍后调用服务)

  beginner_tutorials::AddTwoInts srv;(实例化一个服务类)

  srv.request.a = atoll(argv[1]);
  srv.request.b = atoll(argv[2]);(为两个成员赋值)

  if (client.call(srv)){
    ROS_INFO("Sum: %ld", (long int)srv.response.sum);
  }
  else{
    ROS_ERROR("Failed to call service add_two_ints");
    return 1;
  }

  return 0;
}

更改CMakeLists.txt(添加代码):

add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server beginner_tutorials_gencpp)

add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client beginner_tutorials_gencpp)

编译:

cd ~/catkin_ws
catkin_make

14.检验简单的服务与客户端

运行服务:

rosrun beginner_tutorials add_two_ints_server

运行客户端:

rosrun beginner_tutorials add_two_ints_client 1 3

15.录制和回放数据

将正在运行的ROS系统中的数据记录到一个bag文件中,然后通过回放这些数据来来重现相似的运行过程
录制数据:

roscore
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key(又打开了小乌龟)
mkdir ~/bagfiles
cd ~/bagfiles
rosbag record -a(-a表明所有发布的话题都积累在一个bag文件中)

随便控制小乌龟移动一会儿
回放:

rosbag info <your bagfile>(查看bag中的记录,bag名字似乎和时间有关)
rosbag play <your bagfile>(先结束key的进程)
rosbag play -r 2 <your bagfile>(-r可以改变消息发布的频率)(-s可以指定开始时间,使不从头开始)

录制数据子集:

rosbag record命令支持只录制特定的话题到bag文件中,这样就可以只录制用户感兴趣的话题
rosbag record -O subset /turtle1/cmd_vel /turtle1/pose
(-O参数告诉rosbag record将数据记录到名为subset.bag的文件中,而后面的topic参数告诉rosbag record只能订阅这两个指定的话题)

p.s.无法完美模仿,精度不够

16.从bag文件中读取消息

注:命令前面都有一个time,这样做可以同时输出执行每个命令花费的时间
立即回放消息并在多个终端中查看输出:

time rosbag info demo.bag  (手动检查所有已发布的话题,以及向每个话题发布了多少消息)
rostopic echo /obs1/gps/fix | tee topic1.yaml(订阅/obs1/gps/fix话题并复读该话题上发布的所有内容,同时用tee命令转储到一个yaml格式的文件中以便之后查看)

订阅另一个话题:rostopic echo /diagnostics_agg | tee topic2.yaml
time rosbag play --immediate demo.bag --topics /topic1 /topic2 /topic3 /topicN(回放,–immediate尽可能快)
即:time rosbag play --immediate demo.bag --topics /obs1/gps/fix /diagnostics_agg
可以使用ros_readbagfile脚本提取感兴趣的话题

17.roswtf入门

安装检查:

roswtf可以检查系统并尝试发现问题(首先确保ros没有运行)
roscd rosmaster
roswtf

在线检查:

roscd
roswtf

错误:

roscd
ROS_PACKAGE_PATH=bad:$ROS_PACKAGE_PATH roswtf

最后:在vscode中配置ROS环境:

在workspace中添加文件(devel里面):catkin_make -DCMAKE_EXPORT_COMPILE_COMMANDS=Yes
更改c_cpp_propertries.json为:

{
  "configurations": [
    {
      "browse": {
        "databaseFilename": "",
        "limitSymbolsToIncludedHeaders": true
      },
      "includePath": [
        "/home/nova/catkin_ws/devel/include/**",
        "/opt/ros/kinetic/include/**",
        "/home/nova/catkin_ws/src/beginner_tutorials/include/**",
        "/home/nova/catkin_ws/src/try0/include/**",
        "/usr/include/**"
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "c11",
      "cppStandard": "c++17",
      "compileCommands": "${workspaceFolder}/build/compile_commands.json"
    }
  ],
  "version": 4

}

就可以找到ros.h等头文件啦!

;