跳转至

心口如一,犹不失为光明磊落丈夫之行也。——梁启超

文章目录

😏1. ros环境安装

官网:https://www.ros.org/
中文wiki:http://wiki.ros.org/cn
ubuntu安装ros:http://wiki.ros.org/cn/melodic/Installation/Ubuntu

小叶玩ros的autoware教程 Teemo的autoware教程 松灵机器人autoware演示 自动驾驶仿真 Autoware + Carla Autoware Carla G29 自动驾驶仿真

ros安装最常见的是在ubuntu系统中,有amd64arm64两种,安装流程如下:

  1. 配置Ubuntu软件仓库
  2. 设置sources.list
  3. 设置密钥
  4. 安装ros-distro
  5. 初始化 rosdep(包含rosdep init和rosdep update)
  6. 设置环境
  7. 构建工厂依赖

之前常见的是在第5步出现问题卡住,原因是ros的github仓库国内网络访问缓慢,有这几种解决方法:

  1. 用手机热点更新:rosdep依赖下载的就是从https://github.com/ros/rosdistro这个repo里的yaml文件,大家知道,手机一般是能访问github的,因此rosdep update这步用手机热点一般是能更新成功。
  2. 将rosdistro下载到本地:手动改URL路径,把https://raw.githubusercontent.com/ros替换为file:///home/xxx/Software
  3. 将rosdistro转存到gitee仓库:将https://raw.githubusercontent.com/ros替换为https://gitee.com/ros

后来,一位机器人领域的大佬自制了一个安装工具,让我们终于不用花时间在安装上了,简直是太良心,不用浪费生命了。这就是小鱼一键安装ROS工具

wget http://fishros.com/install -O fishros && . fishros

包含了换源、选择ROS版本等操作选项,极为方便,从此安装ROS不再烦恼!大佬网址在这:http://fishros.com/#/fish_home

当然如果确实想用rosdep工具,也可用以下方法试试:

# 首先下载20-default.list(和运行sudo rosdep init是一样的效果)
sudo mkdir -p /etc/ros/rosdep/sources.list.d/
sudo curl -o /etc/ros/rosdep/sources.list.d/20-default.list https://mirrors.tuna.tsinghua.edu.cn/github-raw/ros/rosdistro/master/rosdep/sources.list.d/20-default.list
# 然后更新配置
echo 'export ROSDISTRO_INDEX_URL=https://mirrors.tuna.tsinghua.edu.cn/rosdistro/index-v4.yaml' >> ~/.bashrc
source ~/.bashrc
# 最后重新执行
rosdep update

ros安装完成后,可通过在命令行输入roscore查看主节点启动信息;

在这里插入图片描述

然后启动小乌龟节点:rosrun turtlesim turtlesim_node

在这里插入图片描述
最后启动键盘控制节点:rosrun turtlesim turtle_teleop_key

在这里插入图片描述

以上是ros的简单示例,通过这个有趣的例子让大家能对ros有基本的认知。

😊2. ros命令与工具

随着ros的流行,相关的学习媒介也越来越多,除了官网和书籍外,还有古月居、创客智造、赵虚左等都有ros的相关教程,这里就不放链接了,网上很容易找到。

ros的基本组成单元是节点。节点之间的通信有消息通信(发布/订阅,msg定义数据结构)、服务通信(请求/响应,srv定义数据结构)、动作通信三种,但最常用的是消息通信,通信的形式一般是话题即/topic。消息传递的理念是:当节点想要分享消息时,可以发布(publish)消息到对应话题;当节点想要接收消息时,可以订阅(subscribe)所需要的话题。

节点之间的话题可以用bag的形式存储下来,并可以重播。当节点内有些参数需要配置时,可以使用参数服务器rosparam来配置。

常用命令:

roscore # 主节点会启动节点管理器
rosrun package_name node_name   # 启动节点
# 显示设置节点名称 __name:=node-name
# 指定命名空间 __ns:=namespace
roslaunch package_name xxx.launch   # 启动多节点
rosnode list    # 查看node列表 rqt_graph 查看节点计算图
rosnode kill    # 终止节点 rosnode cleanup 终止后清除
rostopic list   # 查看topic列表
rosservice list # 查看service列表
rosservice call # 调用服务
rosmsg list     # 查看msg列表
rosparam list   # 查看param列表
rosbag record/play  # 数据包记录/重播
rospack find    # 找到软件包目录
rosls   # 查看目录下的文件
roscd   # 切换到软件包目录
rostopic hz # 发布频率每秒发布的消息数量
rostopic bw # 发布带宽每秒消息所占字节数
roslaunch # 多节点启动有多种参数可调

常用工具:

rviz
rqt
rosrun rqt_publisher rqt_publisher  # rqt界面手动发送数据
gazebo

在这里插入图片描述

😆3. helloworld节点创建

创建ros工程的一般流程如下:

  1. 创建工作空间:mkdir catkin_ws && cd catkin_ws
  2. 创建源文件目录:mkdir src && cd src
  3. 使用catkin_create_pkg命令来创建一个名为beginner_tutorials的新程序包。 这个程序包依赖于std_msgs、roscpp和rospycatkin_create_pkg beginner_tutorials std_msgs rospy roscpp
  4. 返回catkin工程目录并编译:cd ~/catkin_ws && catkin_make

在这里插入图片描述

C++创建ros节点helloworld:

定义msg消息:

Header header
int64 num
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist

找到CMakeLists.txtpackage.xml添加消息的构建与运行(这个要练习并熟悉)。

基于msg消息,编写发布器节点和订阅器节点,实现话题通信。

发布器talker.cpp:

/* 
 * 代码思路如下:
 * 1.初始化ROS系统
 * 2.在ROS网络内广播我们将要在chatter topic上发布std_msgs/String消息
 * 3.以每秒10次的频率在chatter上发布消息
*/ 
##include "ros/ros.h"
##include "std_msgs/String.h"

##include <sstream>

/**
 * This tutorial demonstrates simple sending of messages over the ROS system.
 */
int main(int argc, char **argv)
{
  ros::init(argc, argv, "talker");
  ros::NodeHandle n;
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
  ros::Rate loop_rate(10);  // ros::Rate 可以考虑循环中其他部分消耗的时间

  int count = 0;
  while (ros::ok())
  {
    std_msgs::String msg;
    std::stringstream ss;
    ss << "hello ! ROS world !" << count;
    msg.data = ss.str();
    ROS_INFO("%s", msg.data.c_str());
    chatter_pub.publish(msg);

    ros::spinOnce();
    loop_rate.sleep();
    ++count;
  }

  return 0;
}

订阅器listener.cpp:

/*
 * 1.初始化ROS系统
 * 2.订阅chatter topic
 * 3.进入自循环,等待消息的到达
 * 4.当消息到达,调用chatterCallback()函数
*/
##include "ros/ros.h"
##include "std_msgs/String.h"

/**
 * This tutorial demonstrates simple receipt of messages over the ROS system.
 */
// 回调函数
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();      // 自循环,等待并执行回调函数,相当于在while循环里调用ros::spinOnce()

  return 0;
}

然后运行catkin_make编译,会生成talker和listener两个可执行文件,存储到devel/lib目录。

服务通信类似,用到的时候再学习就行;另外,如果是python玩家,也可以用python实现。

参数服务器(给节点设置参数)参考:

http://t.csdn.cn/pYEpb
http://t.csdn.cn/zyL12

另外,默认ros的编译是以debug模式编译的,如果想打包release,运行命令:

catkin build --cmake-args -DCMAKE_BUILD_TYPE=Release

或者在cmake中设置:SET(CMAKE_BUILD_TYPE "Release")

😏4. 日志消息

程序中用于排查问题的简短文本字符流,即日志消息,根据严重级别,可分为DEBUG INFO WARN ERROR FATAL。输出指令分别为:

ROS_DEBUG_STREAM("")
ROS_INFO_STREAM("")
ROS_WARN_STREAM("")
ROS_ERROR_STREAM("")
ROS_FATAL_STREAM("")
// 若要生成一次性日志消息,加上ONCE
ROS_INFO_STREAM_ONCE("")

rosout节点会默认生成日志文件保存到~/.ros/log中,一段时间后,日志会大于1G,我们可以通过以下命令查看日志大小和清除:

rosclean check
rosclean purge

ros默认会生成INFO级别以上的日志,可通过rqt_logger_level界面来设置日志级别,也可在C++代码中设置:

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

😏5. ros程序打包

在实际工程中,为避免在机器人上直接放源码编译,最好还是打包成deb,再部署到环境中。

有两篇教程,使用bloon和fakeroot来打包程序:

https://juejin.cn/post/7136394709219409933
https://zhuanlan.zhihu.com/p/578951132

但在实际执行bloom-generate rosdebian --os-name ubuntu --ros-distro melodic的时候,遇到个问题,各位博友看有没有遇到过的?

==> Generating debs for ubuntu:bionic for package(s) ['test_msgs']
No homepage set, defaulting to ''
Could not resolve rosdep key 'roscpp'
Try to resolve the problem with rosdep and then continue.
Continue [Y/n]? n
GeneratorError: Error running generator: Failed to resolve rosdep key 'roscpp', aborting.

另外,也有一种简单的打包方式,即通过catkin_make install的方式:

# 编译install(会生成install目录,包含include、lib和share等文件)
catkin_make install
# 然后要将devel中lib目录下的节点名目录拷贝到install中lib目录下
# 最后,如果有config配置文件的,放在install目录中即可
# 依次执行即可

在这里插入图片描述

以上!

ROS机器人操作系统集成

ROS学习入门

一、ROS介绍

ros是机器人操作系统(Robot Operating System)的英文缩写。ROS是用于编写机器人软件程序的一种具有高度灵活性的软件架构。ROS的原型源自斯坦福大学的Stanford Artificial Intelligence Robot (STAIR) 和 Personal Robotics (PR)项目。

ROS的4大优势:

1. 通道ROS提供了一种发布-订阅式的通信框架用以简单快速地构建分布式计算系。——计算图级
2. 工具ROS提供了大量的工具组合用以配置启动自检调试可视化登录测试终止分布式计算系统
3. 强大的库ROS提供了广泛的库文件实现以机动性操作控制感知为主的机器人功能
4. 生态系统ROS的支持与发展依托着一个强大的社区ros.org尤其关注兼容性和支持文档提供了一套一站式的方案使得用户得以搜索并学习来自全球开发者数以千计的ROS程序包。——社区级
另外在ROS三个级中还有一个文件系统级后面会有介绍

主要功能:

1. ROS提供一些标准操作系统服务例如硬件抽象底层设备控制常用功能实现进程间消息以及数据包管理ROS是基于一种图状架构从而不同节点的进程能接受发布聚合各种信息例如传感控制状态规划等等)。
2. ROS可以分成两层低层是上面描述的操作系统层高层则是广大用户群贡献的实现不同功能的各种软件包例如定位绘图行动规划感知模拟等等

二、ROS环境搭建

推荐使用“鱼香ROS”大佬的一键安装命令:wget http://fishros.com/install -O fishros && . fishros

我自己安装的记录:原文链接

安装好后,可通过roscore命令进行主节点测试,将出现以下信息:

    .. logging to /home/ubuntu/.ros/log/cb38e680-dee2-11ea-bae1-70665563e003/roslaunch-nx-1205.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://nx:36773/
ros_comm version 1.14.7


SUMMARY
========

PARAMETERS
 * /rosdistro: melodic
 * /rosversion: 1.14.7

NODES

auto-starting new master
process[master]: started with pid [1215]
ROS_MASTER_URI=http://nx:11311/

setting /run_id to cb38e680-dee2-11ea-bae1-70665563e003
process[rosout-1]: started with pid [1228]
started core service [/rosout]
关于安装ros及rosdep update更新问题
1.目前安装ros基本上是将github的rosdistro转移到gitee实现国内正常安装用上面的一键安装脚本即可),但安装之后rosdep还是不可用的对于后面的如导航需要安装的依赖包会有影响
2.目前解决rosdep update更新问题主要是通过增加代理访问github实现的具体实现这里推荐"ncnynl"大佬的一键工具

具体实现
下载脚本wget https://gitee.com/ncnynl/rosdep/raw/master/rosdep_update.sh ;         
管理员给执行权限sudo chmod +x ./rosdep_update.sh;  
管理员运行脚本sudo ./rosdep_update.sh 
出现这一行代表成功:`all files replaced is finished, please continues run rosdecp update`    
然后执行:`sudo rosdep init`  `rosdep update`即可

三、第一个ROS案例

测试案例——小海龟

第一个终端:roscore

第二个终端:rosrun turtlesim turtlesim_node

第三个终端:rosrun turtlesim turtle_teleop_key

启动完成后,将鼠标放在第三个终端上,就可以键盘控制第二个终端启动的小海龟移动了!

编译案例——空程序包

创建工作空间:mkdir catkin_ws && cd catkin_ws

创建源文件目录:mkdir src

catkin编译:catkin_make

创建案例——新程序包

首先切换到catkin工作空间中的src目录下:cd ~/catkin_ws/src

使用catkin_create_pkg命令来创建一个名为’beginner_tutorials’的新程序包。
这个程序包依赖于std_msgs、roscpp和rospy:
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp

这个文件夹里面包含一个package.xml文件(可查看程序依赖的包)和一个CMakeLists.txt文件。

catkin_make编译通过。

四、ROS文件系统

文件系统概念

