ROS安装教程和入门笔记

大四做完毕设闲着没事打算学习一下无人机仿真,然后就开始了解ROS,研究生阶段涉及仿真时或许可以用上。学习过程中主要记录了用到的教程以及遇到的一些坑,限于篇幅没有完全记录教程中的所有细节,所以这篇文章的使用方式应该是根据给出的教程参考链接打开原来的教程,按照原来的教程学习,在学习进行到某一步遇到问题时,可以看看这篇文章在这一步是否遇到了相同的问题,如果有则方便参考,没有只能自行探索解决办法了。希望这篇文章能给其他人入门提供一些帮助。

文章目录

    • 1. ROS中文入门教程
      • 安装ROS
      • ROS基本知识
      • 1.1 ROS文件系统导览
      • 1.2 创建ROS软件包
      • 1.3 理解ROS节点
      • 1.4 理解ROS话题
      • 1.5 理解ROS服务和参数
      • 1.6 使用rqt_console和roslaunch
      • 1.7 创建ROS消息和服务
      • 1.8 编写简单的发布者和订阅者(C++)
      • 1.9 编写简单的服务和客户端(C++)
      • 1.10 录制和回放数据
    • 2. TF学习
      • 2.1 TF示例
      • 2.2 写一个Static Broadcaster(Python)
      • 2.3 写一个Broadcaster(python)
      • 2.4 写一个Listener(Python)
      • 2.5 添加一个坐标系Frame(Python)
      • 2.6 了解tf2和时间
      • 2.7 跨时间的tf2
      • 2.8 四元数基础
      • 2.9 调试 tf2
      • 2.10 tf2使用传感器信息
      • 2.11设置机器人用tf2
    • 3. URDF模型学习
      • 3.1 用URDF从Scratch建立一个可视机器人模型
      • 3.2 创建一个可移动机器人模型
      • 3.3 给URDF模型添加物理和碰撞属性
      • 3.4 使用Xacro简化URDF文件
      • 3.5 在Gazebo中使用URDF
    • 4 Gazebo学习
      • 4.1 安装和开始Gazebo
      • 4.2 创建和引用自定义URDF物体进行仿真
      • 4.3 Gazebo ROS API for C-Turtle
      • 4.4 经典Gazebo教程
    • 5 PR2机器人学习
      • 5.1 创建场景
      • 5.2 ROS和Gazebo交互:
      • 5.3 PR2 仿真器工厂

1. ROS中文入门教程

安装ROS

安装时我参考的:一篇博客

安装和配置环境一般都比较麻烦,所谓从入门到放弃应该就是从这里开始的吧,只是环境配置就花费了一两天时间。在安装ROS的时候我用国内源提示找不到包,所以安装ROS的时候我换回Ubuntu官方源了(换回方法),但是在国内访问国外的网络特别慢而且容易出问题(懂得都懂),导致有些中途有些包没有下载下来而报错,根据终端提示用fixmissing参数再次尝试,一直尝试到所有包下载完毕就行。。。。

安装完毕后需要运行 sudo rosdep init,可能因为网络问题失败,解决办法参考这个:解决sudo rosdep init问题

解决完上面的init问题,再运行rosdep update,同样可能因为网络问题失败,如果遇到问题参考这个:解决rosdep update问题

ROS基本知识

学习时参考的:ROS官方中文入门教程

这个官方的教程解释的很清楚,所以学习这一部分时几乎没有遇到错误。看的时候忘记记笔记了,后面有机会复习的时候补充。

开始复习补充:

1.1 ROS文件系统导览

文件系统概念
软件包Packages:ROS由一个个实现不同功能的包构成。
清单Manifests(package.xml): 对软件包的描述,定义软件包之间的依赖关系。

文件系统工具
ROS自带的一些工具命令。
rospack:用来获取软件包的有关信息
例如寻找包所在路径:

rospack find [package_name]

roscd:切换目录到某个软件包所在路径

roscd [package_name]

如果已经运行过ROS程序,那么通过以下命令进入log路径:

roscd log

1.2 创建ROS软件包

一个catkin软件包由什么组成?

  • 包含package.xml文件:提供有关该软件包的信息,依赖等。
  • 包含CMakeLists.txt文件:提供编译信息。
  • 必须有自己的目录:一个目录下不能有嵌套的或多个软件包存在。
	workspace_folder/       	 		-- 第一级:工作空间文件夹
  		src/                   			-- 第二级:源代码文件夹
    	CMakeLists.txt       			-- 顶层的CMake文件,由catkin工具自动创建
    		package_1/
      			CMakeLists.txt     		-- 第一个包的的CMakeLists.txt
      			package.xml        		-- 第一个包的package.xml清单
    		...
    		package_n/
      			CMakeLists.txt     		-- 第n个包的的CMakeLists.txt
      			package.xml        		-- 第n个包的package.xml清单

创建catkin软件包
由于catkin_ws文件夹之前的一个软件包用到了,所以我自己创建的工作空间文件夹是catkin_ws2

cd ~/catkin_ws2/src

catkin_create_pkg创建名为beginner_tutorials的软件包,依赖于std_msgsroscpprospy

catkin_create_pkg beginner_tutorials std_msgs rospy roscpp

这条命令创建了beginner_tutorials文件夹,里面包含CMakeLists.txt文件和package.xml文件,提供编译所需信息。
编译软件包并配置环境
catkin_make编译刚刚的软件包(目前软件包应该什么也没有)

cd ~/catkin_ws2
catkin_make

编译完成后把这个工作空间添加到ROS环境中:

source ~/catkin_ws2/devel/setup.bash

查看软件包的依赖
查看一级依赖:

rospack depends1 beginner_tutorials

查看依赖的依赖(间接依赖):

rospack depends1 rospy

查看所有依赖:

rospack depends beginner_tutorials

package.xml文件解释
标签:任意内容,主要对包简短描述

  <description>The beginner_tutorials packagedescription>

标签:方便联系维护者

<maintainer email="[email protected]">Your Namemaintainer>

标签:说明开源许可协议

<license>BSDlicense>

依赖项标签:

  
  <build_depend>genmsgbuild_depend>
  
  <buildtool_depend>catkinbuildtool_depend>
  
  <exec_depend>python-yamlexec_depend>
  
  <test_depend>gtesttest_depend>

1.3 理解ROS节点

相关概念
计算图是ROS进程组成的点对点网络,共同处理输出。基本的计算图概念包括:

  • 节点(Nodes):节点是一个可执行文件,可以通过ROS来与其他节点进行通信。
  • 消息(Messages):订阅或发布话题时所使用的ROS数据类型。
  • 话题(Topics):节点可以将消息发布到话题,或通过订阅话题来接受消息。
  • 主节点(Master):ROS的命名服务
  • rosout:在ROS中相当于stdout/stderr
  • roscore:主节点+rosout+参数服务器

节点