1.Packages: 软件包,是ROS应用程序代码的组织单元,每个软件包都可以包含程序库、可执行文件、脚本或者其它手动创建的东西。

2.Manifest (package.xml): 清单,是对于’软件包’相关信息的描述,用于定义软件包相关元信息之间的依赖关系,这些信息包括版本、维护者和许可协议等。

文件系统工具
rospack = ros + pack(age) 
rospack find [package_name]

roscd = ros + cd 
roscd [package_name]

rosls = ros + ls 
rosls [package_name]
rosbag数据录制与回放

值得一提的是rosbag工具的使用。

rosbag record -a
rosbag info <your bagfile>
rosbag play (-r 2/-l) <your bagfile>

五、ROS消息机制

通过ROS计算图来学习ROS的消息通信机制。

计算图概念:
Nodes:节点,一个节点即为一个可执行文件,它可以通过ROS与其它节点进行通信
Messages:消息,消息是一种ROS数据类型,用于订阅或发布到一个话题。
Topics:话题,节点可以发布消息到话题,也可以订阅话题以接收消息。
Master:节点管理器,ROS名称服务 (比如帮助节点找到彼此)
rosout: ROS中相当于stdout/stderr
roscore: 主机+ rosout + 参数服务器 (参数服务器会在后面介绍)
节点概念:
一个节点其实只不过是ROS程序包中的一个可执行文件。
ROS节点可以使用ROS客户库与其他节点通信。
节点可以发布或接收一个话题。
节点也可以提供或使用某种服务。
客户端库概念:

ROS客户端库允许使用不同编程语言编写的节点之间互相通信:

rospy = python 客户端库
roscpp = c++ 客户端库
rosjs = javascripts客户端库
rosjava = java客户端库
节点详解:
### roscore:

roscore是你在运行所有ROS程序前首先要运行的命令。
如果roscore运行后无法正常初始化,很有可能是存在网络配置问题。

### rosnode:
打开一个新的终端, 可以使用 rosnode 像运行roscore一样看看在运行什么...
注意: 当打开一个新的终端时,你的运行环境会复位,同时你的~/.bashrc文件会复原。
如果你在运行类似于rosnode的指令时出现一些问题,
也许你需要添加一些环境设置文件到你的~/.bashrc或者手动重新配置他们。
rosnode显示当前运行的ROS节点信息。
rosnode list指令列出活跃的节点:`rosnode list`
rosnode info命令返回的是关于一个特定节点的信息:`rosnode info /rosout`

下面,让我们看看更多的节点,为此,我们将使用rosrun弹出另一个节点。

### rosrun:

rosrun允许你使用包名直接运行一个包内的节点(而不需要知道这个包的路径)。
用法:rosrun [package_name] [node_name]

还是以前面小海龟的例子来说:rosrun turtlesim turtlesim_node
现在我们调用rosnode list,会看到多了一个/turtlesim节点。

此外,rosrun执行节点时,还可以通过命令行重新配置名称,如下: rosrun turtlesim turtlesim_node __name:=my_turtle
现在我们重新调用rosnode list,会看到节点名称变为/my_turtle
另外还可以通过rosnode ping /my_turtle来测试节点的通讯情况。

话题详解:

以小海龟为例来看其中的话题通讯机制。

### Topics:
turtlesim_node节点和turtle_teleop_key节点之间是通过一个ROS话题来互相通信的。
turtle_teleop_key在一个话题上发布按键输入消息,而turtlesim则订阅该话题以接收该消息。
### rqt_graph:
rqt_graph能够创建一个显示当前系统运行情况的动态图形。
rqt_graph是rqt程序包中的一部分。

下面让我们使用rqt_graph来显示当前运行的节点和话题。
可以运行rosrun rqt_graph rqt_graph,也可以直接输入rqt_graph打开。

表明两个节点之间正通过一个ROS话题来通讯。

### rostopic:

ros提供的rostopic命令工具能让你获取有关ROS话题的信息。

rostopic echo [topic] 可以显示在某个话题上发布的数据
rostopic list 能够列出所有当前订阅和发布的话题-v可以列出详细信息
rostopic type [topic] 命令用来查看所发布话题的消息类型

ROS Messages

话题之间的通信是通过在节点之间发送ROS消息实现的
对于发布器(turtle_teleop_key和订阅器(turtulesim_node)之间的通信发布器和订阅器之间必须发送和接收相同类型的消息
这意味着话题的类型是由发布在它上面的消息类型决定的
使用rostopic type命令可以查看发布在某个话题上的消息类型
使用rosmsg show [topic]命令可以查看消息的详细情况

例如通过rosmsg show geometry_msgs/Twist来查看小海龟运动的消息,显示:

geometry_msgs/Vector3 linear
  float64 x
  float64 y
  float64 z
geometry_msgs/Vector3 angular
  float64 x
  float64 y
  float64 z

现在我们已经知道了turtlesim节点所期望的消息类型,接下来我们就可以给turtle发布命令了。

我们已经了解了什么是ROS的消息,接下来我们开始结合消息来使用rostopic。

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]'`
以上命令会发送一条消息给turtlesim告诉它以2.0大小的线速度和1.8大小的角速度开始移动
但上面的命令只会让小海龟移动一次如果想让小海龟保持运动可以使用rostopic pub -r命令来发布一个稳定的命令流
发布一个频率为1Hz的命令流:`rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'`
### rqt_plot

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

这里我们将使用rqt_plot命令来绘制正在发布到/turtle1/pose话题上的数据变化图形。

可以通过命令rosrun rqt_plot rqt_plot启动或直接输入rqt_plot

这会弹出一个新窗口,在窗口左上角的一个文本框里面你可以添加需要绘制的话题。
在里面输入/turtle1/pose/y后之前处于禁用状态的加号按钮将会被使能变亮。 可以通过+-号来增加或删除话题。

服务详解:
服务(services)是节点之间通讯的另一种方式。
服务允许节点发送请求(request) 并获得一个响应(response),也就是请求-回应式的通讯机制。
### rosservice

rosservice可以很轻松的使用 ROS 客户端/服务器框架提供的服务。

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

还是以小海龟为例,输入rosservice list会显示:

/clear(清除)
/kill(终止)
/my_turtle/get_loggers
/my_turtle/set_logger_level
/reset(重置)
/rosout/get_loggers(rosout)
/rosout/set_logger_level(rosout)
/spawn(再生)
/teleop_turtle/get_loggers
/teleop_turtle/set_logger_level
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative

使用rosservice type [service]命令更进一步查看clear服务,如rosservice type clear,会显示: std_srvs/Empty
服务的类型为空(empty),这表明在调用这个服务是不需要参数(比如,请求不需要发送数据,响应也没有数据)。

因为服务类型为空,通过rosservice call [service] [args]命令调用无参数服务: rosservice call clear
小海龟的历史轨迹会被清除。

通过查看再生(spawn)服务的信息,我们来了解带参数的服务: 输入rosservice type spawn | rossrv show后会显示:

float32 x
float32 y
float32 theta
string name
---
string name

这个服务使得我们可以在给定的位置和角度生成一只新的乌龟。
名字参数是可选的,这里我们不设具体的名字,让turtlesim自动创建一个。
执行命令:rosservice call spawn 2 2 0.2 ""
服务会返回新产生的乌龟的名字: name: turtle2,这就是再生(spawn)。

顺着这个思路,大家可以去试试/reset(重置)这个服务的用法。

### rosparam

rosparam使得我们能够存储并操作ROS参数服务器(Parameter Server)上的数据。

参数服务器能够存储整型、浮点、布尔、字符串、字典和列表等数据类型。 rosparam使用YAML标记语言的语法。
一般而言,YAML的表述很自然:

1是整型
1.0是浮点型
one是字符串
true是布尔
[1,2,3]是整型列表
{a:b,c:d}是字典

rosparam的指令有以下:

rosparam set            设置参数
rosparam get            获取参数
rosparam load           从文件读取参数
rosparam dump           向文件中写入参数
rosparam delete         删除参数
rosparam list           列出参数名

以小海龟为例,我们来看看现在参数服务器上都有哪些参数,执行rosparam list会显示:

/my_turtle/background_b
/my_turtle/background_g
/my_turtle/background_r(背景色)
/rosdistro
/roslaunch/uris/host_wangzf_lenovo__45571
/rosversion
/run_id

改变参数值使用rosparam set [param_name],获取参数值使用rosparam get [param_name]

现在我们修改背景颜色的红色通道:rosparam set /my_turtle/background_r 150

上述指令修改了参数的值,现在我们调用清除服务(clear)使得修改后的参数生效:rosservice call clear

可以使用rosparam get来显示参数服务器上的所有内容:rosparam get /

也可以存储这些当前参数以备使用,使用方法如下:

rosparam dump [file_name]
rosparam load [file_name] [namespace]   // 可以载入新的命名空间
roslaunch多节点启动

在说roslaunch之前,提一下ros中有自带的调试工具:rqt_console和rqt_logger_level,属于ROS日志框架(logging framework)的一部分,用来显示节点的输出信息和修改日志级别(前面也看到过),如果有需要可以自己去调试用,这里不赘述。

可以通过下面命令启动:

rosrun rqt_console rqt_console
rosrun rqt_logger_level rqt_logger_level

主要说一下roslaunch的使用:

roslaunch [package] [filename.launch]可以用来启动定义在launch文件中的多个节点。

先切换到beginner_tutorials程序包目录下:roscd beginner_tutorials

然后创建一个launch文件夹:mkdir launch && cd launch

现在我们来创建一个名为turtle_test.launch的launch文件并复制粘贴以下内容到该文件里面:

<launch>

   <group ns="turtlesim1">
     <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
   </group>

   <group ns="turtlesim2">
     <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
   </group>

   <node pkg="turtlesim" name="mimic" type="mimic">
     <remap from="input" to="turtlesim1/turtle1"/>
     <remap from="output" to="turtlesim2/turtle1"/>
   </node>

</launch>

launch文件创建完成后,就通过roslaunch命令来启动launch文件:roslaunch beginner_tutorials turtle_test.launch

将会有两个turtlesims被启动,然后我们在一个新终端中使用rostopic命令发送速度设定消息:rostopic pub /turtlesim1/turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'

你会看到两个turtlesims会同时开始移动,虽然发布命令只是给turtlesim1发送了速度设定消息。

也可以通过rqt_graph来更好的理解在launch文件中所做的事情(节点和话题)。

六、话题与服务机制实现

创建ROS消息和ROS服务
### 消息(msg)和服务(srv)介绍

消息(msg): msg文件就是一个描述ROS中所使用消息类型的简单文本。它们会被用来生成不同语言的源代码。

服务(srv): 一个srv文件描述一项服务。它包含两个部分:请求和响应。

msg文件存放在package的msg目录下,srv文件则存放在srv目录下。

msg文件实际上就是每行声明一个数据类型和变量名。

可以使用的数据类型如下

int8, int16, int32, int64 (plus uint*) float32, float64 string time, duration other msg files variable-length array[] and fixed-length array[C]

在ROS中有一个特殊的数据类型:Header,它含有时间戳和坐标系信息。
下面是一个msg文件的样例,它使用了Header,string,和其他另外两个消息类型。

Header header
  string child_frame_id
  geometry_msgs/PoseWithCovariance pose
  geometry_msgs/TwistWithCovariance twist

srv服务文件分为请求和响应两部分,由’—‘分隔。下面是srv的一个样例:

int64 A
int64 B
---
int64 Sum
### 创建一个 msg

首先创建msg文件:roscd beginner_tutorials && mkdir msg 将msg消息写入文件(最简单的消息):echo "int64 num" > msg/Num.msg

接下来,还有关键的一步:我们要确保msg文件被转换成为C++,Python和其他语言的源代码:

查看package.xml, 确保它包含一下两条语句,分别是消息的构建与运行:

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

然后在CMakeLists.txt文件中,利用find_packag函数,增加对message_generation的依赖,这样就可以生成消息了。

你可以直接在COMPONENTS的列表里增加message_generation,就像这样: find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)

同样,你需要确保你设置了运行依赖:

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

找到如下代码块,添加你的msg文件:

add_message_files(
  FILES
  Num.msg
)

手动添加.msg文件后,我们要确保CMake知道在什么时候重新配置我们的project。

确保添加了如下代码:

generate_messages(
   DEPENDENCIES
   std_msgs
)

然后执行catkin_make,会生成msg消息。

### 使用 rosmsg

创建完成后,通过rosmsg show命令,检查ROS是否能够识消息。
rosmsg show beginner_tutorials/Num/rosmsg show Num 将会显示:int64 num

### 创建一个srv

roscd beginner_tutorials && mkdir srv 这次我们不再手动创建服务,而是从其他的package中复制一个服务。
roscp [package_name] [file_to_copy_path] [copy_path]是一个很实用的命令行工具,它实现了将文件从一个package复制到另外一个package的功能。

现在我们从rospy_tutorials package中复制一个服务文件:

roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv

还有很关键的一步:我们要确保srv文件被转换成C++,Python和其他语言的源代码。

如前边所介绍的,在CMakeLists.txt文件中增加了对message_generation的依赖(message_generation 对msg和srv都起作用)。

用你自己的srv文件名替换掉那些Service*.srv文件:

add_service_files(
  FILES
  AddTwoInts.srv
)

同样,跟msg文件类似,你也需要在package.xml文件中做一些修改(增加依赖项)。

现在,你可以生成自己的服务源代码了。

### 使用 rossrv

同样可以通过rossrv show命令,检查ROS是否能够识该服务:
rossrv show beginner_tutorials/AddTwoInts/rossrv show AddTwoInts

将会显示:

[rospy_tutorials/AddTwoInts]:
int64 a
int64 b
---
int64 sum

[beginner_tutorials/AddTwoInts]:
int64 a
int64 b
---
int64 sum
C++编写简单的消息发布器和订阅器

基于消息,编写发布器节点和订阅器节点,实现话题通信。

进入源文件目录:roscd beginner_tutorials/src

### 发布器

创建 talker.cpp 文件并粘贴代码:

/* 
 * 代码思路如下:
 * 1.初始化ROS系统
 * 2.在ROS网络内广播我们将要在chatter topic上发布std_msgs/String消息
 * 3.以每秒10次的频率在chatter上发布消息
*/ 
##include "ros/ros.h"
##include "std_msgs/String.h"

##include <sstream>

/**
 * This tutorial demonstrates simple sending of messages over the ROS system.
 */
int main(int argc, char **argv)
{
  /**
   * The ros::init() function needs to see argc and argv so that it can perform
   * any ROS arguments and name remapping that were provided at the command line. For programmatic
   * remappings you can use a different version of init() which takes remappings
   * directly, but for most command-line programs, passing argc and argv is the easiest
   * way to do it.  The third argument to init() is the name of the node.
   *
   * You must call one of the versions of ros::init() before using any other
   * part of the ROS system.
   */
  ros::init(argc, argv, "talker");

  /**
   * NodeHandle is the main access point to communications with the ROS system.
   * The first NodeHandle constructed will fully initialize this node, and the last
   * NodeHandle destructed will close down the node.
   */
  ros::NodeHandle n;

  /**
   * The advertise() function is how you tell ROS that you want to
   * publish on a given topic name. This invokes a call to the ROS
   * master node, which keeps a registry of who is publishing and who
   * is subscribing. After this advertise() call is made, the master
   * node will notify anyone who is trying to subscribe to this topic name,
   * and they will in turn negotiate a peer-to-peer connection with this
   * node.  advertise() returns a Publisher object which allows you to
   * publish messages on that topic through a call to publish().  Once
   * all copies of the returned Publisher object are destroyed, the topic
   * will be automatically unadvertised.
   *
   * The second parameter to advertise() is the size of the message queue
   * used for publishing messages.  If messages are published more quickly
   * than we can send them, the number here specifies how many messages to
   * buffer up before throwing some away.
   */
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

  ros::Rate loop_rate(10);

  /**
   * A count of how many messages we have sent. This is used to create
   * a unique string for each message.
   */
  int count = 0;
  while (ros::ok())
  {
    /**
     * This is a message object. You stuff it with data, and then publish it.
     */
    std_msgs::String msg;

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

    ROS_INFO("%s", msg.data.c_str());

    /**
     * The publish() function is how you send messages. The parameter
     * is the message object. The type of this object must agree with the type
     * given as a template parameter to the advertise<>() call, as was done
     * in the constructor above.
     */
    chatter_pub.publish(msg);

    ros::spinOnce();

    loop_rate.sleep();
    ++count;
  }

  return 0;
}
### 订阅器

创建 listener.cpp 文件并粘贴以下代码:

/*
 * 1.初始化ROS系统
 * 2.订阅chatter topic
 * 3.进入自循环,等待消息的到达
 * 4.当消息到达,调用chatterCallback()函数
*/
##include "ros/ros.h"
##include "std_msgs/String.h"

/**
 * This tutorial demonstrates simple receipt of messages over the ROS system.
 */
// 回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

int main(int argc, char **argv)
{
  /**
   * The ros::init() function needs to see argc and argv so that it can perform
   * any ROS arguments and name remapping that were provided at the command line. For programmatic
   * remappings you can use a different version of init() which takes remappings
   * directly, but for most command-line programs, passing argc and argv is the easiest
   * way to do it.  The third argument to init() is the name of the node.
   *
   * You must call one of the versions of ros::init() before using any other
   * part of the ROS system.
   */
  ros::init(argc, argv, "listener");

  /**
   * NodeHandle is the main access point to communications with the ROS system.
   * The first NodeHandle constructed will fully initialize this node, and the last
   * NodeHandle destructed will close down the node.
   */
  ros::NodeHandle n;

  /**
   * The subscribe() call is how you tell ROS that you want to receive messages
   * on a given topic.  This invokes a call to the ROS
   * master node, which keeps a registry of who is publishing and who
   * is subscribing.  Messages are passed to a callback function, here
   * called chatterCallback.  subscribe() returns a Subscriber object that you
   * must hold on to until you want to unsubscribe.  When all copies of the Subscriber
   * object go out of scope, this callback will automatically be unsubscribed from
   * this topic.
   *
   * The second parameter to the subscribe() function is the size of the message
   * queue.  If messages are arriving faster than they are being processed, this
   * is the number of messages that will be buffered up before beginning to throw
   * away the oldest ones.
   */
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

  /**
   * ros::spin() will enter a loop, pumping callbacks.  With this version, all
   * callbacks will be called from within this thread (the main one).  ros::spin()
   * will exit when Ctrl-C is pressed, or the node is shutdown by the master.
   */
  ros::spin();      // 自循环

  return 0;
}
### 编译节点

调整 package.xml 和 CMakeLists.txt 文件。

在CMakeLists.txt文件末尾加入几条语句:

include_directories(include ${catkin_INCLUDE_DIRS})

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

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

这会生成两个可执行文件, talker 和 listener, 默认存储到devel/lib目录

然后运行catkin_make编译。

启动:

rosrun beginner_tutorials talker
rosrun beginner_tutorials listener  
Python编写简单的消息发布器和订阅器
### 发布器

进入并创建目录:roscd beginner_tutorials && mkdir scripts && cd scripts
创建 talker.py ,写入以下代码:

##!/usr/bin/env python
import rospy
from std_msgs.msg import String

def talker():
    pub = rospy.Publisher('chatter', String, queue_size=10)
    rospy.init_node('talker', anonymous=True)
    rate = rospy.Rate(10) ## 10hz
    while not rospy.is_shutdown():
        hello_str = "hello world %s" % rospy.get_time()
        rospy.loginfo(hello_str)
        pub.publish(hello_str)
        rate.sleep()

if __name__ == '__main__':
    try:
        talker()
    except rospy.ROSInterruptException:
        pass
    '''
    标准的Python main检查,这个会捕获rospy.ROSInterruptException异常,当按下Ctrl-C或节点关闭的话,即使在rospy.sleep()和rospy.Rate.sleep()函数里都会抛出异常。
    '''
### 订阅器

创建 listener.py ,写入以下代码:

##!/usr/bin/env python
import rospy
from std_msgs.msg import String

## 新增加的函数回调机制
def callback(data):
    rospy.loginfo(rospy.get_caller_id() + 'I heard %s', data.data)

def listener():

    ## In ROS, nodes are uniquely named. If two nodes with the same
    ## name are launched, the previous one is kicked off. The
    ## anonymous=True flag means that rospy will choose a unique
    ## name for our 'listener' node so that multiple listeners can
    ## run simultaneously.
    rospy.init_node('listener', anonymous=True)

    rospy.Subscriber('chatter', String, callback)

    ## spin() simply keeps python from exiting until this node is stopped
    rospy.spin()

if __name__ == '__main__':
    listener()
### 编译节点

我们使用CMake作为构建系统,即使是Python节点也需要使用。

catkin_make编译完成。

启动:

rosrun beginner_tutorials talker.py 
rosrun beginner_tutorials listener.py 
C++编写简单的Service和Client
### 服务端

创建 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;
}

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;
}
### 客户端

创建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");
  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;
}
### 编译节点

再来编辑一下beginner_tutorials里面的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)

这段代码将生成两个可执行程序”add_two_ints_server”和”add_two_ints_client”,这两个可执行程序默认被放在你的devel/lib下的包目录下。

可以直接调用可执行程序,或者使用rosrun命令去调用它们。

catkin_make编译完成。

Python编写简单的Service和Client
### 服务端

创建scripts/add_two_ints_server.py文件,并粘贴以下内容:

##!/usr/bin/env python

from beginner_tutorials.srv import *
import rospy

def handle_add_two_ints(req):
    print "Returning [%s + %s = %s]"%(req.a, req.b, (req.a + req.b))
    return AddTwoIntsResponse(req.a + req.b)

def add_two_ints_server():
    rospy.init_node('add_two_ints_server')
    s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
    print "Ready to add two ints."
    rospy.spin()

if __name__ == "__main__":
    add_two_ints_server()
### 客户端

创建scripts/add_two_ints_client.py文件,并粘贴以下内容:

##!/usr/bin/env python

import sys
import rospy
from beginner_tutorials.srv import *

def add_two_ints_client(x, y):
    rospy.wait_for_service('add_two_ints')
    try:
        add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
        resp1 = add_two_ints(x, y)
        return resp1.sum
    except rospy.ServiceException, e:
        print "Service call failed: %s"%e

def usage():
    return "%s [x y]"%sys.argv[0]

if __name__ == "__main__":
    if len(sys.argv) == 3:
        x = int(sys.argv[1])
        y = int(sys.argv[2])
    else:
        print usage()
        sys.exit(1)
    print "Requesting %s+%s"%(x, y)
    print "%s + %s = %s"%(x, y, add_two_ints_client(x, y))
### 编译节点

catkin_make编译完成。

执行:

rosrun beginner_tutorials add_two_ints_server.py
rosrun beginner_tutorials add_two_ints_client.py

注意:python2 和 python3 语法有区别的。

七、ROS常用工具

rqt

rqt 是ROS提供的 GUI 工具集。

前面我们已经用过rqt工具的一部分,现在我们来整理了解一下:

通过rqt命令打开该工具。

rviz

RViz是一款强大的可视化工具,它允许你查看机器人中的传感器和内部状态。

通过rviz命令打开该工具。

可以通过Add添加想要Display的Topics,并且可以配置其参数。

还可以从不同的视角来看当前机器人的状态,尤其在机器人导航时很有用。

底部会显示Time,即ROS系统的内部时钟。

八、ROS入门总结

总结
  • 此时你应该已经对ROS中的一些核心概念有了一定的理解。
  • 给你一台运行ROS的机器人,你应该能够运用所学知识来列出机器人上发布和订阅的各种话题(topic),查看话题中发布的消息,然后编写你自己的节点(node)来处理传感器数据,最后让机器人在真实环境中动起来。
  • ROS真正的吸引力不在于它自己发布或订阅的中间件,而在于ROS为世界各地的开发者提供了一种标准机制来分享他们的代码。
  • ROS最大的特色在于庞大的社区。
  • 大量现成可用的程序包(packages)已经势不可挡。
接下来做什么?

ROS作为一种机器人软件开发平台,学习完基本概念后,我们可以从一辆真实的机器人或者一个模拟器入手,学习其底盘控制、传感器数据获取、导航算法、规划算法等,先用所学的节点等概念去全面了解Robot,然后再去钻研自己感兴趣的那一方面。

ROS入门完!ROS Distro

有兴趣的也可以看看《ROS史话36篇》,出自华东师范大学-张新宇老师

摘抄:

计算机使用的操作系统英语称为Operating System缩写为OS它是一种计算机程序帮助使用计算机的人操控计算机硬件管理各种应用软件

人们听说过的硬件包括CPU主板内存硬盘显示器打印机U盘等人们常用的应用软件有文字处理软件比如微软开发的Word幻灯片制作软件比如微软开发的PowerPoint看电影的软件比如微软的视频播放器浏览网页的软件比如谷歌的Chrome聊天软件比如腾讯的QQ和微信等等

一开始计算机比较简单并没有操作系统人们通过各种操作按钮就可以控制计算机但是这种操作方式效率比较低后来人们通过有孔的纸带将程序输入计算机进行编译再通过程序员自己编写的程序运行这种方式效率还是很低为了更有效的管理计算机硬件并提高计算机程序的开发效率就出现了操作系统

与计算机操作系统类似机器人操作系统的出现也遵循这样一个规律为了提高机器人设计和开发的效率机器人操作系统英语称为Robot Operating System缩写为ROS

2009年摩根·奎格利(吴恩达学生)吴恩达和柳树车库机器人公司的工程师们在当年的IEEE国际机器人与自动化会议上发表了ROS: An Open-Source Robot Operating System》,正式向外界介绍ROS

正如文章中说强调的

ROS is not an operating system in the traditional sense of process management and scheduling; rather, it provides a structured communications layer above the host operating systems of a heterogenous compute cluster.

译文ROS不是传统意义上的操作系统不是用于进程管理和调度而是构建在其它操作系统之上的一种结构化的通讯层。)

从2008年开始柳树车库机器人开始主导ROS的开发奎格利因为还没有毕业但以学生兼职的形式指导着ROS的进展

2010年随着PR2正式对外发布柳树车库机器人也正式推出ROS正式开发版这就是ROS 1.0

ROS秉承开源的原则与全球开发者一起推动机器人事业

2012年摩根·奎格利终于博士毕业了他选择作为首席架构师与布莱恩·格基Brian GerkeyCEO)、罗伯特·弗里德曼Roberta Friedman CFO)、凯特·考尼Nate Koenig CTO一起创建了开源机器人基金会 (Open Source Robot FoundationOSRF)”。后来OSRF接管了ROS的开发和维护工作