注:节点是ROS中非常重要的一个概念,为了帮助初学者理解这个概念,这里举一个通俗的例子:
例如,咱们有一个机器人,和一个遥控器,那么这个机器人和遥控器开始工作后,就是两个节点。遥控器起到了下达指 令的作用;机器人负责监听遥控器下达的指令,完成相应动作。从这里我们可以看出,节点是一个能执行特定工作任 务的工作单元,并且能够相互通信,从而实现一个机器人系统整体的功能。在这里我们把遥控器和机器人简单定义为两个节点,实际上在机器人中根据控制器、传感器、执行机构等不同组成模块,还可以将其进一步细分为更多的节点,这个是根据用户编写的程序来定义的。)

客户端库
ROS客户端可以让用户用不同变成语言编写的节点相互通信:

  • rospy = Python客户端库
  • roscpp = C++客户端库

roscore:ROS核心
ROS核心,是在运行所有ROS程序前首先要运行的命令:

roscore

rosnode:ROS节点工具
保持roscore运行,打开一个新终端,运行查看正在运行的ROS节点:

rosnode list

查看某个节点的详情:

rosnode info /rosout

可以看到rosout节点的发布的消息,订阅的消息,提供的服务:

Node [/rosout]
Publications:
 * /rosout_agg [rosgraph_msgs/Log]

Subscriptions:
 * /rosout [unknown type]

Services:
 * /rosout/get_loggers
 * /rosout/set_logger_level

contacting node http://machine_name:54614/ ...
Pid: 5092

rosrun:ROS运行工具
用来直接运行某个包的节点:

rosrun [package_name] [node_name]

保持roscore运行,在新终端:

rosrun turtlesim turtlesim_node

可以看到一个乌龟(彩蛋:每次的乌龟都不同^ o ^):
ROS安装教程和入门笔记_第1张图片
这时查看节点:

rosnode list
/rosout
/turtlesim

可以ping节点检查是否正常运行:

rosnode ping turtlesim

1.4 理解ROS话题

运行乌龟程序:

roscore
打开新终端
rosrun turtlesim turtlesim_node

打开新终端,运行可以用键盘控制turtle:

rosrun turtlesim turtle_teleop_key

ROS安装教程和入门笔记_第2张图片
ROS话题
turtlesim_node节点和turtle_teleop_key节点是通过一个ROS话题来相互通信的。turtle_teleop_key发布键盘按下的消息到某个话题,turtlesim订阅这个话题以接受消息。
rqt_graph:显示当前运行的节点的话题
运行:

rosrun rqt_graph rqt_graph

ROS安装教程和入门笔记_第3张图片
可以看到节点/teleop_turtle发布/turtle1/cmd_vel话题被节点/turtlesim接收。
rostopic:获取ROS话题
查看rostopic如何使用:

rostopic -h

显示话题数据

rostopic echo [topic]

运行:

rostopic echo /turtle1/cmd_vel

在再键盘操作那个终端控制乌龟移动,可以看到echo这个终端输出了话题信息。刷新graph图也可以看到这个话题被rostopic接收。
ROS安装教程和入门笔记_第4张图片
查看所有话题:

rostopic list

查看话题中的消息类型:

rostopic type [topic]

发布消息到某个话题:

rostopic pub [topic] [msg_type] -- [参数]

例如只发布一条消息:

rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist -- ‘[2.0, 0.0, 0.0]‘ ’[0.0, 0.0, 1.8]

如果要以1Hz一直发消息:

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

查看数据发布的速度:

rostopic hz [topic]

rqt_plot:绘制话题数据
运行命令,通过gui窗口就可以绘制话题数据

rosrun rqt_plot rqt_plot

1.5 理解ROS服务和参数

服务services是节点之间通讯的另一个方式,服务允许节点发送一个请求reques,并获得一个响应response。

rosservice list         输出活跃服务的信息
rosservice call         用给定的参数调用服务
rosservice type         输出服务的类型
rosservice find         按服务的类型查找服务
rosservice uri          输出服务的ROSRPC uri

保持乌龟教程运行,查看服务:

rosservice list
/clear
/kill
/reset
/rosout/get_loggers
/rosout/set_logger_level
/spawn
/teleop_turtle/get_loggers
/teleop_turtle/set_logger_level
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/get_loggers
/turtlesim/set_logger_level

调用服务:
清除轨迹:

rosservice call /clear

生成乌龟:

rosservice call /spawn 2 2 0.2 ""

rosparam:ROS参数服务器
用来存储和操作数据。

rosparam set            设置参数
rosparam get            获取参数
rosparam load           从文件中加载参数
rosparam dump           向文件中转储参数
rosparam delete         删除参数
rosparam list           列出参数名

查看参数名:

rosparam list
/rosdistro
/roslaunch/uris/host_nxt__43407
/rosversion
/run_id
/turtlesim/background_b
/turtlesim/background_g
/turtlesim/background_r

设置参数:
改变背景颜色:

rosparam set /turtlesim/background_r 150
设置完后
rosservice call /clear

保存和加载参数:

rosparam dump [file_name] [namespace]
rosparam load [file_name] [namespace]
保存所有
rosparam dump params.yaml

1.6 使用rqt_console和roslaunch

rqt_console
rqrt_console连接了ROS的日志框架,显示节点的输出信息,可以查看loggers的Debug, Info, Warn, Error, Fatal信息。
日志优先级:

Fatal (致命)
Error (错误)
Warn  (警告)
Info  (信息)
Debug (调试)

roslaunch
roslaunch可以用来在launch文件中自定义需要启动的节点:

roslaunch [package] [filename.launch]

创建launch文件:

roscd beginner_tutorials
mkdir launch
cd launch
gedit turtlemimic.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>

运行:

roslaunch beginner_tutorials turtlemimic.launch

发布话题控制其中一个turtle:

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

运行:

rqt_graph

ROS安装教程和入门笔记_第5张图片
可以看到上面的launch文件启动了三个节点,中间的mimic节点接收turtel1的pose话题,然后发布到cmd_vel话题,被turttle2接收。

1.7 创建ROS消息和服务

msg和srv介绍

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

msg文件就是一个简单的文本文件,每一行都有一个字段类型和字段名称,包括:

int8, int16, int32, int64 (以及 uint*)
float32, float64
string
time, duration
其他msg文件
variable-length array[] 和 fixed-length array[C]
Header :一个特殊的数据类型,包含时间戳和ROS中的坐标帧信息。

一个典型的msg文件(包含一个Header类型,一个string类型,两个另外的消息的类型):

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

一个典型的srv文件(请求和响应用—隔开):

int64 A
int64 B
---
int64 Sum

创建msg

roscd beginner_tutorials
mkdir msg
gedit Num.msg

Num.msg文件中添加数据类型:

string first_name
string last_name
uint8 age
uint32 score

打开package.xml,添加依赖:

  <build_depend>message_generationbuild_depend>
  <exec_depend>message_runtimeexec_depend>