在ROS的发展历程中有两件非常重要的事件其一就是成立了**独立运营的开源机器人基金会**推动ROS的发展其二就是**每年举办的ROS开发者大会**

随着ROS社群不断的壮大在2012年5月,“开源机器人基金会组织了第一届ROS开发者大会简称ROSCon)。

微软的Kinect深度相机在2010年发布2011年已经很火了通过Kinect采集获得的是可视范围内的深度数据即可视范围内物体到相机的距离从这些深度数据可以计算出3D点云Point Cloud)。这样点云处理算法就要跟进因此ROS加入了点云库Point Cloud Library, PCL的0.1版本这是一个开源的软件库包含大量与点云有关的算法包括显示点云数据滤波分割特征提取配准重建等支持Windows和Linux等平台也可以在一些嵌入式开发板上使用PCL在三维点云中的地位相当于OpenCV在图像处理和计算机视觉中的地位

PCL是由莱度·茹苏Radu B. Rusu在车库期间创建的开源点云处理软件那时点云数据和点云数据处理在逆向工程已经应用的很广泛了只是很少有人把自己的算法和代码开源出来莱度·茹苏毕业于德国慕尼黑工业大学TechnischeUniversität München),后来负责由车库孵化的 Open Perception公司致力于2D/3D 数据的处理
ROS已经加入对视觉信息处理的支持尤其是改进OpenCV在ROS中的易用性

OpenCV 是盖里·布拉德斯基Gary Bradski在Intel 工作期间开发的一个开源计算机视觉库OpenCVOpen SourceComputer Vision Libraryhttp://opencv.org是由一系列C 函数/C++类构成实现了图像处理和计算机视觉方面的很多通用算法很多人都在享受OpenCV带来的便利

跟所有的开源项目一样OpenCV致力于软件算法的共享,“不重复造轮子”。早期的主要目标是

提供一套开源的标准算法加速机器视觉的研究
建立一套基本算法库研究者和开发工程师能够快速进行视觉软件开发
利用开源或免费的软件许可与授权促进商业视觉应用的开发通过共享共同促进知识的传播

2013年到2014年初是车库机器人研发的最后一个阶段外界传闻很多车库内部很多人心绪不定对于PR2的未来背地里也是议论纷纷车库的房东斯科特·哈森决定停止继续投入资金 PR2的研究也停下来车库的各路人马正式从机器人研究的阵地退下来吹响机器人商业化的号角经过8年努力,“柳树车库系终于全力冲进了机器人市场

正是这个时候笔者回国创建机器人运动与视觉实验室”。

微软在ROSCon2018展示了一款ROBOTIS Turtlebot 3机器人运行于ROS Melodic Morenia版本上可以识别靠近的人该机器人运行新版的Windows 10 IoT Enterprise利用ROS节点进行通讯在Windows环境中利用硬件加速机器学习微软还展示了在Azure中运行的ROS仿真环境

微软的介入预示着ROS生态系统的大变动

关于编译系统相比 ROS 最初使用的 rosbuildcatkin 在做了一些改进如支持 CMake 配置文件的自动生成ament的引入还主要是为ROS2考量的所以原有的ROS1还是用catkin 作为编译系统ROS2 则用 ament

Player 是一个各种机器人设备的服务器客户端可以利用计算机网络通过 Player 控制机器人设备和传感器

Stage是一个二维的机器人仿真环境

2004 年左右Gazebo这样一个三维的基于物理的机器人仿真环境加入到了 Player项目中后来车库资助Gazebo的开发Gazebo独立成一个单独的项目

Player和Stage的名称来源于莎士比亚戏剧皆大欢喜舞台剧中的独白Gazebo意思为舞台中带圆顶的露台


在2015年DARPA举办了一届机器人挑战赛DARPA Robotics Challenge)。

23只进入决赛的队伍中有18支队伍使用ROS有14支队伍利用Gazebo进行仿真ROS取得非凡的成绩

DARPA 万万没想到机器人挑战赛是一只来自东方的力量压倒西方强队以媒体的说法是横空出世的一匹黑马”。来自韩国高等科学技术学院 KAIST21团队夺得了这项堪称有史以来最引人注目的机器人大赛的冠军夺走高达两百万美金的大奖

DARPA受到刺激,“我们投的钱去哪了”?

DARPA机器人挑战赛至今没有再举办的计划

这次比赛集结了全球最顶尖的机器人AtlasHRP可以大多数机器人会出现摔倒现象而且无法爬起这是组委会没有充分预料到的

2 支中国队中有一个是来自香港大学的队伍另外就是中国科学院合肥物质科学研究院先进制造技术研究所一支队伍在决赛现场并没有看到合肥这支队伍的身影也就是说他们并没有真正参加这次比赛香港大学也没有完成比赛的任何一项在比赛中垫底

引用邓小平南巡讲话

在经济上呢则是近三十年来亚洲四小龙的崛起22这两年除了四小龙外泰国印尼马来西亚也迎头赶上四小龙和东南亚诸国的人有中国人聪明勤劳吗我看未必。…….

“……我刚才提到二十世纪一个重大的经济现象就是四小龙的崛起中国怎么办我看先别赶英超美连日本我们也别去比中国当前的任务和出路就是向四小龙学习经过二三十年的奋斗实现小康。”

机器人这条路还有很多要学习的地方


传统的工业机器人是预先编程好的并不能感知周围环境的变化自然也不能随着环境的变化进行智能的调整运动

现在一个大趋势是越来越多机器人需要跟工人协同工作他们跟工人离得非常近共享同一个工作空间共同完成一项任务

自动驾驶可能是机器人与人类协同工作最好的例子

在拥挤的街道和高速公路上行驶中的自动驾驶汽车如果把他也归类为机器人话除了与其他车辆共享空间还必须与行人自行车共享空间相当于协同工作

家庭服务机器人面临的环境更为复杂凌乱的家居椅子横七竖八走到哪里都有障碍物家庭成员进进出出不断闯入机器人的领地动态改变机器人的工作空间

在这样复杂环境中运动的机器人必须清楚其周围环境必须避免周围的任何障碍物必须避免家庭成员发生碰撞


有了移动平台有了摄像头还需要一个计算机来运行 ROS 的相关算法自助导航是 ROS 的精髓为了把这个精髓表现的淋漓精致Turtlebot这个平台必须能实现自主导航

笔者所在的实验室最开始也是利用Turtlebot做研究的考虑到成本笔者从美国采购了iRobot Create学生们自己DIY了其他支持部件深度相机用的是华硕的Xtion Pro Live

Turtle是海龟的意思Bot是机器人的缩写TurtleBot字面上讲就是海龟机器人的意思

乌龟Turtle在机器人领域基本成了机器人的代名词了 TurtleBot 这样的名字也就不足为奇了

TurtleBot的硬件包括扫地机扩展的运动底盘 iRobot CreateRGB-D相机 Kinect一个笔记本电池和支撑架在笔记本上安装 ROS 就可以实现 3D 导航跟随等功能

主要步骤有准备硬件软件ubunturos相机驱动键盘控制游戏杆控制跟随mapping SLAMamcl自主导航

**下层电池电机电控  上层激光雷达IMU嵌入式AI平台

柳树车库系:

从“柳树车库”直接衍生出的公司正在改变着世界。一些前期的公司包括:

hiDOF:机器人与自动化软件咨询公司,2013被Google收购。

IPI(Industrial Perception Inc.):利用视觉辅助工业机械臂搬运货物,2013被Google收购。

OpenCV:一家非盈利机构,开发开源视觉和机器学习算法。

PCL:一家非盈利机构,开发点云处理算法。

OPF(Open Perception Foundation):一家非盈利机构,致力于 2D/3D 数据的处理。

OSRF(Open Source Robotics Foundation):一家非盈利机构,致力于机器人开源软件的研究、教育、产品开发。

Open Robotics:由 OSRF创立的一家盈利机构,致力于机器人开源软件的对外服务、软件开发、机器人开发咨询服务。

Redwood Robotics:低成本协作机械臂,2013年被Google收购.

Suitable Technologies:远程遥控机器人beam。

Unbounded Robotics:开发低成本移动机器人平台。

Fetch Robotics:由 Unbounded Robotics 团队重新打造的一款面向仓储物流的移动机器人平台。

世界上最庞大的机器人派系“柳树车库系”在硅谷形成了,这里有商业巨子、优秀的企业家、专业的机器人工程师,他们聚集强大的力量,融合各种资源和技术:计算机视觉、运动规划,努力打造完成各种复杂任务的机器人。

在机器人领域,无处不在的“柳树车库系”的影响。离开柳树车库的员工加入了各种机器人团队:如Google的机器人团队、Bosch自动驾驶团队、Tango 智能手机3D扫描团队等。也有些人回到大学,成为了自动驾驶车辆开发者、无人机的研究者。

柳树车库和ROS的经历,正是我们看到的硅谷的真实故事,跌宕起伏。“柳树车库系”正以无法估量的力量影响整个机器人世界。柳树车库的人们坚信,他们已经开垦了荒芜的土地,培育了优质的土壤,播撒了繁多的种子,它们定会生根、发芽,最终开花、结果。

人类社会正在快速地向前发展,机器人行业会不会成为下一个焦点,每个人都有自己的答案。但是不管怎么样,机器人领域一直在不断的革新。

ROS 作为一个开源的基础机器人软件系统,毫无疑问会成为这场变革中凶猛的助推剂。我们看到越来越多的初创公司转向 ROS,并利用 ROS 提供的完善的软件系统快速地搭建起机器人平台。

越来越多的传统大公司也注意到这一新兴的变化,他们的产品也开始支持 ROS。若干年后, ROS 将毫无疑问成为机器人工程师的必备技能,机器人领域的从业人员没有人能回避ROS。

所有的这一切,都昭示着ROS有着光明的前途。

ROS进阶探索

一、ROS简介

ROS设计目标

ROS是开源的,是用于机器人的一种后操作系统,或者说次级操作系统。它提供类似操作系统所提供的功能,包含硬件抽象描述、底层驱动程序管理、共用功能的执行、程序间的消息传递、程序发行包管理,它也提供一些工具程序和库用于获取、建立、编写和运行多机整合的程序。

ROS的首要设计目标是在机器人研发领域提高代码复用率。ROS是一种分布式处理框架(又名Nodes)。这使可执行文件能被单独设计,并且在运行时松散耦合。这些过程可以封装到数据包(Packages)和堆栈(Stacks)中,以便于共享和分发。ROS还支持代码库的联合系统。使得协作亦能被分发。这种从文件系统级别到社区一级的设计让独立地决定发展和实施工作成为可能。上述所有功能都能由ROS的基础工具实现。

ROS特点

ROS的运行架构是一种使用ROS通信模块实现模块间P2P的松耦合的网络连接的处理架构,它执行若干种类型的通讯,包括基于服务的同步RPC(远程过程调用)通讯、基于Topic的异步数据流通讯,还有参数服务器上的数据存储。但是ROS本身并没有实时性。

ROS的主要特点可以归纳为以下几条:

1.点对点设计
一个使用ROS的系统包括一系列进程,这些进程存在于多个不同的主机并且在运行过程中通过端对端的拓扑结构进行联系。虽然基于中心服务器的那些软件框架也可以实现多进程和多主机的优势,但是在这些框架中,当各电脑通过不同的网络进行连接时,中心数据服务器就会发生问题。

ROS的点对点设计以及服务和节点管理器等机制可以分散由计算机视觉和语音识别等功能带来的实时计算压力,能够适应多机器人遇到的挑战。

2.多语言支持
在写代码的时候,许多编程者会比较偏向某一些编程语言。这些偏好是个人在每种语言的编程时间、调试效果、语法、执行效率以及各种技术和文化的原因导致的结果。为了解决这些问题,我们将ROS设计成了语言中立性的框架结构。ROS现在支持许多种不同的语言,例如C++、Python、Octave和LISP,也包含其他语言的多种接口实现。

ROS的特殊性主要体现在消息通讯层,而不是更深的层次。端对端的连接和配置利用XML-RPC机制进行实现,XML-RPC也包含了大多数主要语言的合理实现描述。我们希望ROS能够利用各种语言实现的更加自然,更符合各种语言的语法约定,而不是基于C语言给各种其他语言提供实现接口。然而,在某些情况下利用已经存在的库封装后支持更多新的语言是很方便的,比如Octave的客户端就是通过C++的封装库进行实现的。

为了支持交叉语言,ROS利用了简单的、语言无关的接口定义语言去描述模块之间的消息传送。接口定义语言使用了简短的文本去描述每条消息的结构,也允许消息的合成。

3.精简与集成
为了应对机器人软件复杂和中间层混乱的趋势,我们鼓励将所有的驱动和算法逐渐发展成为和ROS没有依赖性单独的库。ROS建立的系统具有模块化的特点,各模块中的代码可以单独编译,而且编译使用的CMake工具使它很容易的就实现精简的理念。ROS基本将复杂的代码封装在库里,只是创建了一些小的应用程序为ROS显示库的功能,就允许了对简单的代码超越原型进行移植和重新使用。作为一种新加入的有优势,单元测试当代码在库中分散后也变得非常的容易,一个单独的测试程序可以测试库中很多的特点。

ROS利用了很多现在已经存在的开源项目的代码,比如说从Player项目中借鉴了驱动、运动控制和仿真方面的代码,从OpenCV中借鉴了视觉算法方面的代码,从OpenRAVE借鉴了规划算法的内容,还有很多其他的项目。在每一个实例中,ROS都用来显示多种多样的配置选项以及和各软件之间进行数据通信,也同时对它们进行微小的包装和改动。ROS可以不断的从社区维护中进行升级,包括从其他的软件库、应用补丁中升级ROS的源代码。