打开CMakeLists.txt,在COMPONENTS列表中添加依赖:

# 不要直接复制这一大段,只需将message_generation加在括号闭合前即可
find_package(catkin REQUIRED COMPONENTS
   roscpp
   rospy
   std_msgs
   message_generation
)
catkin_package(
  ...
  CATKIN_DEPENDS message_runtime ...
  ...)

找到下面这个:

# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )

取消注释,加入:

add_message_files(
  FILES
  Num.msg
)

取消下面的注释:

# generate_messages(
#   DEPENDENCIES
#   std_msgs
# )

rosmsg
使用rosmsg show看看ROS能否识别刚刚创建的消息:

rosmsg show Num

创建srv

roscd beginner_tutorials
mkdir srv

从其他包中copy服务:

roscp [package_name] [file_to_copy_path] [copy_path]
roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv

AddTwoInts.srv文件中的内容:

int64 a
int64 b
---
int64 sum

跟前面创建msg一样,添加相同的依赖。
然后在package.xml找到下面这个模块:

# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

修改为:

add_service_files(
  FILES
  AddTwoInts.srv
)

rossrv
查看ROS能否识别:

rossrv show AddTwoInts

重新编译

roscd beginner_tutorials
cd ../..
catkin_make

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目录中。

1.8 编写简单的发布者和订阅者(C++)

编写发布者节点

roscd beginnger_tutorials
mddir src
cd src
gedit talker.cpp

填入:

#include "ros/ros.h"
// 这里导入了std_msg包中的String.msg消息。编译后在include文件夹生成了String.h文件。前面的教程解释了怎么创建自定义消息
#include "std_msgs/String.h"

#include 

/**
 * 这个教程演示给ROS系统发送消息
 */
int main(int argc, char **argv)
{
  /**
   ros::init()函数需要argc和argv,这样就可以接收从命令行提供的任何ROS参数和名字
   ros::init()的第三个参数是节点的名字
   在使用其他ROS部分时必须先调用ros::init()
   */
  ros::init(argc, argv, "talker");

  /**
   NodeHandle:手柄,构造的第一个手柄将初始化这个节点,销毁的最后一个手柄将关闭这个节点
   */
  ros::NodeHandle n;

  /**
   advertise()函数返回一个Publisher用来发布消息。所有的Publisher对象被销毁后,话题将自动停止广播。
   advertise()的第二个参数是消息队列数目,如果消息发布过快,这个参数指明在发布之前最多能缓存多少消息。
   是消息类型
   "chatter"是发布的话题名字
   */
  ros::Publisher chatter_pub = n.advertise("chatter", 1000);
  
  // 消息发布速率10Hz吧
  ros::Rate loop_rate(10);

  /**
  count计数发布了多少条消息,用来对每一个消息创建一个独立的字符串
   */
  int count = 0;
  while (ros::ok())
  {
    /**
    msg:消息对象,用数据填充,然后再发布它。
     */
    std_msgs::String msg;
	
	// ss:字符串流
    std::stringstream ss;
    ss << "hello world " << count;
    // 把字符串流给msg.data
    msg.data = ss.str();
	
	// 给ROS通知消息
    ROS_INFO("%s", msg.data.c_str());

    /**
     publish()函数用来发布消息,消息类型必须与前面声明时指定的消息类型一致。
     */
    chatter_pub.publish(msg);
	
	// ros::spinOnce()保持程序一直运行?还是一次?
    ros::spinOnce();
	
	// 以10Hz暂停
    loop_rate.sleep();
    ++count;
  }

  return 0;
}

编写订阅者节点
在src目录下创建listerner.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,这样就可以接收从命令行提供的任何ROS参数和名字
   ros::init()的第三个参数是节点的名字
   在使用其他ROS部分时必须先调用ros::init()
   */
  ros::init(argc, argv, "listener");

  /**
   NodeHandle:手柄,构造的第一个手柄将初始化这个节点,销毁的最后一个手柄将关闭这个节点
   */
  ros::NodeHandle n;

  /**
   subscribe()返回一个Subscriber对象
   “chatter”是接收来自chatter话题的消息
   1000指最多缓存1000条消息
   chatterCallback是指在接收到消息时,回调这个函数。
   */
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

  /**
   保持程序一直运行
   */
  ros::spin();

  return 0;
}

编译发布者和订阅者节点
首先在CMakeLists.txt添加:

# 添加可执行代码
add_executable(talker src/talker.cpp)
# 添加talker所需要的依赖
target_link_libraries(talker ${catkin_LIBRARIES})
# 为talker添加"消息生成依赖"
add_dependencies(talker beginner_tutorials_generate_messages_cpp)

# 添加可执行代码
add_executable(listener src/listener.cpp)
# 添加listener所需要的依赖
target_link_libraries(listener ${catkin_LIBRARIES})
# 为listener添加"消息生成依赖"
add_dependencies(listener beginner_tutorials_generate_messages_cpp)

添加依赖这里还是不太懂,参考了这篇文章也不是很清楚。
编译:

cd ~/catkin_ws2
catkin_make

运行:

第一个终端:
roscore
第二个终端
rosrun beginner_tutorials talker
第三个终端
rosrun beginner_tutorials listener

1.9 编写简单的服务和客户端(C++)

编写服务节点
编写一个简单的服务add_two_ints_server,接收两个整数,并返回它们的和。

roscd beginner_tutorials

在前面的教程中我们在srv文件夹创建了AddTwoInts.srv文件。
然后创建src/add_two_ints_server.cpp文件:

#include "ros/ros.h"
// AddTwoInts.h在前面创建服务文件,并编译后产生。
#include "beginner_tutorials/AddTwoInts.h"