4.工具包丰富
为了管理复杂的ROS软件框架,我们利用了大量的小工具去编译和运行多种多样的ROS组建,从而设计成了内核,而不是构建一个庞大的开发和运行环境。

5.免费并且开源
ROS所有的源代码都是公开发布的。我们相信这将必定促进ROS软件各层次的调试,不断的改正错误。虽然像Microsoft Robotics Studio和Webots这样的非开源软件也有很多值得赞美的属性,但是我们认为一个开源的平台也是无可为替代的。当硬件和各层次的软件同时设计和调试的时候这一点是尤其真实的。

二、创建一个smart_car

智能车smart_car全程会用仿真的方式进行,因此有一台电脑就可以学习ROS进阶。

1.创建简单的smartcar模型

这一节我们将建立一个简单的智能车机器人smartcar,为后面建立复杂机器人打下基础。

URDF相关工具安装: 安装urdf_tutorial,可以使用其中的例子

sudo apt-get install ros-noetic-urdf-tutorial

安装liburdfdom-tools,提供对urdf文件的检查

sudo apt-get install liburdfdom-tools

创建硬件描述包:catkin_create_pkg smartcar_description std_msgs rospy roscpp urdf

建立urdf文件:

在smartcar_description文件夹下建立urdf文件夹 $mkdir -p urdf ,创建智能车的描述文件smartcar.urdf,描述代码如下:

<?xml version="1.0"?>  

<robot name="smartcar">  

  <link name="base_link">  

    <visual>  

      <geometry>  

        <box size="0.25 .16 .05"/>  

    </geometry>  

    <origin rpy="0 0 1.57075" xyz="0 0 0"/>  

    <material name="blue">  

        <color rgba="0 0 .8 1"/>  

    </material>  

    </visual>  

</link>  


<link name="right_front_wheel">  

    <visual>  

      <geometry>  

        <cylinder length=".02" radius="0.025"/>  

      </geometry>  

      <material name="black">  

        <color rgba="0 0 0 1"/>  

      </material>  

    </visual>  

  </link>  


  <joint name="right_front_wheel_joint" type="continuous">  

    <axis xyz="0 0 1"/>  

    <parent link="base_link"/>  

    <child link="right_front_wheel"/>  

    <origin rpy="0 1.57075 0" xyz="0.08 0.1 -0.03"/>  

    <limit effort="100" velocity="100"/>  

    <joint_properties damping="0.0" friction="0.0"/>  

  </joint>  


  <link name="right_back_wheel">  

    <visual>  

      <geometry>  

        <cylinder length=".02" radius="0.025"/>  

      </geometry>  

      <material name="black">  

        <color rgba="0 0 0 1"/>  

      </material>  

    </visual>  

  </link>  


  <joint name="right_back_wheel_joint" type="continuous">  

    <axis xyz="0 0 1"/>  

    <parent link="base_link"/>  

    <child link="right_back_wheel"/>  

    <origin rpy="0 1.57075 0" xyz="0.08 -0.1 -0.03"/>  

    <limit effort="100" velocity="100"/>  

    <joint_properties damping="0.0" friction="0.0"/>  

</joint>  


<link name="left_front_wheel">  

    <visual>  

      <geometry>  

        <cylinder length=".02" radius="0.025"/>  

      </geometry>  

      <material name="black">  

        <color rgba="0 0 0 1"/>  

      </material>  

    </visual>  

  </link>  


  <joint name="left_front_wheel_joint" type="continuous">  

    <axis xyz="0 0 1"/>  

    <parent link="base_link"/>  

    <child link="left_front_wheel"/>  

    <origin rpy="0 1.57075 0" xyz="-0.08 0.1 -0.03"/>  

    <limit effort="100" velocity="100"/>  

    <joint_properties damping="0.0" friction="0.0"/>  

  </joint>  


  <link name="left_back_wheel">  

    <visual>  

      <geometry>  

        <cylinder length=".02" radius="0.025"/>  

      </geometry>  

      <material name="black">  

        <color rgba="0 0 0 1"/>  

      </material>  

    </visual>  

  </link>  


  <joint name="left_back_wheel_joint" type="continuous">  

    <axis xyz="0 0 1"/>  

    <parent link="base_link"/>  

    <child link="left_back_wheel"/>  

    <origin rpy="0 1.57075 0" xyz="-0.08 -0.1 -0.03"/>  

    <limit effort="100" velocity="100"/>  

    <joint_properties damping="0.0" friction="0.0"/>  

  </joint>  


  <link name="head">  

    <visual>  

      <geometry>  

        <box size=".02 .03 .03"/>  

      </geometry>  

      <material name="white">  

          <color rgba="1 1 1 1"/>  

      </material>  

    </visual>  

  </link>  


  <joint name="tobox" type="fixed">  

    <parent link="base_link"/>  

    <child link="head"/>  

    <origin xyz="0 0.08 0.025"/>  

  </joint>  

</robot>

建立launch命令文件:
在smartcar_description文件夹下建立launch文件夹mkdir -p launch,创建智能车的描述文件 base.urdf.rviz.launch,描述代码如下:

<launch>     

    <arg name="model" />      

    <arg name="gui" default="False" />     

    <param name="robot_description" textfile="$(find smartcar_description)/urdf/smartcar.urdf" />      

    <param name="use_gui" value="$(arg gui)"/>     

    <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" >

    </node>      

    <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />      

    <node name="rviz" pkg="rviz" type="rviz" args="-d $(find smartcar_description)/rviz/smartcar.rviz" />  

</launch>

建立rviz文件:
在smartcar_description文件夹下建立rviz文件夹mkdir -p rviz,创建智能车的描述文件 smartcar.rviz(后面也可以自己改),描述代码如下:

Panels:
  - Class: rviz/Displays
    Help Height: 78
    Name: Displays
    Property Tree Widget:
      Expanded:
        - /Global Options1
        - /Status1
      Splitter Ratio: 0.5
    Tree Height: 353
  - Class: rviz/Selection
    Name: Selection
  - Class: rviz/Tool Properties
    Expanded:
      - /2D Pose Estimate1
      - /2D Nav Goal1
      - /Publish Point1
    Name: Tool Properties
    Splitter Ratio: 0.5886790156364441
  - Class: rviz/Views
    Expanded:
      - /Current View1
    Name: Views
    Splitter Ratio: 0.5
  - Class: rviz/Time
    Experimental: false
    Name: Time
    SyncMode: 0
    SyncSource: ""
  - Class: rviz_plugin_tutorials/Teleop
    Name: Teleop
    Topic: ""
Preferences:
  PromptSaveOnExit: true
Toolbars:
  toolButtonStyle: 2
Visualization Manager:
  Class: ""
  Displays:
    - Alpha: 0.5
      Cell Size: 1
      Class: rviz/Grid
      Color: 160; 160; 164
      Enabled: true
      Line Style:
        Line Width: 0.029999999329447746
        Value: Lines
      Name: Grid
      Normal Cell Count: 0
      Offset:
        X: 0
        Y: 0
        Z: 0
      Plane: XY
      Plane Cell Count: 10
      Reference Frame: <Fixed Frame>
      Value: true
    - Alpha: 1
      Axes Length: 1
      Axes Radius: 0.10000000149011612
      Class: rviz/Pose
      Color: 255; 25; 0
      Enabled: false
      Head Length: 0.30000001192092896
      Head Radius: 0.10000000149011612
      Name: Pose
      Queue Size: 10
      Shaft Length: 1
      Shaft Radius: 0.05000000074505806
      Shape: Arrow
      Topic: /move_base_simple/goal
      Unreliable: false
      Value: false
    - Alpha: 1
      Class: rviz/RobotModel
      Collision Enabled: false
      Enabled: true
      Links:
        All Links Enabled: true
        Expand Joint Details: false
        Expand Link Details: false
        Expand Tree: false
        Link Tree Style: Links in Alphabetic Order
        base_link:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        head:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        left_back_wheel:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        left_front_wheel:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        right_back_wheel:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        right_front_wheel:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
      Name: RobotModel
      Robot Description: robot_description
      TF Prefix: ""
      Update Interval: 0
      Value: true
      Visual Enabled: true
    - Alpha: 1
      Class: rviz/Axes
      Enabled: false
      Length: 1
      Name: Axes
      Radius: 0.10000000149011612
      Reference Frame: <Fixed Frame>
      Show Trail: false
      Value: false
  Enabled: true
  Global Options:
    Background Color: 48; 48; 48
    Default Light: true
    Fixed Frame: base_link
    Frame Rate: 30
  Name: root
  Tools:
    - Class: rviz/Interact
      Hide Inactive Objects: true
    - Class: rviz/MoveCamera
    - Class: rviz/Select
    - Class: rviz/FocusCamera
    - Class: rviz/Measure
    - Class: rviz/SetInitialPose
      Theta std deviation: 0.2617993950843811
      Topic: /initialpose
      X std deviation: 0.5
      Y std deviation: 0.5
    - Class: rviz/SetGoal
      Topic: /move_base_simple/goal
    - Class: rviz/PublishPoint
      Single click: true
      Topic: /clicked_point
  Value: true
  Views:
    Current:
      Class: rviz/Orbit
      Distance: 0.5815290212631226
      Enable Stereo Rendering:
        Stereo Eye Separation: 0.05999999865889549
        Stereo Focal Distance: 1
        Swap Stereo Eyes: false
        Value: false
      Field of View: 0.7853981852531433
      Focal Point:
        X: -0.06951690465211868
        Y: -0.05873965844511986
        Z: 0.1176082193851471
      Focal Shape Fixed Size: false
      Focal Shape Size: 0.05000000074505806
      Invert Z Axis: false
      Name: Current View
      Near Clip Distance: 0.009999999776482582
      Pitch: 0.6004970073699951
      Target Frame: <Fixed Frame>
      Yaw: 4.431729316711426
    Saved: ~
Window Geometry:
  Displays:
    collapsed: false
  Height: 650
  Hide Left Dock: false
  Hide Right Dock: false
  QMainWindow State: 000000ff00000000fd000000040000000000000156000001ecfc0200000009fb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001d20000014c00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb0000001200530065006c0065006300740069006f006e000000003d000000a10000005c00fffffffb000000100044006900730070006c006100790073010000003d000001ec000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000c00540065006c0065006f00700000000192000000970000004700ffffff000000010000010f000001ecfc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d000001ec000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000005560000003efc0100000002fb0000000800540069006d0065010000000000000556000002eb00fffffffb0000000800540069006d00650100000000000004500000000000000000000002e5000001ec00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
  Selection:
    collapsed: false
  Teleop:
    collapsed: false
  Time:
    collapsed: false
  Tool Properties:
    collapsed: false
  Views:
    collapsed: false
  Width: 1366
  X: 0
  Y: 27

运行模型:roslaunch smartcar_description base.urdf.rviz.launch gui:=true

加载键盘控制器:rosrun joint_state_publisher_gui joint_state_publisher_gui

2.使用smartcar进行仿真

安装相关工具包:

安装 arbotix_python :sudo apt-get install ros-noetic-arbotix

模型完善:

之前我们使用的都是urdf文件格式的模型,在很多情况下,ROS对urdf文件的支持并不是很好,使用宏定义的.xacro文件兼容性更好,扩展性也更好。所以我们把之前的urdf文件重新整理编写成.xacro文件。

.xacro文件主要分为三部分:

1、机器人主体smartcar_body.urdf.xacro