// 服务的回调函数:接收srv文件中定义的请求和响应的类型,返回bool值true
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)
{
  // 初始化一个节点add_two_ints_server
  ros::init(argc, argv, "add_two_ints_server");
  ros::NodeHandle n;

  // advertiseService返回一个服务,名称“add_two_ints”,回调函数add
  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 

int main(int argc, char **argv)
{
  // 初始化节点,名为“add_two_ints_client”
  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;
  // 为beginner_tutorials::AddTwoInts服务类型,名为"add_two_ints"的服务创建客户端
  ros::ServiceClient client = n.serviceClient("add_two_ints");
  // 声明一个服务srv
  beginner_tutorials::AddTwoInts srv;
  // srv的请求中的a保存第一个参数。atoll()把string转换为long long int
  srv.request.a = atoll(argv[1]);
  srv.request.b = atoll(argv[2]);
  // client.call(srv)调用服务,成功返回true,失败返回false
  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_ws2
catkin_make

运行:

第一个终端:
roscore
第二个终端:
rosrun beginner_tutorials add_two_ints_server
第三个终端:
rosrun beginner_tutorials add_two_ints_client 1 3

1.10 录制和回放数据

录制数据

roscore
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key

rosbag record -a记录所有话题中的数据:

mkdir ~/bagfiles
cd ~/bagfiles
rosbag record -a

然后通过键盘移动乌龟一段时间,回到rosbag终端ctrl+c,这个时候已经产生了一个以时间命名的记录文件.bag。
检查并回放bag文件
查看文件详情:

rosbag info <your bagfile>

回放数据,首先ctrl+c关闭turtle_teleop_key终端,然后运行:

rosbag play <your bagfile>

乌龟会按照之前的控制方式移动。
只录制一部分话题

rosbag record -O subset /turtle1/cmd_vel /turtle1/pose

上述命令中的-O参数告诉rosbag record将数据记录到名为subset.bag的文件中,而后面的topic参数告诉rosbag record只能订阅这两个指定的话题。然后通过键盘控制乌龟随意移动几秒钟,最后按Ctrl+C退出rosbag record命令。

ROS基础部分复习完毕。

2. TF学习

兴致冲冲地以为可以开始对PR2机器人进行仿真了,但是发现教程要求需要首先了解物理仿真软件Gazebo教程,点开Gazebo教程,发现在Gazebo仿真软件里面创建模型需要通过URDF模型结构进行创建,然后点开URDF教程,又发现建立一个模型涉及很多相对坐标系,所以需要先了解TF包方便坐标变换。感觉自己被套娃了:),无奈先从TF包开始。

学习时参考的:TF教程

2.1 TF示例

教程首先给出了需要创建工作空间:

source /opt/ros/noetic/setup.bash
mkdir -p ~/tutorial_ws/src
cd ~/tutorial_ws
catkin_init_workspace src
catkin_make
source devel/setup.bash

然后给出了一个示例,运行示例roslaunch turtle_tf2 turtle_tf2_demo.launch的时候遇到了process has died问题,看错误提示发现有两个原因,第一个原因是import yaml失败,所以需要pip install pyyaml

另一个原因是python版本问题,需要把下面文件夹的所有py文件第一行的#!/usr/bin/env python替换为#!/usr/bin/env python3
ROS安装教程和入门笔记_第6张图片

2.2 写一个Static Broadcaster(Python)

catking_create_pkg创建一个名为learning_tf2的软件包,需要给出依赖。命令如下:

catkin_create_pkg learning_tf2 tf2 tf2_ros roscpp rospy turtlesim

进入目录:

roscd learning_tf2

创建节点文件夹:

mkdir nodes

在节点文件夹创建py文件:static_turtle_tf2_broadcaster.py,直接复制教程中给出的代码

添加执行权限,但是好像这里py文件路径跟上面创建的py文件路径不一致,所以我手动把py文件放到这行代码对应的位置了,像这样:
ROS安装教程和入门笔记_第7张图片
然后再运行命令添加执行权限:

chmod +x ~/tutorial_ws/src/learning_tf2/src/nodes/static_turtle_tf2_broadcaster.py

回到工作空间文件夹:cd ~/tutorial_ws,教程中直接给出了编译命令,但是下面这个CMakeLIst.txt还没设置好,我记得ROS中文官方教程里面是需要先设置后编译的,所以这里也需要设置一下,像下面这样把刚刚py文件的路径添加进去:
ROS安装教程和入门笔记_第8张图片
ROS安装教程和入门笔记_第9张图片
然后执行命令编译:

catkin_make

之后再根据教程的步骤,完成topic发布和查看。

. devel/setup.bash
roscore
rosrun learning_tf2 static_turtle_tf2_broadcaster.py mystaticturtle 0 0 1 0 0 0
rostopic echo /tf_static

2.3 写一个Broadcaster(python)

在刚才的nodes文件夹添加文件turtle_tf2_broadcaster.py,复制原教程的代码。
添加权限

chmod +x nodes/turtle_tf2_broadcaster.py

创建start_demo.launch文件,在下面这个文件夹,代码复制原教程的:
ROS安装教程和入门笔记_第10张图片
Build刚刚的代码文件,第一步先修改这个CMakeList.txt,166行是新加的,165行是上面2.2节那里加的。还有下面那一段取消注释,然后加上刚刚创建的launch文件:
ROS安装教程和入门笔记_第11张图片
ROS安装教程和入门笔记_第12张图片
ROS安装教程和入门笔记_第13张图片
然后cd到~/tf_tutorial_ws:catkin_make开始编译。

运行:roslaunch learning_tf2 start_demo.launch
另一个终端运行:rosrun tf tf_echo /world /turtle1查看结果

2.4 写一个Listener(Python)

根据原教程,创建文件nodes/turtle_tf2_listener.py
添加执行权限:

chmod +x nodes/turtle_tf2_listener.py

修改之前的start_demo.launch,添加一个node:

  <launch>
    ...
    <node pkg="learning_tf2" type="turtle_tf2_listener.py" 
          name="listener" output="screen"/>
  launch>

重新编译:注意修改CMakeList.txt,标明新的py文件的位置。

运行并查看结果:

roslaunch learning_tf2 start_demo.launch

2.5 添加一个坐标系Frame(Python)

这一节创建了通过Broadcaster创建了一个固定的和时变的坐标系,方法与之前创建Broadcaster一样。

2.6 了解tf2和时间

    while not rospy.is_shutdown():
        try:
            trans = tfBuffer.lookup_transform('turtle2',
                                              'carrot1',
                                              rospy.Time.now(),
                                              rospy.Duration(1.0))

2.7 跨时间的tf2

        try:
            past = rospy.Time.now() - rospy.Duration(5.0)
            trans = tfBuffer.lookup_transform(turtle_name, 'carrot1', past, rospy.Duration(1.0))
        except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException):

这是根据5秒前carrot1相对于5秒前turtle2的位置对现在进行控制,因为从现在的turtle2看起来是混乱的。

        try:
            past = rospy.Time.now() - rospy.Duration(5.0)
            trans = tfBuffer.lookup_transform_full(
                target_frame=turtle_name,
                target_time=rospy.Time.now(),
                source_frame='carrot1',
                source_time=past,
                fixed_frame='world',
                timeout=rospy.Duration(1.0)
            )

这是根据5秒前carrot1相对于现在turtle2的位置对现在进行控制,因此现在的turtle2追踪的是5秒前carrot1的位置。

2.8 四元数基础

注意tf2定义的四元数顺序是x/y/z/axes, 所以不进行旋转的四元数表示为(0,0,0,1)

直接创建四元数消息:

from geometry_msgs.msg import Quaternion

# Create a list of floats, which is compatible with tf
# quaternion methods
quat_tf = [0, 1, 0, 0]

quat_msg = Quaternion(quat_tf[0], quat_tf[1], quat_tf[2], quat_tf[3])

通过欧拉矩阵创建消息:(旋转顺序RPY: Row, Pitch, Yaw)

from tf.transformations import quaternion_from_euler
  
if __name__ == '__main__':

  # RPY to convert: 90deg, 0, -90deg
  q = quaternion_from_euler(1.5707, 0, -1.5707)

  print "The quaternion representation is %s %s %s %s." % (q[0], q[1], q[2], q[3])

坐标旋转直接用四元数乘法就行:

from tf.transformations import *

q_orig = quaternion_from_euler(0, 0, 0)
q_rot = quaternion_from_euler(pi, 0, 0)
q_new = quaternion_multiply(q_rot, q_orig)
print q_new

逆(转置):
感觉官网给出来的不太对:

q[3] = -q[3]

不应该是下面这样吗?:

q[:2] = -q[:2]

求相对旋转:

q1_inv[0] = prev_pose.pose.orientation.x
q1_inv[1] = prev_pose.pose.orientation.y
q1_inv[2] = prev_pose.pose.orientation.z
q1_inv[3] = -prev_pose.pose.orientation.w # Negate for inverse

q2[0] = current_pose.pose.orientation.x
q2[1] = current_pose.pose.orientation.y
q2[2] = current_pose.pose.orientation.z
q2[3] = current_pose.pose.orientation.w

qr = tf.transformations.quaternion_multiply(q2, q1_inv)

2.9 调试 tf2

没找到start_debug_demo.launch文件。。。

查看frame结构的命令:

rosrun tf2_tools view_frames
evince frames.pdf

检查时间戳:

rosrun tf2 tf2_monitor turtle2 turtle1

2.10 tf2使用传感器信息

2.11设置机器人用tf2

C++暂时看不懂

3. URDF模型学习

看完tf2部分后,进入到URDF建立机器人模型阶段,看看学完之后能不能创建一个简单的机器人模型。

3.1 用URDF从Scratch建立一个可视机器人模型

roscd urdf_tutorial
roslaunch urdf_tutorial display.launch model:=urdf/01-myfirst.urdf

注意fixed frame是网格的中心,是由urdf文件中base_link自己定义的。显示的圆柱的中心默认在网格中心。

再来创建一个多形状的物体(程序文件中已经自带了,所以直接显示):

roslaunch urdf_tutorial display.launch model:=urdf/02-multipleshapes.urdf

以及另一个改变joint位置和link位置的urdf文件,主要理解怎么定义文件中joint的位置和link的位置:

roslaunch urdf_tutorial display.launch model:=urdf/03-origins.urdf

在运行这个launch文件的时候会自动根据URDF文件创建TF坐标。

根据教程再分别看看怎么添加表面材料,以及完整的模型:

roslaunch urdf_tutorial display.launch model:=urdf/04-materials.urdf
roslaunch urdf_tutorial display.launch model:=urdf/05-visual.urdf

最后机器人的手指是用的dae文件:

  <link name="left_gripper">
    <visual>
      <origin rpy="0.0 0 0" xyz="0 0 0"/>
      <geometry>
        <mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/>
      geometry>
    visual>
  link>

3.2 创建一个可移动机器人模型

joints类型除了fixed, 还有continuous(连续转动副), revolute(转动副), prismatic(移动副或称棱柱副)类型。其实还有其实还有planar平面移动副和float空间移动副,教程未具体解释。

头与身体的连续转动副示例:

  <joint name="head_swivel" type="continuous">
    <parent link="base_link"/>
    <child link="head"/>
    <axis xyz="0 0 1"/>
    <origin xyz="0 0 0.3"/>
  joint>

注意转动副定义的旋转轴(0,0,1),显然头是绕Z轴旋转的。

夹持器的转动副示例:

  <joint name="left_gripper_joint" type="revolute">
    <axis xyz="0 0 1"/>
    <limit effort="1000.0" lower="0.0" upper="0.548" velocity="0.5"/>
    <origin rpy="0 0 0" xyz="0.2 0.01 0"/>
    <parent link="gripper_pole"/>
    <child link="left_gripper"/>
  </joint>

注意转动副还要定义limit: effort:力气,可以施加的最大力(矩),lower:最小角度, upper:最大角度, velocity:转动速度。

3.3 给URDF模型添加物理和碰撞属性

在一个link里面添加碰撞属性:

    <collision>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      geometry>
    collision>

在一个link里面添加物理属性:
惯性(质量和转动惯量):

    <inertial>
      <mass value="10"/>
      <inertia ixx="0.4" ixy="0.0" ixz="0.0" iyy="0.4" iyz="0.0" izz="0.2"/>
    inertial>

接触系数:
mu: 摩擦系数
kp:刚度系数
kd:阻尼系数

副的动力学:
friction:静摩擦力或摩擦力矩
damping:动摩擦系数

3.4 使用Xacro简化URDF文件

3.4.1 Xacro

Xacro是XML的宏观语言(macro language)。

使用以下形式的命令可以将.xacro文件转化为urdf文件:

xacro --inorder model.xacro > model.urdf

在新版本中–inorder可以省略。

可以在.launch文件写这个命令,运行时自动生成urdf文件,例如:

<param name="robot_description"
  command="xacro --inorder '$(find pr2_description)/robots/pr2.urdf.xacro'" />

一个Xacro文件必须在开头包含下面两行才能正确解释:


<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="your thing's name">

之前的那个模型的base_link里面有些参数重复了两遍,改起来很麻烦:

  <link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      geometry>
      <material name="blue"/>
    visual>
    <collision>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      geometry>
    collision>
  link>

利用xacro属性名,可以这样写:

<xacro:property name="width" value="0.2" />
<xacro:property name="bodylen" value="0.6" />
<link name="base_link">
    <visual>
        <geometry>
            <cylinder radius="${width}" length="${bodylen}"/>
        geometry>
        <material name="blue"/>
    visual>
    <collision>
        <geometry>
            <cylinder radius="${width}" length="${bodylen}"/>
        geometry>
    collision>
link>

甚至属性名可以这样用(代替一部分):

<xacro:property name=”robotname” value=”marvin” />
<link name=”${robotname}s_leg” />

等价于:

<link name=”marvins_leg” />

甚至做数学操作:

<cylinder radius="${wheeldiam/2}" length="0.1"/>
<origin xyz="${reflect*(width+.02)} 0 0.25" />

3.4.2 Macro: xacro中最常用的包

定义一个简单的xacro:macro:

<xacro:macro name="default_origin">
    <origin xyz="0 0 0" rpy="0 0 0"/>
xacro:macro>
<xacro:default_origin />

最后会生成:

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

参数化的macro:

    <xacro:macro name="default_inertial" params="mass">
        <inertial>
                <mass value="${mass}" />
                <inertia ixx="1.0" ixy="0.0" ixz="0.0"
                     iyy="1.0" iyz="0.0"
                     izz="1.0" />
        inertial>
    xacro:macro>
    
<xacro:default_inertial mass="10"/>