<?xml version="1.0"?>  
<robot name="smartcar" xmlns:xacro="http://ros.org/wiki/xacro">  
  <xacro:property name="M_PI" value="3.14159"/>    
  <!-- Macro for SmartCar body. Including Gazebo extensions, but does not include Kinect -->  
  <xacro:include filename="$(find smartcar_description)/urdf/gazebo.urdf.xacro"/>  
  <xacro:property name="base_x" value="0.33" />  
  <xacro:property name="base_y" value="0.33" />  
  <xacro:macro name="smartcar_body">  

    <link name="base_link">  
    <inertial>  
      <origin xyz="0 0 0.055"/>  
      <mass value="1.0" />  
      <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0"/>  
    </inertial>  
    <visual>  
      <geometry>  
        <box size="0.25 .16 .05"/>  
      </geometry>  
      <origin rpy="0 0 0" xyz="0 0 0.055"/> 
      <material name="blue">  
      <color rgba="0 0 .8 1"/>  
      </material>  
   </visual>  
   <collision>  
      <origin rpy="0 0 0" xyz="0 0 0.055"/>  
      <geometry>  
        <box size="0.25 .16 .05" />  
      </geometry>  
    </collision>  
  </link>  



 <link name="left_front_wheel">  
    <inertial>  
      <origin  xyz="0.08 0.08 0.025"/>  
      <mass value="0.1" />  
       <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0"/>  
    </inertial>  

    <visual>  
      <geometry>  
        <cylinder length=".02" radius="0.025"/>  
      </geometry>  
      <material name="black">  
        <color rgba="0 0 0 1"/>  
      </material>  
    </visual>  
    <collision>  
      <origin rpy="0 1.57075 1.57075" xyz="0.08 0.08 0.025"/>  
      <geometry>  
         <cylinder length=".02" radius="0.025"/>  
      </geometry>  
    </collision>  
  </link>  

  <joint name="base_l_wheel_joint" type="continuous">  
    <axis xyz="0 0 1"/>  
    <parent link="base_link"/>  
    <child link="left_front_wheel"/>  
    <origin rpy="0 1.57075 1.57075" xyz="0.08 0.08 0.025"/>  
    <limit effort="100" velocity="100"/>  
    <joint_properties damping="0.0" friction="0.0"/>  
  </joint>  

  <link name="right_front_wheel">  

    <inertial>  

      <origin xyz="0.08 -0.08 0.025"/>  

      <mass value="0.1" />  

       <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0"/>  

    </inertial>  

    <visual>  

      <geometry>  

        <cylinder length=".02" radius="0.025"/>  

      </geometry>  

      <material name="black">  

        <color rgba="0 0 0 1"/>  

      </material>  

    </visual>  

    <collision>  

      <origin rpy="0 1.57075 1.57075" xyz="0.08 -0.08 0.025"/>  

      <geometry>  

         <cylinder length=".02" radius="0.025"/>  

      </geometry>  

    </collision>  

  </link>  



  <joint name="base_r_wheel_joint" type="continuous">  

    <axis xyz="0 0 1"/>  

    <parent link="base_link"/>  

    <child link="right_front_wheel"/>  

    <origin rpy="0 1.57075 1.57075" xyz="0.08 -0.08 0.025"/>  

    <limit effort="100" velocity="100"/>  

    <joint_properties damping="0.0" friction="0.0"/>  

 </joint>  



 <link name="left_back_wheel">  

    <inertial>  

      <origin xyz="-0.08 0.08 0.025"/>  

      <mass value="0.1" />  

       <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0"/>  

    </inertial>  

    <visual>  

      <geometry>  

        <cylinder length=".02" radius="0.025"/>  

      </geometry>  

      <material name="black">  

        <color rgba="0 0 0 1"/>  

      </material>  

   </visual>  

   <collision>  

       <origin rpy="0 1.57075 1.57075" xyz="-0.08 0.08 0.025"/>  

      <geometry>  

         <cylinder length=".02" radius="0.025"/>  

      </geometry>  

    </collision>  

  </link>  



  <joint name="left_back_wheel_joint" type="continuous">  

    <axis xyz="0 0 1"/>  

    <parent link="base_link"/>  

    <child link="left_back_wheel"/>  

    <origin rpy="0 1.57075 1.57075" xyz="-0.08 0.08 0.025"/>  

    <limit effort="100" velocity="100"/>  

    <joint_properties damping="0.0" friction="0.0"/>  

  </joint>  



  <link name="right_back_wheel">  

    <inertial>  

       <origin xyz="-0.08 -0.08 0.025"/>  

       <mass value="0.1" />  

       <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0"/>  

    </inertial>  

    <visual>  

      <geometry>  

        <cylinder length=".02" radius="0.025"/>  

      </geometry>  

      <material name="black">  

        <color rgba="0 0 0 1"/>  

      </material>  

   </visual>  

   <collision>  

      <origin rpy="0 1.57075 1.57075" xyz="-0.08 -0.08 0.025"/>  

      <geometry>  

         <cylinder length=".02" radius="0.025"/>  

      </geometry>  

    </collision>  

  </link>  





  <joint name="right_back_wheel_joint" type="continuous">  

    <axis xyz="0 0 1"/>  

    <parent link="base_link"/>  

    <child link="right_back_wheel"/>  

    <origin rpy="0 1.57075 1.57075" xyz="-0.08 -0.08 0.025"/>  

    <limit effort="100" velocity="100"/>  

    <joint_properties damping="0.0" friction="0.0"/>  

  </joint>  



  <link name="head">  

    <inertial>  

      <origin xyz="0.08 0 0.08"/>  

      <mass value="0.1" />  

      <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0"/>  

    </inertial>  

    <visual>  

      <geometry>  

        <box size=".02 .03 .03"/>  

      </geometry>  

      <material name="white">  

        <color rgba="1 1 1 1"/>  

      </material>  

     </visual>  

     <collision>  

      <origin xyz="0.08 0 0.08"/>  

      <geometry>  

         <cylinder length=".02" radius="0.025"/>  

      </geometry>  

    </collision>  

  </link>  



  <joint name="tobox" type="fixed">  

    <parent link="base_link"/>  

    <child link="head"/>  

    <origin xyz="0.08 0 0.08"/>  

  </joint>  

  </xacro:macro>  



</robot>

2、gazebo属性部分 gazebo.urdf.xacro

<?xml version="1.0"?>  
<robot xmlns:controller="http://playerstage.sourceforge.net/gazebo/xmlschema/##controller"   

    xmlns:interface="http://playerstage.sourceforge.net/gazebo/xmlschema/##interface"   

    xmlns:sensor="http://playerstage.sourceforge.net/gazebo/xmlschema/##sensor"   

    xmlns:xacro="http://ros.org/wiki/xacro"   

    name="smartcar_gazebo">  



<!-- ASUS Xtion PRO camera for simulation -->  

<!-- gazebo_ros_wge100 plugin is in kt2_gazebo_plugins package -->  

<xacro:macro name="smartcar_sim">  

    <gazebo reference="base_link">  

        <material>Gazebo/Blue</material>  

    </gazebo>  



    <gazebo reference="right_front_wheel">  

        <material>Gazebo/FlatBlack</material>  

    </gazebo>  

    <gazebo reference="right_back_wheel">  

        <material>Gazebo/FlatBlack</material>  

    </gazebo>  

    <gazebo reference="left_front_wheel">  

        <material>Gazebo/FlatBlack</material>  

    </gazebo>   

    <gazebo reference="left_back_wheel">  

        <material>Gazebo/FlatBlack</material>  

    </gazebo>  

    <gazebo reference="head">  

        <material>Gazebo/White</material>  

    </gazebo>  

</xacro:macro>  

</robot>

3、主文件 smartcar.urdf.xacro

<?xml version="1.0"?>  
<robot name="smartcar"    

    xmlns:xi="http://www.w3.org/2001/XInclude"  

    xmlns:gazebo="http://playerstage.sourceforge.net/gazebo/xmlschema/##gz"  

    xmlns:model="http://playerstage.sourceforge.net/gazebo/xmlschema/##model"  

    xmlns:sensor="http://playerstage.sourceforge.net/gazebo/xmlschema/##sensor"  

    xmlns:body="http://playerstage.sourceforge.net/gazebo/xmlschema/##body"  

    xmlns:geom="http://playerstage.sourceforge.net/gazebo/xmlschema/##geom"  

    xmlns:joint="http://playerstage.sourceforge.net/gazebo/xmlschema/##joint"  

    xmlns:controller="http://playerstage.sourceforge.net/gazebo/xmlschema/##controller"  

    xmlns:interface="http://playerstage.sourceforge.net/gazebo/xmlschema/##interface"  

    xmlns:rendering="http://playerstage.sourceforge.net/gazebo/xmlschema/##rendering"  

    xmlns:renderable="http://playerstage.sourceforge.net/gazebo/xmlschema/##renderable"  

    xmlns:physics="http://playerstage.sourceforge.net/gazebo/xmlschema/##physics"  

    xmlns:xacro="http://ros.org/wiki/xacro">  



  <xacro:include filename="$(find smartcar_description)/urdf/smartcar_body.urdf.xacro" />  


  <!-- Body of SmartCar, with plates, standoffs and Create (including sim sensors) -->  

  <xacro:smartcar_body/>  



  <xacro:smartcar_sim/>  
</robot>

创建lanuch文件smartcar_display.rviz.launch:

在launch文件中要启动节点和模拟器。

<launch>
    <param name="/use_sim_time" value="false" />

    <!-- Load the URDF/Xacro model of our robot -->
    <arg name="urdf_file" default="$(find xacro)/xacro '$(find smartcar_description)/urdf/smartcar.urdf.xacro'" />
    <arg name="gui" default="false" />

    <param name="robot_description" command="$(arg urdf_file)" />
    <param name="use_gui" value="$(arg gui)"/>

    <node name="arbotix" pkg="arbotix_python" type="arbotix_driver" output="screen">
        <rosparam file="$(find smartcar_description)/config/smartcar_arbotix.yaml" command="load" />
        <param name="sim" value="true"/>
    </node>

    <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" >
    </node>

    <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher">
        <param name="publish_frequency" type="double" value="20.0" />
    </node>

     <!-- We need a static transforms for the wheels -->
    <node pkg="tf" type="static_transform_publisher" name="odom_left_wheel_broadcaster" args="0 0 0 0 0 0 /base_link /left_front_link 100" />
    <node pkg="tf" type="static_transform_publisher" name="odom_right_wheel_broadcaster" args="0 0 0 0 0 0 /base_link /right_front_link 100" />

    <node name="rviz" pkg="rviz" type="rviz" args="-d $(find smartcar_description)/rviz/smartcar_xacro.rviz" />
</launch>

创建rviz文件smartcar_xacro.rviz:

Panels:
  - Class: rviz/Displays
    Help Height: 78
    Name: Displays
    Property Tree Widget:
      Expanded:
        - /Global Options1
        - /Status1
      Splitter Ratio: 0.5
    Tree Height: 353
  - Class: rviz/Selection
    Name: Selection
  - Class: rviz/Tool Properties
    Expanded:
      - /2D Pose Estimate1
      - /2D Nav Goal1
      - /Publish Point1
    Name: Tool Properties
    Splitter Ratio: 0.5886790156364441
  - Class: rviz/Views
    Expanded:
      - /Current View1
    Name: Views
    Splitter Ratio: 0.5
  - Class: rviz/Time
    Experimental: false
    Name: Time
    SyncMode: 0
    SyncSource: ""
Preferences:
  PromptSaveOnExit: true
Toolbars:
  toolButtonStyle: 2
Visualization Manager:
  Class: ""
  Displays:
    - Alpha: 0.5
      Cell Size: 1
      Class: rviz/Grid
      Color: 160; 160; 164
      Enabled: true
      Line Style:
        Line Width: 0.029999999329447746
        Value: Lines
      Name: Grid
      Normal Cell Count: 0
      Offset:
        X: 0
        Y: 0
        Z: 0
      Plane: XY
      Plane Cell Count: 10
      Reference Frame: <Fixed Frame>
      Value: true
    - Alpha: 1
      Class: rviz/RobotModel
      Collision Enabled: false
      Enabled: true
      Links:
        All Links Enabled: true
        Expand Joint Details: false
        Expand Link Details: false
        Expand Tree: false
        Link Tree Style: Links in Alphabetic Order
        base_link:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        head:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        left_back_wheel:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        left_front_wheel:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        right_back_wheel:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        right_front_wheel:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
      Name: RobotModel
      Robot Description: robot_description
      TF Prefix: ""
      Update Interval: 0
      Value: true
      Visual Enabled: true
    - Angle Tolerance: 0.10000000149011612
      Class: rviz/Odometry
      Covariance:
        Orientation:
          Alpha: 0.5
          Color: 255; 255; 127
          Color Style: Unique
          Frame: Local
          Offset: 1
          Scale: 1
          Value: true
        Position:
          Alpha: 0.30000001192092896
          Color: 204; 51; 204
          Scale: 1
          Value: true
        Value: true
      Enabled: true
      Keep: 100
      Name: Odometry
      Position Tolerance: 0.10000000149011612
      Queue Size: 10
      Shape:
        Alpha: 1
        Axes Length: 1
        Axes Radius: 0.10000000149011612
        Color: 255; 25; 0
        Head Length: 0.30000001192092896
        Head Radius: 0.10000000149011612
        Shaft Length: 1
        Shaft Radius: 0.05000000074505806
        Value: Arrow
      Topic: /odom
      Unreliable: false
      Value: true
  Enabled: true
  Global Options:
    Background Color: 48; 48; 48
    Default Light: true
    Fixed Frame: odom
    Frame Rate: 30
  Name: root
  Tools:
    - Class: rviz/Interact
      Hide Inactive Objects: true
    - Class: rviz/MoveCamera
    - Class: rviz/Select
    - Class: rviz/FocusCamera
    - Class: rviz/Measure
    - Class: rviz/SetInitialPose
      Theta std deviation: 0.2617993950843811
      Topic: /initialpose
      X std deviation: 0.5
      Y std deviation: 0.5
    - Class: rviz/SetGoal
      Topic: /move_base_simple/goal
    - Class: rviz/PublishPoint
      Single click: true
      Topic: /clicked_point
  Value: true
  Views:
    Current:
      Class: rviz/Orbit
      Distance: 2.777475595474243
      Enable Stereo Rendering:
        Stereo Eye Separation: 0.05999999865889549
        Stereo Focal Distance: 1
        Swap Stereo Eyes: false
        Value: false
      Field of View: 0.7853981852531433
      Focal Point:
        X: 0
        Y: 0
        Z: 0
      Focal Shape Fixed Size: true
      Focal Shape Size: 0.05000000074505806
      Invert Z Axis: false
      Name: Current View
      Near Clip Distance: 0.009999999776482582
      Pitch: 0.785398006439209
      Target Frame: <Fixed Frame>
      Yaw: 0.785398006439209
    Saved: ~