最后会生成:

        <inertial>
                <mass value="10" />
                <inertia ixx="1.0" ixy="0.0" ixz="0.0"
                     iyy="1.0" iyz="0.0"
                     izz="1.0" />
        inertial>

3.4.3 实际使用

如果需要查看由xacro文件产生的模型,同样用下面的命令:

roslaunch urdf_tutorial display.launch model:=urdf/08-macroed.urdf.xacro

如果模型中存在对称的模块或者对称的位置,那么运用macro将会方便很多,具体示例查看上面命令中的文件08-macroed.urdf.xacro

一些技巧:
用名字前缀来定义两个名字相似的物体
用数学计算joint的orgin,这样在改变物体尺寸的时候会省去很多麻烦
用镜像参数描述镜像针对的物体,将参数分别设置为-1和1

3.4.3 xacro资源整理
由于CSDN没法收藏其他网址,只能在这里建立一个链接收藏:

  • 一篇xacro基础博客

3.5 在Gazebo中使用URDF

打开教程的launch文件,尽量根据教程理解launch怎么写的:

roslaunch urdf_sim_tutorial gazebo.launch 

为了让机器人可交互,需要指定插件Plugins和广播消息Transmissions.
为了连接Gazebo和ROS,我们在URDF标签中指定了插件:

  <gazebo>
    <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
      <robotNamespace>/robotNamespace>
    plugin>
  gazebo>

可以在urdf/09-publishjoints.urdf.xacro文件中查看

还需要引用控制器:

These are initially loaded into the ROS parameter space. We have a yaml file joints.yaml that specifies our first controller.

type: "joint_state_controller/JointStateController"
publish_rate: 50

09-joints.launch中给出了怎么加载这个yaml文件到r2d2_joint_state_controller,但是现在还没完。。。

还需要广播消息:
对一每一个非固定joints,需要指定一个transmission,例如头的joint, 在URDF文件添加:

  <transmission name="head_swivel_trans">
    <type>transmission_interface/SimpleTransmissiontype>
    <actuator name="$head_swivel_motor">
      <mechanicalReduction>1mechanicalReduction>
    actuator>
    <joint name="head_swivel">
      <hardwareInterface>hardware_interface/PositionJointInterfacehardwareInterface>
    joint>
  transmission>

暂时把上面这个当作模板吧,现在我们运行下面的命令就可以看到joint的状态了:

roslaunch urdf_sim_tutorial 09-joints.launch model:=urdf/10-firsttransmission.urdf.xacro 
rostopic echo /joint_states

接下来考虑怎么控制Joints
所以又需要添加一个控制器配置:

type: "position_controllers/JointPositionController"
joint: head_swivel

This specifies to use the a JointPositionController from the position_controllers package to control the head_swivel transmission. Note that hardware interface in the URDF for this joint matches the controller type 注意URDF文件的接口与控制器类型要对应.

10-head.launch已经添加进去了,现在我们直接运行看看:

roslaunch urdf_sim_tutorial 10-head.launch

现在Gazebo已经订阅了一个新话题,你可以通过发布消息进行控制:

rostopic pub /r2d2_head_controller/command std_msgs/Float64 "data: -0.707"

但是joint会立刻变到该位置,因为没有给joint加限制,所以添加limit:

  <joint name="head_swivel" type="continuous">
    <parent link="base_link"/>
    <child link="head"/>
    <axis xyz="0 0 1"/>
    <origin xyz="0 0 ${bodylen/2}"/>
    <limit effort="30" velocity="1.0"/>
  joint>

运行:

roslaunch urdf_sim_tutorial 10-head.launch model:=urdf/11-limittransmission.urdf.xacro

采用类似的方式对其他joint进行控制,但是像夹持器这种可能需要组合控制,所以需要一个不同的控制器JointGroupPositionController

type: "position_controllers/JointGroupPositionController"
joints:
  - gripper_extension
  - left_gripper_joint
  - right_gripper_joint

运行:

roslaunch urdf_sim_tutorial 12-gripper.launch 

所以发布消息的时候需要发布一个数组:

rostopic pub  /r2d2_gripper_controller/command std_msgs/Float64MultiArray "layout:
  dim:
  - label: ''
    size: 3
    stride: 1
  data_offset: 0
data: [0, 0.5, 0.5]"

为了让机器人可以移动,需要控制机器人的轮子,在机器人的macro文件中给每个轮子添加transmission,注意接口的名字叫VelocityJointInterface

    <transmission name="${prefix}_${suffix}_wheel_trans">
      <type>transmission_interface/SimpleTransmissiontype>
      <actuator name="${prefix}_${suffix}_wheel_motor">
        <mechanicalReduction>1mechanicalReduction>
      actuator>
      <joint name="${prefix}_${suffix}_wheel_joint">
        <hardwareInterface>hardware_interface/VelocityJointInterfacehardwareInterface>
      joint>
    transmission>

因为轮子在地上走,那么需要对轮子的材料和接触系数进行定义:

<gazebo reference="${prefix}_${suffix}_wheel">
      <mu1 value="200.0"/>
      <mu2 value="100.0"/>
      <kp value="10000000.0" />
      <kd value="1.0" />
      <material>Gazebo/Greymaterial>
    gazebo>

然后给每个轮子添加控制器,下面这个yaml文件就有点长了,用的控制器叫DiffDriveController 差速驱动控制器

  type: "diff_drive_controller/DiffDriveController"
  publish_rate: 50

  left_wheel: ['left_front_wheel_joint', 'left_back_wheel_joint']
  right_wheel: ['right_front_wheel_joint', 'right_back_wheel_joint']

  wheel_separation: 0.44

  # Odometry covariances for the encoder output of the robot. These values should
  # be tuned to your robot's sample odometry data, but these values are a good place
  # to start
  pose_covariance_diagonal: [0.001, 0.001, 0.001, 0.001, 0.001, 0.03]
  twist_covariance_diagonal: [0.001, 0.001, 0.001, 0.001, 0.001, 0.03]

  # Top level frame (link) of the robot description
  base_frame_id: base_link

  # Velocity and acceleration limits for the robot
  linear:
    x:
      has_velocity_limits    : true
      max_velocity           : 0.2   # m/s
      has_acceleration_limits: true
      max_acceleration       : 0.6   # m/s^2
  angular:
    z:
      has_velocity_limits    : true
      max_velocity           : 2.0   # rad/s
      has_acceleration_limits: true
      max_acceleration       : 6.0   # rad/s^2

运行:

roslaunch urdf_sim_tutorial 13-diffdrive.launch

launch文件里面添加了一个机器人控制面板可以控制该机器人,cool!

URDF还有一些教程,后面可能需要深入学习, 但现在先开始Gazebo教程。

4 Gazebo学习

打开Gazebo教程,发现有一个提醒,提示现在这个教程已经不适用了:
在这里插入图片描述
但是还是按照过时的教程学了一部分,现在新的Gazebo官网里面有个Library名字好像叫Ignition,但是按照之前的教程一路下来配置的环境好像是安装的经典的Gazebo(Gazebo11)经典Gazebo官网。