Window Geometry:
  Displays:
    collapsed: false
  Height: 650
  Hide Left Dock: false
  Hide Right Dock: false
  QMainWindow State: 000000ff00000000fd000000040000000000000156000001ecfc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000001ec000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000001ecfc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d000001ec000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000005560000003efc0100000002fb0000000800540069006d0065010000000000000556000002eb00fffffffb0000000800540069006d00650100000000000004500000000000000000000002e5000001ec00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
  Selection:
    collapsed: false
  Time:
    collapsed: false
  Tool Properties:
    collapsed: false
  Views:
    collapsed: false
  Width: 1366
  X: 0
  Y: 27

创建config文件夹并建立smartcar_arbotix.yaml文件放置配置参数:

port: /dev/ttyUSB0
baud: 115200
rate: 20
sync_write: True
sync_read: True
read_rate: 20
write_rate: 20

controllers: {
   ##  Pololu motors: 1856 cpr = 0.3888105m travel = 4773 ticks per meter (empirical: 4100)
   base_controller: {type: diff_controller, base_frame_id: base_link, base_width: 0.26, ticks_meter: 4100, Kp: 12, Kd: 12, Ki: 0, Ko: 50, accel_limit: 1.0 }
}

仿真测试:

首先运行lanuch,既可以看到rviz中的机器人:

roslaunch smartcar_description smartcar_display.rviz.launch

将frame id改为odom,然后发布一条消息:

rostopic pub -r 10 /cmd_vel geometry_msgs/Twist '{linear: {x: 0.5, y: 0, z: 0}, angular: {x: 0, y: 0, z: 0.5}}'

通过rqt_graph查看节点关系。

3.键盘控制

每次让机器人移动还要在终端里输入指令,这也太麻烦了,有没有办法通过键盘来控制机器人的移动呢?答案是当然的了。

我们循序渐进,先来把前面的指令写入python脚本进行自动发指令。

Python自动指令
创建一个单独的程序包:roscreate-pkg smartcar_teleop rospy geometry_msgs std_msgs roscpp

在smartcar_teleop/scripts文件夹下创建teleop.py编写如下的控制代码:

##!/usr/bin/env python3  

import roslib; roslib.load_manifest('smartcar_teleop')  

import rospy  

from geometry_msgs.msg import Twist  

from std_msgs.msg import String  



class Teleop:  

    def __init__(self):  

        pub = rospy.Publisher('cmd_vel', Twist)  

        rospy.init_node('smartcar_teleop')  

        rate = rospy.Rate(rospy.get_param('~hz', 1))  

        self.cmd = None  



        cmd = Twist()  

        cmd.linear.x = 0.2  

        cmd.linear.y = 0  

        cmd.linear.z = 0  

        cmd.angular.z = 0  

        cmd.angular.z = 0  

        cmd.angular.z = 0.5  



        self.cmd = cmd  

        while not rospy.is_shutdown():  

            str = "hello world %s" % rospy.get_time()  

            rospy.loginfo(str)  

            pub.publish(self.cmd)  

            rate.sleep()  



if __name__ == '__main__':Teleop()

执行Python自动脚本: rosrun smartcar_teleop teleop.py

也可以建立一个launch文件(teleop.launch)运行:

<launch>  

  <arg name="cmd_topic" default="cmd_vel" />  

  <node pkg="smartcar_teleop" type="teleop.py" name="smartcar_teleop">  

    <remap from="cmd_vel" to="$(arg cmd_topic)" />  

  </node>  

</launch>

运行成功后,在rviz里看smartcar有没有动起来!!!

Python键盘控制:
虽然很多机器人的键盘控制使用的都是C++编写的代码,但是考虑到python的强大,我们还是需要尝试使用python来编写程序。

创建keyboard.py,粘贴以下代码:

##!/usr/bin/env python3  

## -*- coding: utf-8 -*  

import  os  

import  sys  

import  tty, termios  

import roslib; roslib.load_manifest('smartcar_teleop')  

import rospy  

from geometry_msgs.msg import Twist  

from std_msgs.msg import String  


## 全局变量  

cmd = Twist()  

pub = rospy.Publisher('cmd_vel', Twist)  


def keyboardLoop():  

    ##初始化  

    rospy.init_node('smartcar_teleop')  

    rate = rospy.Rate(rospy.get_param('~hz', 1))  

    ##速度变量  

    walk_vel_ = rospy.get_param('walk_vel', 0.5)  

    run_vel_ = rospy.get_param('run_vel', 1.0)  

    yaw_rate_ = rospy.get_param('yaw_rate', 1.0)  

    yaw_rate_run_ = rospy.get_param('yaw_rate_run', 1.5)  

    max_tv = walk_vel_  

    max_rv = yaw_rate_  

    ##显示提示信息  

    print ("Reading from keyboard" ) 

    print ("Use WASD keys to control the robot"  )

    print ("Press Caps to move faster"  )

    print ("Press q to quit"  )

    ##读取按键循环  

    while not rospy.is_shutdown():  

        fd = sys.stdin.fileno()  

        old_settings = termios.tcgetattr(fd)  

        ##不产生回显效果  

        old_settings[3] = old_settings[3] & ~termios.ICANON & ~termios.ECHO  

        try :  

            tty.setraw( fd )  

            ch = sys.stdin.read( 1 )  

        finally :  

            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)  



        if ch == 'w':  

            max_tv = walk_vel_  

            speed = 1  

            turn = 0  

        elif ch == 's':  

            max_tv = walk_vel_  

            speed = -1  

            turn = 0  

        elif ch == 'a':  

            max_rv = yaw_rate_  

            speed = 0  

            turn = 1  

        elif ch == 'd':  

            max_rv = yaw_rate_  

            speed = 0  

            turn = -1  

        elif ch == 'W':  

            max_tv = run_vel_  

            speed = 1  

            turn = 0  

        elif ch == 'S':  

            max_tv = run_vel_  

            speed = -1  

            turn = 0  

        elif ch == 'A':  

            max_rv = yaw_rate_run_  

            speed = 0  

            turn = 1  

        elif ch == 'D':  

            max_rv = yaw_rate_run_  

            speed = 0  

            turn = -1  

        elif ch == 'q':  

            exit()  

        else:  

            max_tv = walk_vel_  

            max_rv = yaw_rate_  

            speed = 0  

            turn = 0  



        ##发送消息  

        cmd.linear.x = speed * max_tv;  

        cmd.angular.z = turn * max_rv;  

        pub.publish(cmd)  

        rate.sleep()  

        ##停止机器人  

        stop_robot();  



def stop_robot():  

    cmd.linear.x = 0.0  

    cmd.angular.z = 0.0  

    pub.publish(cmd)  



if __name__ == '__main__':  

    try:  

        keyboardLoop()  

    except rospy.ROSInterruptException:  

        pass
4.操纵杆控制

对于移动机器人,键盘的控制往往满足不了我们的需求。

操作杆(joystick)控制会更加有操作感,ROS中的很多机器人也带有操作杆的相关代码,只需要简单的移植即可。

一、测试操作杆驱动

首先将操作杆的接口插入电脑,然后在终端中输入:ls /dev/input/

其中的js0就代表我们的操作杆。然后测试操作杆的操作是否有效,输入:sudo jstest /dev/input/js0

然后会在终端中显示操作杆的各个控制值的即时值,操作操作杆,如果每个按键和操作都有效,说明操作杆是正常的。最后在ROS中的节点里进行测试。打开joy节点:rosrun joy joy_node

再打开一个窗口,输入下面命令,查看数据:rostopic echo joy

二、控制代码

从上面的测试中,我们发现在前后左右摇动操作杆时,相应改变的数据是Axes中0号和1号位的数据,也是我们最常用的数据,其他按键对应的位置也可以找到,编程的时候就是利用的这些数据位置,所以一定要找到每个按键的对应编号。

ROS中已经为我们建立了操作杆的数据结构。

我们主要用到的就是axes和buttons数据。最终的代码如下:

import roslib; roslib.load_manifest(&##39;smartcar_teleop&##39;)  

import rospy  

from sensor_msgs.msg import Joy  

from geometry_msgs.msg import Twist  

from std_msgs.msg import String  

class Teleop:  

    def __init__(self):  

        rospy.init_node(&##39;smartcar_teleop_joy&##39;)  



        self.turn_scale = rospy.get_param(&##39;~turn_scale&##39;)  

        self.drive_scale = rospy.get_param(&##39;~drive_scale&##39;)  

        self.deadman_button = rospy.get_param(&##39;~deadman_button&##39;, 0)  



        self.cmd = None  

        cmd_pub = rospy.Publisher(&##39;cmd_vel&##39;, Twist)  



        announce_pub = rospy.Publisher(&##39;/smartcar/announce/teleops&##39;,  

                                       String, latch=True)  

        announce_pub.publish(rospy.get_namespace());  



        rospy.Subscriber("joy", Joy, self.callback)  

        rate = rospy.Rate(rospy.get_param(&##39;~hz&##39;, 20))  



        while not rospy.is_shutdown():  

            rate.sleep()  

            if self.cmd:  

                cmd_pub.publish(self.cmd)  



    def callback(self, data):  

        """ Receive joystick data, formulate Twist message. """  

        cmd = Twist()  

        cmd.linear.x = data.axes[1] * self.drive_scale  

        cmd.angular.z = data.axes[0] * self.turn_scale  



        if data.buttons[self.deadman_button] == 1:  

            self.cmd = cmd  

        else:  

            self.cmd = None  



if __name__ == "__main__": Teleop()

三、机器人控制

首先来创建一个launch文件(teleop_joy.launch):

<launch>  

  <arg name="drive_speed" default="1.0" />  

  <arg name="turn_speed" default="1.0" />  

  <arg name="joy_dev" default="/dev/input/js0" />  

  <arg name="cmd_topic" default="cmd_vel" />  



  <node pkg="joy" type="joy_node" name="joy_node">  

    <param name="dev" value="$(arg joy_dev)" />  

    <param name="deadzone" value="0.3" />  

  </node>  



  <node pkg="smartcar_teleop" type="teleop_joy.py" name="smartcar_teleop">  

    <param name="turn_scale" value="$(arg turn_speed)" />  

    <param name="drive_scale" value="$(arg drive_speed)" />  

    <remap from="cmd_vel" to="$(arg cmd_topic)" />  

  </node>  

</launch>

在rviz中打开我们的机器人模型,然后打开操作杆的控制节点:

roslaunch smartcar_display.rviz.launch  
roslaunch smartcar_teleop teleop_joy.launch

在新终端中输入:rostopic echo joy可以查看到实时的操作杆控制数据。

三、action通讯机制

ROS中常用的通讯机制是topic和service,但是在很多场景下,这两种通讯机制往往满足不了我们的需求,比如机械臂控制,如果用topic发布一个运动目标,由于topic没有反馈,还需要另外订阅一个机械臂状态的topic,来获得运动过程的状态。如果用service来发布运动目标,虽然可以获得反馈,但是反馈只有一次,对于我们的控制来讲数据太少了,而且如果反馈迟迟没收到,也只能傻傻等待,干不了其他事情。那么有没有一种更加适合的通讯机制,来满足类似这样场景的需求呢?当然是有的,就是我们这篇要讲到的action通讯机制。

什么是action

ROS中有一个actinlib的功能包集,实现了action的通讯机制。那么什么是action呢?action也是一种类似于service的问答通讯机制,不一样的地方是action还带有一个反馈机制,可以不断反馈任务的实施进度,而且可以在任务实施过程中,中止运行。

回到我们前边提到的场景,我们使用action来发布一个机器人的运动目标,机器人接到这个action后,就开始运动,在运动过程中不断反馈当前的运动状态,在运动过程中我们可以取消运动,让机器人停止,如果机器人完成了运动目标,那么action会返回任务完成的标志。

action的工作机制

action采用了服务器端/客户端(client and server)的工作模式

client和server之间通过actionlib定义的“action protocol”进行通讯。这种通讯协议是基于ROS的消息机制实现的,为用户提供了client和server的接口

在上边的action的接口框图上,我们可以看到,client向server端发布任务目标以及在必要的时候取消任务,server会向client发布当前的状态、实时的反馈和最终的任务结果。

  • goal:任务目标
  • cancel:请求取消任务
  • status:通知client当前的状态
  • feedback:周期反馈任务运行的监控数据
  • result:向client发送任务的执行结果,这个topic只会发布一次。
action的定义

ROS中的message是通过.msg文件来定义的,service是通过.srv定义,那么action是不是也是通过类似的方法定义呢?答案是肯定的,action通过.action文件定义,放置在功能包的action文件夹下,格式类似如下:

## Define the goal
uint32 dishwasher_id  ## Specify which dishwasher we want to use
---
## Define the result
uint32 total_dishes_cleaned
---
## Define a feedback message 
float32 percent_complete

从上边的.action文件示例中,我们可以发现,定义一个action需要三个部分:goal、result、feedback,具体含义在上一小节中已经讲过了。

实现了.action之后,还需要将这个文件加入编译,在CMakeLists.txt文件中添加如下的编译规则:

find_package(catkin REQUIRED genmsg actionlib_msgs actionlib)

add_action_files(DIRECTORY action FILES DoDishes.action) generate_messages(DEPENDENCIES actionlib_msgs

还需要在功能包的package.xml中添加:

<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<run_depend>actionlib</run_depend> 
<run_depend>actionlib_msgs</run_depend>

现在就可以进行编译了,编译完成后会产生一系列的.msg文件:

DoDishesAction.msg
DoDishesActionGoal.msg
DoDishesActionResult.msg
DoDishesActionFeedback.msg
DoDishesGoal.msg
DoDishesResult.msg
DoDishesFeedback.msg

这些不同的消息类型,我们在调用action时根据需要会用到。从这里我们也可以看到,action确实是基于message实现的。

例程运行

洗盘子例程

rosrun action_tutorials DoDishes_server
rosrun action_tutorials DoDishes_client

四、ros_tf

五、ros与navigation

SLAM定位与建图

slam-toolbox是基于karto slam算法的改进,需要odom与scan的数据。

sudo apt-get install ros-noetic-slam-toolbox

gmapping算法,需要odom与scan的数据。

hector算法,只需要scan的数据。

cartographer是谷歌的slam算法,需要odom与scan的数据。

六、ros与TEB轨迹规划

介绍teb_local_planner本地规划算法的整合使用。

TEB优化测试

介绍如何运行轨迹优化以及如何更改基础参数以设置自定义行为和性能。

安装TEB(二进制):sudo apt-get install ros-noetic-teb-local-planner

安装TEB例程(源码):

git clone https://ghproxy.com/https://github.com/rst-tu-dortmund/teb_local_planner_tutorials.git
sudo apt-get install ros-noetic-stage-ros   // 依赖项
sudo apt-get install ros-noetic-navigation
sudo apt-get install ros-noetic-map-server

轨迹优化:

该软件包包括一个简单的测试节点(test_optim_node),它可以优化固定开始和目标姿势之间的轨迹。

我们首先开始在开始和目标之间配置单个轨迹(Timed-Elastic-Band)的规划,然后我们将在独特的拓扑中激活和设置规划。

使用ROS参数服务器停用并行计划(确保运行roscore)

`rosparam set /test_optim_node/enable_homotopy_class_planning False`

启动test_optim_node并结合预配置的rviz节点进行可视化:

`roslaunch teb_local_planner test_optim_node.launch`

通过运行rqt_reconfigure来定制我们的优化:

`rosrun rqt_reconfigure rqt_reconfigure`

独特拓扑中多个轨迹的优化:

我们现在解决本地优化方案的问题,并在独特的拓扑中实现并行规划。

此扩展计划程序默认启用,需要更多计算资源。

重新启动roscore或重新激活扩展计划程序:

`rosparam set /test_optim_node/enable_homotopy_class_planning True`

启动test_optim_node并结合预配置的rviz节点进行可视化:

`roslaunch teb_local_planner test_optim_node.launch`

通过在每个轨迹配置处显示各个姿势(如红色箭头)来突出显示当前最佳轨迹(在最便宜的优化成本意义上)。
TEB优化反馈
介绍如何检查优化轨迹的反馈; 提供了一个示例,其可视化当前所选轨迹的速度分布
在前面的示例中,您学习了如何测试优化,如何调整参数以及如何在rviz中可视化生成的轨迹。
然而,在rviz中可视化的轨迹不包含任何时间信息,仅显示其空间状态。
为了进一步参数调整或评估目的,可能有兴趣获得对底层优化状态的访问(涉及时间优化状态)。
因此teb_local_planner提供反馈消息teb_local_planner/FeedbackMsg,其包含所有内部状态和一些推断变量(如速度曲线)。
请注意,加速度曲线当前为空。
此外,该消息包含在独特拓扑中找到的所有备选轨迹。
当前选择的轨迹索引存储在变量selected_trajectory_idx中。
反馈主题(参见规划器API)可以由任何节点订阅(例如,将数据导出到文件,编写一些自定义可视化,......)。

注意:

默认情况下关闭发送反馈消息以减轻计算负担。
可以通过将rosparam ~/publish_feedback设置为true或通过调用rqt_reconfigure来启用它。

可视化速度曲线

在下文中,提供了一个小的python脚本,它订阅了上一个教程中介绍的test_optim_node。
脚本取决于pypose以创建绘图。
仅显示当前所选轨迹的速度分布。

创建visualize_test.py,粘贴下面代码:

##!/usr/bin/env python3

import rospy, math
from teb_local_planner.msg import FeedbackMsg, TrajectoryMsg, TrajectoryPointMsg
from geometry_msgs.msg import PolygonStamped, Point32
import numpy as np
import matplotlib.pyplot as plotter

def feedback_callback(data):
  global trajectory

  if not data.trajectories: ## empty
    trajectory = []
    return
  trajectory = data.trajectories[data.selected_trajectory_idx].trajectory


def plot_velocity_profile(fig, ax_v, ax_omega, t, v, omega):
  ax_v.cla()
  ax_v.grid()
  ax_v.set_ylabel('Trans. velocity [m/s]')
  ax_v.plot(t, v, '-bx')
  ax_omega.cla()
  ax_omega.grid()
  ax_omega.set_ylabel('Rot. velocity [rad/s]')
  ax_omega.set_xlabel('Time [s]')
  ax_omega.plot(t, omega, '-bx')
  fig.canvas.draw()



def velocity_plotter():
  global trajectory
  rospy.init_node("visualize_velocity_profile", anonymous=True)

  topic_name = "/test_optim_node/teb_feedback" ## define feedback topic here!
  rospy.Subscriber(topic_name, FeedbackMsg, feedback_callback, queue_size = 1) 

  rospy.loginfo("Visualizing velocity profile published on '%s'.",topic_name) 
  rospy.loginfo("Make sure to enable rosparam 'publish_feedback' in the teb_local_planner.")

  ## two subplots sharing the same t axis
  fig, (ax_v, ax_omega) = plotter.subplots(2, sharex=True)
  plotter.ion()
  plotter.show()


  r = rospy.Rate(2) ## define rate here
  while not rospy.is_shutdown():

    t = []
    v = []
    omega = []

    for point in trajectory:
      t.append(point.time_from_start.to_sec())
      v.append(point.velocity.linear.x)
      omega.append(point.velocity.angular.z)

    plot_velocity_profile(fig, ax_v, ax_omega, np.asarray(t), np.asarray(v), np.asarray(omega))

    r.sleep()

if __name__ == '__main__': 
  try:
    trajectory = []
    velocity_plotter()
  except rospy.ROSInterruptException:
    pass

该脚本已作为visualize_velocity_profile.py包含在teb_local_planner_tutorials包中(但自带的脚本我运行不了,粘贴我上面的是可以正常运行的)。

运行优化反馈测试:

rosparam set /test_optim_node/publish_feedback true ## or use rqt_reconfigure later
roslaunch teb_local_planner test_optim_node.launch
rosrun teb_local_planner_tutorials visualize_test.py

将看到线速度和角速度曲线。

七、rosbridge_suite

Rosbridge为非ROS程序提供了一个使用ROS功能的JSON API。

rosbridge协议是用于向ROS(并且在理论上,任何其他机器人中间件)发送基于JSON的命令的规范。

rosbridge_suite包是一个包集合,用于实现rosbridge协议和提供WebSocket传输层。

包含如下几个包:

rosbridge_library - 核心rosbridge包rosbridge_library负责获取JSON字符串并将命令发送到ROS反之亦然
rosapi - 通过服务调用使某些ROS actions可访问 这包括获取和设置参数获取主题列表等
rosbridge_server - 虽然rosbridge_library提供了JSON ROS转换但它将传输层留给其他人
    Rosbridge_server提供了一个WebSocket连接所以浏览器可以与rosbridge交谈”。
    Roslibjs是一个浏览器的JavaScript库可以通过rosbridge_server与ROS交谈

例程运行:

rosbridge安装:sudo apt-get install ros-<rosdistro>-rosbridge-server
rosbridge运行:roslaunch rosbridge_server rosbridge_websocket.launch
默认情况下,这将运行rosbridge并创建一个WebSocket在端口9090,可以通过在ROS中设置~/port参数来配置端口。

例如修改端口为8080,如下所示:

<launch>
  <include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" > 
     <arg name="port" value="8080"/>
  </include>
</launch>

现在rosbridge已经启动,并且WebSocket连接可用,我们可以创建一个基本的HTML网页发送和接收呼叫rosbridge。
打开localhost:9090

另外,类似Rviz,ROS其实提供了一个网页可视化的工具Webviz,参考开发教程如下:webtools

八、ROS产品化探索

通信机制篇

概要

近几年,机器人和人工智能繁荣发展,曾经运行在实验室的机器人已经逐渐走入千家万户的生活。作为机器人开发利器的ROS也得到了非常广泛的应用,成为机器人领域的普遍标准。

ROS原本针对科研领域的PR2机器人开发,这种大繁荣的景象远远超过ROS的最初目标,也使得ROS的缺陷在广泛应用的同时暴露无遗:

缺乏构建多机器人系统的标准方法;

在Windows、MacOS、RTOS等系统上无法应用或者功能有限;

缺少实时性方面的设计;

需要良好的网络环境保证数据的完整性,而且网络没有数据加密、安全防护等功能;

ROS 1的稳定性欠佳,研究开发与上市产品之间的过渡艰难;

正是由于ROS1存在种种问题,才促使ROS2.0的出现。ROS2.0虽然从根本上解决了ROS1.0的许多问题,但是却无法短时间替代ROS1.0在庞大生态系统中的关键位置,只能借助越来越多的关键开发者逐渐推进。所以从目前ROS2.0的情况来看,近几年ROS1.0的应用还会是主流。

ROS1.0能否产品化?

那么目前的ROS1.0到底能不能在机器人的产品化过程中应用?如果可以,又该如何应用于机器人产品的开发?

ROS的核心虽然是通信机制,但是如今的ROS已经远远超过通信机制的范畴。总体来讲,ROS包含四个部分:通信机制、开发工具、应用功能、生态系统。

所以在说ROS产品化的时候,我们需要针对这四个方面进行讨论。由于内容较多,这个主题会分为几篇文章分别讨论。

首先来看第一个部分:

1.通信机制

点对点的分布式通信机制是ROS的核心,使用了基于TCP/IP的通信方式,实现模块间点对点的松耦合连接,可以执行若干种类型的通信,包括基于话题(Topic)的异步数据流通信,基于服务(Service)的同步数据流通信,还有参数服务器上的数据存储等。

但是这种通信机制的实时性和稳定性不好,而且强依赖于中心节点ROS Master。比如我们在机器人产品开发的过程中,就遇到无数类似下边的问题:

在进行压力测试时,系统连续运行一周时间,运行到第四天时(时间不确定),ROS Master莫名宕机,整个系统不再受控,某个节点突然失效也是时有发生的。。。

系统存在大量话题和数据时,本地传输的数据延时大而不确定,远程传输的数据更是经常受到带宽和处理性能的影响。

在系统中频繁创建话题时(比如连续创建几百个话题),某些话题的数据莫名就消失了。

通信的实时性较差,没办法实现毫秒级的机器人控制。

通信数据是开放式的,没有加密,只要在网络中的节点都可以轻松获取。

核心机制的性能没有优化,运行在配置较低的ARM上会占用过多资源。

这样看来,好像ROS的核心部分在机器人产品中完全没有可用性呀!如何去突破这个瓶颈呢?先来看看部分开发者的改良方法。

首先聊聊百度

自从百度All in人工智能之后,无人驾驶汽车平台Apollo就被推上了风口浪尖,甚至还登上了2018春晚的舞台。在这个吸睛无数的Apollo平台背后,就隐藏着ROS的身影。

Apollo平台基于ROS开发,但是对通信机制部分进行了众多改变,有兴趣的小伙伴可以看Apollo改良之后的ROS。

1.去中心化也就是干掉ROS Master这部分使用了DDS技术
DDS虽然提供的也是发布/订阅模型的通信机制但商用版本可以达到军工标准国际上有几家大公司也在推进DDS在ROS 2.0中的应用 
2.使用共享内存的方法优化大数据传输的瓶颈
共享内存也是ROS2.0中时间敏感型数据通信的方法吞吐量和传输速度肯定可以得到很大程度的优化同时占用的CPU资源也比较少
使用Protobuf优化数据格式的兼容性
Protobuf是google开源的一种结构化数据存储格式百度拿它取代了ROS中的Message可以向后兼容数据协议的扩展
无人驾驶汽车的稳定性是一个性命攸关的问题百度敢对ROS进行大刀阔斧的优化也正说明了ROS的行业认可度和强大生命力

其他

基于ROS的机器人产品有不少已经投入使用,除上边提到的百度无人驾驶汽车,还有另外一个开源无人驾驶汽车平台Autoware、NASA部署在国际空间站的Robonaut 2机器人等,目前很多大公司的开源部门也在推进ROS的产品化应用。
综观这些机器人产品和平台,都可以说是基于ROS开发的,但是在ROS通信机制部分,也都进行大量的优化和改良。
这种做法对于创业型的公司来讲,如果不是以ROS的改良作为产品的话,往往都不愿意去干这件事情,费时费力。那么对于小公司来讲,如果不想去动ROS的核心通信部分,应该怎么做呢?
这就要说回笔者(guyueju)在创业过程中的实践了。大概有两种方法:
第一种:自己不愿意做,就用别人做好的呗
ROS是一个庞大的开源社区,上边提到的那些ROS优化,基本上都可以在社区中找到开源实现,或是找其他公司做好的成熟产品,站在别人的肩膀上继续前进。
但是这样也有问题,一方面,别人实现的功能不一定完全符合自己的需求,另外一方面,这些实现的相关文档不多,有问题还得从源码上解决。
那就请使用第二种方法!
第二种:自己不愿意做,也不想基于其他平台做,那就别强求了。
这就是笔者创业公司采用的策略——放弃ROS的核心通信机制部分。这句话说来容易,做起来难!毕竟机器人系统还是需要通信功能的,于是我们重新设计了一个。