首先有必要了解一下Gazebo和ROS是怎么连接的,这是一篇知乎文章的解释:
Gazebo与ROS 版本的集成是通过一组叫做gazebo_ros_pkgs的包完成的,gazebo_ros_pkgs不是一个包,而是一堆包:

  • gazebo_dev:开发Gazebo插件可以用的API
  • gazebo_msgs:定义的ROS2和Gazebo之间的接口(Topic/Service/Action)
  • gazebo_ros:提供方便的 C++ 类和函数,可供其他插件使用,例如转换和测试实用程序。它还提供了一些通常有用的插件。gazeboros::Node
  • gazebo_plugins:一系列 Gazebo 插件,将传感器和其他功能暴露给 ROS2 例如:

原来是通过一系列包进行连接的,好像在Gazebo和ROS联合仿真时能够经常看见这些包,前面已经大概看到过了。现在暂时还是先看看过时的教程,后面会按照经典的Gazebo(Gazebo11)进行学习。

4.1 安装和开始Gazebo

这一部分在按照前面的教程安装ROS的时候已经装好了。

开始运行:

roslaunch gazebo_ros empty_world.launch

empty_world.launch文件解释:

<launch>
  
  <param name="/use_sim_time" value="true" />
  <node name="gazebo" pkg="gazebo" type="gazebo" args="$(find gazebo_worlds)/worlds/empty.world" respawn="false" output="screen"/>
launch>

4.2 创建和引用自定义URDF物体进行仿真

创建一个object.urdf文件:

<robot name="simple_box">
  <link name="my_box">
    <inertial>
      <origin xyz="2 0 0" />
      <mass value="1.0" />
      <inertia  ixx="1.0" ixy="0.0"  ixz="0.0"  iyy="100.0"  iyz="0.0"  izz="1.0" />
    inertial>
    <visual>
      <origin xyz="2 0 1"/>
      <geometry>
        <box size="1 1 2" />
      geometry>
    visual>
    <collision>
      <origin xyz="2 0 1"/>
      <geometry>
        <box size="1 1 2" />
      geometry>
    collision>
  link>
  <gazebo reference="my_box">
    <material>Gazebo/Bluematerial>
  gazebo>
robot>

在bash中引入模型:
放置在z=1位置,并且命名为my_object

rosrun gazebo spawn_model -file `pwd`/object.urdf -urdf -z 1 -model my_object

用launch文件引入模型的例子(好像找不到这个文件,可能需要自己在相应的文件夹创建,主要学习一下代码就行):

roslaunch gazebo_worlds table.launch

table.launch

<launch>
  
  <param name="table_description" command="$(find xacro)/xacro.py $(find gazebo_worlds)/objects/table.urdf.xacro" />

  
  <node name="spawn_table" pkg="gazebo" type="spawn_model" args="-urdf -param table_description -z 0.01 -model table_model" respawn="false" output="screen" />
launch>

这个文件首先把xacro文件加载到ROS参数服务器,然后利用spawn_model从参数服务器得到模型的XML字符转然后传递给Gazebo来在仿真世界中构建这个物体。

4.3 Gazebo ROS API for C-Turtle

一些术语:Gazebo里面的Body就和URDF里面的Link差不多,表示一个刚体。

4.3.1 添加和删除模型
使用gazebo/spawn_modelgazebo/delete_model

添加模型示例:

rosrun gazebo spawn_model -file `rospack find gazebo_worlds`/objects/desk1.model -gazebo -model desk1 -x 0

使用帮助:

$ rosrun gazebo spawn_model
Commands:
    -[urdf|gazebo] - specify incoming xml is urdf or gazebo format
    -[file|param] [|] - source of the model xml
    -model  - name of the model to be spawned.
    -reference_frame  - optinal: name of the model/body where initial pose is defined.
                                     If left empty or specified as "world", gazebo world frame is used.
    -namespace  - optional: all subsequent ROS interface plugins will be inside of this namespace.
    -x  - optional: initial pose, use 0 if left out
    -y  - optional: initial pose, use 0 if left out
    -z  - optional: initial pose, use 0 if left out
    -R  - optional: initial pose, use 0 if left out
    -P  - optional: initial pose, use 0 if left out
    -Y  - optional: initial pose, use 0 if left out

在命令行中也可以通过rosparam导入urdf模型:

rosparam load `rospack find gazebo_worlds`/objects/coffee_cup.model coffee_cup_description
rosrun gazebo spawn_model -param coffee_cup_description -gazebo -model coffee_cup -x 0 -z 2

删除模型示例:

rosservice call gazebo/delete_model '{model_name: coffee_cup}'

4.3.2 通过服务设置和获取模型位置和姿态
运行仿真环境:

rosrun gazebo spawn_model -file `rospack find gazebo_worlds`/objects/000.580.67.model -gazebo -model cup -z 2

但是好像现在已经没有gazebo_worlds这个包了,参考网址,但是只需要学习后面怎么设置和获取模型位置和姿态就行。
在这里插入图片描述
通过/gazebo/set_model_state设置模型位置和姿态:

rosservice call /gazebo/set_model_state '{model_state: { model_name: cup, pose: { position: { x: 0, y: 0 ,z: 1 }, orientation: {x: 0, y: 0.491983115673, z: 0, w: 0.870604813099 } }, twist: { linear: {x: 0.0 , y: 0 ,z: 0 } , angular: { x: 0.0 , y: 0 , z: 0.0 } } , reference_frame: world } }'

通过/gazebo/get_model_state获取模型位置和姿态:

rosservice call gazebo/get_model_state '{model_name: cup}'

4.3.2 获取仿真的世界和模型的属性

rosservice call gazebo/get_world_properties
rosservice call gazebo/get_model_properties '{model_name: desk1}'

4.3.3 使用话题获取模型和Link的状态

rostopic echo -n 1 /gazebo/model_states
rostopic echo -n 1 /gazebo/link_states

模型的状态是指模型中典型Link的状态,一般是root link。

4.3.4 对Link(物体)添加和删除Wrench(扳手?)

rosservice call gazebo/apply_body_wrench '{body_name: "cup::000_580_67_body" , wrench: { torque: { x: 0.01, y: 0 , z: 0 } }, start_time: 10000000000, duration: 1000000000 }'
rosservice call gazebo/clear_body_wrenches '{body_name: "cup::000_580_67_body"}'

4.3.4 对Joints施加和删除Efforts(力气)

rosservice call gazebo/apply_joint_effort '{joint_name: link_joint, effort: 0.01, start_time: 10000000000, duration: 1000000000}'
rosservice call gazebo/clear_joint_forces '{joint_name: link_joint}'

4.3.5 暂停和启用物理引擎

rosservice call gazebo/pause_physics
rosservice call gazebo/unpause_physics

4.3.6 用话题设置模型位置和姿态

rostopic pub -r 10 gazebo/set_model_state gazebo/ModelState '{model_name: desk1, pose: { position: { x: 0, y: 0, z: 2 }, orientation: {x: 0, y: 0.491983115673, z: 0, w: 0.870604813099 } }, twist: { linear: { x: 0, y: 0, z: 0 }, angular: { x: 0, y: 0, z: 0}  }, reference_frame: world }'

4.4 经典Gazebo教程

由于前面提到的教程过时了,前面的一部分或许有一定参考意义,但是最好还是按照经典的Gazebo教程进行学习!

Gazebo涉及的东西就有点多了,目前的想法是后面遇到了相关的内容再进一步了解,所以先进入PR2机器人教程

5 PR2机器人学习

5.1 创建场景

首先安装PR2包:

sudo apt install ros-noetic-pr2-*

在这一步还是遇到了网络问题。。。先后尝试了换成手机热点,尝试了手动下载,下载方式从Debian网站下载,好像没能解决,然后多试了几遍–fix-missing,然后又能连接上了。。。玄学。

 roslaunch gazebo_ros empty_world.launch
roslaunch pr2_gazebo pr2.launch

运行完之后又报ImportError: No module named yaml, ImportError: No module named rospkg模块错误,这两个模块我已经装过了,但是是python3环境的,而PR2项目的代码是python2的,前面已经遇到过这个问题,需要根据错误找到那些py文件,把文件第一行的#!/usr/bin/env python替换为#!/usr/bin/env python3
弄完之后还有一个错误:[ERROR] [1652446439.190980226]: Skipped loading plugin with error: XML Document ‘/opt/ros/noetic/share/prosilica_camera/plugins/nodelet_plugins.xml’ has no Root Element. This likely means the XML is malformed or missing…
打开报错的那个文件夹发现原来这个nodelet_plugins.xml的路径放错了,按照报错的那个位置新建一个plugins文件夹,把这个文件放进去就好了:
ROS安装教程和入门笔记_第14张图片
好,现在来看一下pr2.launch:

<launch>
  
  <include file="$(find pr2_gazebo)/pr2_no_controllers.launch" />
  
  <include file="$(find pr2_controller_configuration_gazebo)/pr2_default_controllers.launch" />
launch>

好像就是加载了一个无控制器的pr2,然后又加载了一个默认控制器的pr2

5.2 ROS和Gazebo交互:

先看看PR2的Joints状态:

rostopic echo joint_states | less

控制机器人:

rosmake pr2_teleop
roslaunch pr2_teleop teleop_keyboard.launch

可以用键盘控制!

Reading from keyboard
---------------------------
Use 'WASD' to translate
Use 'QE' to yaw
Press 'Shift' to run

5.3 PR2 仿真器工厂

主要探索PR2的接口
为了触发系统使用pr2_machine/sim.machine,将ROS_MASTER_URI导出到localhost,并作为sim。(不懂。。。)

export ROS_MASTER_URI=http://localhost:11311
export ROBOT=sim

然后运行环境:

roslaunch pr2_gazebo pr2_empty_world.launch

可以看看此时ROS节点,话题和服务,参数都有啥:试了一下发现每一项都有很多。

可以用rqt_pr2_dashboard可视化机器人的状态:

rosrun rqt_pr2_dashboard rqt_pr2_dashboard

但是报错好像少了plugin.xml插件,这回我没有找到,不知道怎么解决了。。。跳过这个吧

可视化机器人:

rosrun rviz rviz

根据教程的操作可以进行一些可视化操作。

调用倾斜激光轨迹:

rosservice call laser_tilt_controller/set_periodic_cmd '{ command: { header: { stamp: 0 }, profile: "linear" , period: 3 , amplitude: 1 , offset: 0 }}'

然后用rviz看一下话题tilt_scan,全是有一个激光,暂时还不清楚这个干嘛用的:
ROS安装教程和入门笔记_第15张图片

利用URDF和STL文件添加自定义的物体:

一般可以把Gazebo的模型放在models文件夹里面,教程里面创建了一个桌子,因此放到table文件夹下面:
在这里插入图片描述
tabla.stl文件
table.urdf代码(官网的代码过时了,稍微改了一下):

<robot name="table_model">
  <link name="table_body">
    <inertial>
      <mass value="1.0" />
      
      <origin xyz="0.25 -0.5 0" />
      <inertia  ixx="1.0" ixy="0.0"  ixz="0.0"  iyy="1.0"  iyz="0.0"  izz="1.0" />
    inertial>
    <visual>
      
      <origin xyz="0 0 0" rpy="0 0 0" />
      <geometry>
        <mesh filename="/opt/ros/noetic/share/gazebo_plugins/Media/models/table/table.stl" scale="0.01 0.01 0.01"/>
      geometry>
    visual>
    <collision>
      
      <origin xyz="0 0 0" rpy="0 0 0" />
      <geometry>
        <mesh filename="/opt/ros/noetic/share/gazebo_plugins/Media/models/table/table.stl" scale="0.01 0.01 0.01"/>
      geometry>
    collision>
  link>
  <gazebo reference="table_body">
    <material>Gazebo/Redmaterial>
  gazebo>
robot>

运行Gazebo:

roslaunch gazebo_ros empty_world.launch

将URDF文件添加进ROS参数服务,取名为robot_description,之前没做这一步模型就显示不了!

rosparam set -t '/opt/ros/noetic/share/gazebo_plugins/Media/models/table/table.urdf' robot_description

然后生成模型:

rosrun gazebo_ros spawn_model -urdf -model mycar -param robot_description

可以看到桌子添加进去了!但是好像方位有点问题。。。具体模型需要具体调一下位置。
ROS安装教程和入门笔记_第16张图片

从开始安装ROS一直学到这里感觉才了解ROS,TF,URDF,Gazebo,Rviz的概念,但是怎么从零开始实现一个机器人,可能需要认真研读类似PR2这种项目的代码。但是在重复上面的仿真过程就因为代码过时而遇到不少坑,所以找找其他有用的资源。

我是想学习无人机仿真的,还记得最开始安装ROS参考的博客吗? 这位博主基于苏黎世理工大学的rotors包进行无人机从建模到控制的仿真,感觉可以用来加深对ROS的理解,有时间也可以跟着学习一下。

由于不清楚rotors后续能做到什么程度,我现在知道的用得比较多的一个飞行器仿真平台应该是PX4,已经有很多实际产品,相关的资源比较多,并且PX4可以跟ROS结合,因此打算后面主要学习PX4+ROS

入门教程就先写到这里吧,虽然连半只脚都没有踏入,但是后面开了rotorsPX4+ROS两个新坑,在接下来的博客继续记录,以及本文第一章ROS基础知识会在后面复习的时候补充。

你可能感兴趣的:(ROS,自动化)