第二讲:ROS基础
一. 创造工作空间
工作空间是什么?
工作空间(workspace)是存储和开发相关文件的文件夹。
**src:**代码空间(Source Space)
**build:**编译空间(Build Space)
**devel:**开发空间(Development Space)
**install:**安装空间(Install Space)
2.创造工作空间
2.创造工作空间
创造工作空间:
$ mkdir -p ~/catkin_ws/src $ cd ~/catkin_ws/src $ catkin_init_workspace
编译工作空间:
$ cd ~/catkin_ws/ $ catkin_make
设置环境变量:
$ source devel/setup.bash
检查环境变量:
$ echo $ROS_PACKAGE_PATH
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-31wROikx-1595044670409)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200711160005334.png)]
3.创建功能包
$ catkin_create_package <package_name> [depend1] [depend2] [depend3]
创建功能包:
$ cd ~/catkin_ws/src $ catkin_create_package learning_communication std_msgs roscpp rospy
编译功能包:
$ cd ~/catkin_ws/ $ catkin_make $ source ~/catkin_ws/devel/setup.bash
二、ROS通信编程
1、话题编程
主题编程流程:
- 创建发布者
- 创建订阅者
- 添加编译选项
- 操作可执行程序
- 初始化ROS节点
- 向ROS Master注册节点信息,包括主题中发布的主题名称和新闻类型
- 按照一定频率循环发布信息
#include <sstream> #include "ros/ros.h" #include "std_msgs/String.h" int main(int argc,char **argv) { //ROS节点初始化,"talker"定义整个节点运行的名称 ros::init(argc, argv, "talker"); //创建节点句柄,出版商、订阅者和主题由句柄管理 ros::NodeHandle n; //创建一个Publisher叫做chatter_pub,发布名为chatter的topic,消息类型为std_msgs::String,1000是指发布者的队列长度,当数据发布迅速超过队列长度时,会删除最早的时间戳数据,可能会断帧 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter",1000); //设置循环频率,定义特定的循环周期,10hz=100ms为周期,发布消息 ros::Rate loop_rate(10); int count = 0; while(ros::ok()) { //初始化std_msgs::String类型的消息 std_msgs::String msg; std::stringstream ss; //这句话实现了string型的数据 hello world和int型变量 count的拼接,形成新的string。即如果count是1,那么hello world1会作为string被存放在ss当中。 ss<<"hello world"<<count; msg.data = ss.str(); ///发布消息 //c_str():生成一个const char*指针指向空字符终止的数组。 ROS_INFO("%s",msg.data.c_str()); chatter_pub.publish(msg); //循环等待回调函数 ros::spinOnce(); //按循环频率延迟,节点进入休眠状态,100ms后继续工作 loop_rate.sleep(); count; } return 0; }
- 初始化ROS节点
- 订阅所需的话题
- 循环等待话题消息,收到消息后进入回调函数
- 消息处理在回调函数中完成
#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节点 ros::init(argc,argv,"listener"); //创建节点句点 ros::NodeHandle n; //创建一个Subscriber,订阅名为chatter注册回调函数chatterCallback ros::Subscriber sub = n.subscribe("chatter",1000,chatterCallback); //循环等待回调函数 ros::spin(); return 0; }
- 设置需要编译的代码和生成的可执行文件
- 设置链接库
- 设置依赖
add_executable(talker src/talker.cpp) target_link_libraries(talker ${catkin_LIBRARIES}) add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp) add_executable(listener src/listener.cpp) target_link_libraries(listener ${catkin_LIBRARIES}) add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)
$ rosrun learning_communication talker $ rosrun learning_communication listener
<launch> <node pkg="learning_communication" name="talker" type="talker" output="screen"/> <node pkg="learning_communication" name="listener" type="listener" output="screen"/> </launch>
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-EUmn1cRe-1595044670413)(C:\Usersymz\AppData\Roaming\Typora\typora-user-images\image-20200711164601518.png)]
1、定义msg文件
string name
uint8 sex
uint8 age
uint8 unknown = 0
uint8 male = 1
uint8 female = 2
2、在package.xml中添加功能包依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
3、在CMakeLists.txt中添加编译选项
find_package(catkin REQUIRED COMPONENTS message_generation)
catkin_package(CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime)
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)
/**
* 该例程将发布/person_info话题,learning_communication::Person
*/
#include <ros/ros.h>
#include "learning_communication/Person.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "person_publisher");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为/person_info的topic,learning_communication::Person,队列长度10
ros::Publisher person_info_pub = n.advertise<learning_communication::Person>("/person_info", 10);
// 设置循环的频率
ros::Rate loop_rate(1);
int count = 0;
while (ros::ok())
{
// learning_communication::Person类型的消息
learning_communication::Person person_msg;
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = learning_communication::Person::male;
// 发布消息
person_info_pub.publish(person_msg);
ROS_INFO("Publish Person Info: name:%s age:%d sex:%d",
person_msg.name.c_str(), person_msg.age, person_msg.sex);
// 按照循环频率延时
loop_rate.sleep();
}
return 0;
}
/**
* 该例程将订阅/person_info话题,learning_communication::Person
*/
#include <ros/ros.h>
#include "learning_communication/Person.h"
// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_communication::Person::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Subcribe Person Info: name:%s age:%d sex:%d",
msg->name.c_str(), msg->age, msg->sex);
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "person_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
2、服务编程
服务编程流程:
- 创建服务器
- 创建客户端
- 添加编译选项
- 运行可执行程序
1、定义srv文件
上面是request–服务的请求数据,下面是response–服务的应答数据
int64 a
int64 b
---
int64 sum
2、在package.xml中添加功能包依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
3、在CMakeLists.txt中添加编译选项
find_package(catkin REQUIRED COMPONENTS message_generation)
catkin_package(CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime)
add_message_files(FILES AddTwoInts.srv)
- 初始化ROS节点
- 创建Server实例
- 循环等待服务请求,进入回调函数
- 在回调函数中完成服务功能的处理,并反馈应答函数
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
//service回调函数,输入参数req,输出参数res
bool add(learning_communication::AddTwoInts::Request &req,
learning_communication::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节点初始化
ros::init(argc,argv,"add_two_ints_server");
//创建节点句柄
ros::NodeHandle n;
//创建一个名为add_two_ints的server,注册回调函数add()
ros::ServiceServer service = n.advertiseService("add_two_ints",add);
//循环等待回调函数
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
- 初始化ROS节点
- 创建一个Client实例
- 发布服务请求数据
- 等待Server处理之后的应答结果
#include <cstdlib>
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
//argc是命令行总的参数个数,argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数是命令行后面跟的用户输入的参数
int main(int argc ,char ** argv)
{
//ROS节点初始化
ros::init(argc,argv,"add_two_ints_client");
//从终端命令获取两个加数
if(argc != 3)
{
ROS_INFO("usage:add_two_ints_client X Y");
//return 0代表函数正常终止,return 1代表函数非正常终止
return 1;
}
//创建节点句柄
ros::NodeHandle n;
//创建一个client,请求add_two_int service
//service消息类型是learning_communication::AddTwoInts
ros::ServiceClient client = n.serviceClient<learning_communication::AddTwoInts>("add_two_ints");
//创建learning_communication::AddTwoInts类型的service消息
//atoll把字符串转换成长长整型数(64位)
learning_communication::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
//发布service请求,等待加法运算的应答结果
//call是阻塞命令,如果一直没有response就一直卡在这个地方
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;
}
- 设置需要编译的代码和生成的可执行文件
- 设置链接库
- 设置依赖
add_executable(server src/server.cpp)
target_link_libraries(server ${catkin_LIBRARIES})
add_dependencies(server ${PROJECT_NAME}generate_messages_cpp)
add_executable(client src/client.cpp)
target_link_libraries(client ${catkin_LIBRARIES})
add_dependencies(client ${PROJECT_NAME}generate_messages_cpp)
$ rosrun learning_communication server
$ rosrun learning_communication client
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VJmFVYfF-1595044670414)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200711180330019.png)]
3、动作编程
- 一种问答通信机制
- 带有连续反馈
- 可以在任务过程中中止运行
- 基于ROS的消息机制实现
- goal:发布任务目标
- cancel:请求取消任务
- status:通知客户端当前的状态
- feedback:周期反馈任务运行的监控数据
- result:向客户端发送任务的执行结果,只发布一次
1、定义action文件
# 定义目标信息
uint32 dishwasher_id
# Specify which dishwasher we wa
nt to use
---
# 定义结果信息
uint32 total_dishes_cleaned
---
# 定义周期反馈的消息
float32 percent_complete
2、在package.xml中添加功能包依赖
<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>
3、在CMakeLists.txt中添加编译选项
find_package(catkin REQUIRED COMPONENTS actionlib_msgs actionlib)
add_action_files(DIRECTORY action FILES DoDishes.action)
generate_messages(DEPENDENCIES std_msgs actionlib_msgs)
- 初始化ROS节点
- 创建动作服务器实例
- 启动服务器,等待动作请求
- 在回调函数中完成动作服务功能的处理,并反馈进度信息
- 动作完成,发送结束信息
#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include "learning_communication/DoDishesAction.h"
typedef actionlib::SimpleActionServer<learning_communication::DoDishesAction> Server;
// 收到action的goal后调用该回调函数
void execute(const learning_communication::DoDishesGoalConstPtr& goal, Server* as)
{
ros::Rate r(1);
learning_communication::DoDishesFeedback feedback;
ROS_INFO("Dishwasher %d is working.", goal->dishwasher_id);
// 假设洗盘子的进度,并且按照1hz的频率发布进度feedback
for(int i=1; i<=10; i++)
{
feedback.percent_complete = i * 10;
as->publishFeedback(feedback);
r.sleep();
}
// 当action完成后,向客户端返回结果
ROS_INFO("Dishwasher %d finish working.", goal->dishwasher_id);
as->setSucceeded();
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "do_dishes_server");
ros::NodeHandle n;
// 定义一个服务器
Server server(n, "do_dishes", boost::bind(&execute, _1, &server), false);
// 服务器开始运行
server.start();
ros::spin();
return 0;
}
- 初始化ROS节点
- 创建动作客户端实例
- 连接动作服务端
- 发送动作目标
- 根据不同类型的服务端反馈处理回调函数
#include <actionlib/client/simple_action_client.h>
#include "learning_communication/DoDishesAction.h"
typedef actionlib::SimpleActionClient<learning_communication::DoDishesAction> Client;
// 当action完成后会调用该回调函数一次
void doneCb(const actionlib::SimpleClientGoalState& state,
const learning_communication::DoDishesResultConstPtr& result)
{
ROS_INFO("Yay! The dishes are now clean");
ros::shutdown();
}
// 当action激活后会调用该回调函数一次
void activeCb()
{
ROS_INFO("Goal just went active");
}
// 收到feedback后调用该回调函数
void feedbackCb(const learning_communication::DoDishesFeedbackConstPtr& feedback)
{
ROS_INFO(" percent_complete : %f ", feedback->percent_complete);
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "do_dishes_client");
// 定义一个客户端
Client client("do_dishes", true);
// 等待服务器端
ROS_INFO("Waiting for action server to start.");
//client.waitForServer(),客户端等待服务器函数,可以传递一个ros::Duration作为最大等待值,程序进入到这个函数开始挂起等待,服务器就位或者达到等待最大时间退出,前者返回true,后者返回false.
client.waitForServer();
ROS_INFO("Action server started, sending goal.");
// 创建一个action的goal
learning_communication::DoDishesGoal goal;
goal.dishwasher_id = 1;
// 发送action的goal给服务器端,并且设置回调函数
//client.sendGoal(),客户端发送目标函数,本函数一共需要四个参数。第一个必须,另三个可选。
//第一个参数就是本次交互的目标,第二个参数是服务端运行结束时调用的函数,第三个是服务器刚刚收到参数激活时调用的函数,第四个是服务器在进程中不停回传反馈时调用的函数。
client.sendGoal(goal, &doneCb, &activeCb, &feedbackCb);
ros::spin();
return 0;
}
- 设置需要编译的代码和生成的可执行文件
- 设置链接库
- 设置依赖
add_executable(DoDishes_client src/DoDishes_client.cpp)
target_link_libraries( DoDishes_client ${catkin_LIBRARIES})
add_dependencies(DoDishes_client ${${PROJECT_NAME}_EXPORTED_TARGETS})
add_executable(DoDishes_server src/DoDishes_server.cpp)
target_link_libraries( DoDishes_server ${catkin_LIBRARIES})
add_dependencies(DoDishes_server ${${PROJECT_NAME}_EXPORTED_TARGETS})
$ rosrun learning_communication DoDishes_client
$ rosrun learning_communication DoDishes_server
<launch>
<node pkg="learning_communication" name="DoDishes_client" type="DoDishes_client" output="screen"/>
<node pkg="learning_communication" name="DoDishes_server" type="DoDishes_server" output="screen"/>
</launch>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KxmmVWS-1595044670416)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200711204525843.png)]
三、分布式通信
四、ROS中的关键组件
1、Launch文件
launch文件:通过XML文件实现多节点的配置和启动(可自动启动ROS MASTER)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dUGqtFlq-1595044670418)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200711205036313.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UfwfLqU0-1595044670419)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200711210654420.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdBcxKFO-1595044670420)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200711210727471.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RNaYxvsR-1595044670421)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200711210749153.png)]
2、TF坐标变换
$ sudo apt-get install ros-kinetic-turtle-tf
$ roslaunch turtle_tf turtle_tf_demo.launch
$ rosrun turtlesim turtle_teleop_key
$ rosrun tf view_frames
$ rosrun tf tf_echo turtle1 turtle2
$ rosrun rviz rviz -d `rospack find turtle_tf` /rviz/turtle_rviz.rviz
- 定义TF广播器(TransformBroadcaster)
- 创建坐标变换值
- 发布坐标变换(sendTranform)
/**
* 该例程产生tf数据,并计算、发布turtle2的速度指令
*/
#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <turtlesim/Pose.h>
std::string turtle_name;
void poseCallback(const turtlesim::PoseConstPtr& msg)
{
// 创建tf的广播器
static tf::TransformBroadcaster br;
// 初始化tf数据,根据乌龟当前的位姿,设置相对于世界坐标系的坐标变换
tf::Transform transform;
//setOrigin设置平移变换
transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );
tf::Quaternion q;
q.setRPY(0, 0, msg->theta);
//setRotation设置旋转变换
transform.setRotation(q);
// 广播world与海龟坐标系之间的tf数据
//这里发布的TF消息类型是tf::StampedTransform,不仅包含tf::Transform类型的坐标变换、时间戳,而且需要指定坐标变换的源坐标系(parent)和目标坐标系(child)
br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));
}
int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "my_tf_broadcaster");
// 输入参数作为海龟的名字
if (argc != 2)
{
ROS_ERROR("need turtle name as argument");
return -1;
}
turtle_name = argv[1];
// 订阅海龟的位姿话题
ros::NodeHandle node;
ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);
// 循环等待回调函数
ros::spin();
return 0;
};
- 定义TF监听器(TransformListener)
- 查找坐标变换(waitForTranform、lookupTransform)
/**
* 该例程监听tf数据,并计算、发布turtle2的速度指令
*/
#include <ros/ros.h>
#include <tf/transform_listener.h>
#include <geometry_msgs/Twist.h>
#include <turtlesim/Spawn.h>
int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "my_tf_listener");
// 创建节点句柄
ros::NodeHandle node;
// 请求产生turtle2
ros::service::waitForService("/spawn");
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");
turtlesim::Spawn srv;
add_turtle.call(srv);
// 创建发布turtle2速度控制指令的发布者
ros::Publisher turtle_vel = node.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel", 10);
// 创建tf的监听器
tf::TransformListener listener;
//缓存10秒
ros::Rate rate(10.0);
while (node.ok())
{
// 获取turtle1与turtle2坐标系之间的tf数据
tf::StampedTransform transform;
try
{
//给定源坐标系(source_frame)和目标坐标系(target_frame),等待两个坐标系之间指定时间(time)的变换关系,该函数会阻塞程序运行,所以要设置超时时间
listener.waitForTransform("/turtle2", "/turtle1", ros::Time(0), ros::Duration(3.0));
//给定源坐标系(source_frame)和目标坐标系(target_frame),得到两个坐标系之间指定时间(time)的坐标变换(transform),ros::Time(0)表示我们想要的是最新一次的坐标变换
listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform);
}
catch (tf::TransformException &ex)
{
ROS_ERROR("%s",ex.what());
ros::Duration(1.0).sleep();
continue;
}
// 根据turtle1与turtle2坐标系之间的位置关系,发布turtle2的速度控制指令
geometry_msgs::Twist vel_msg;
//两个坐标系向量的角度得到的角速度
vel_msg.angular.z = 4.0 * atan2(transform.getOrigin().y(),
transform.getOrigin().x());
//通过向量的长度得到线速度
vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
pow(transform.getOrigin().y(), 2));
turtle_vel.publish(vel_msg);
rate.sleep();
}
return 0;
};
- 设置需要编译的代码和生成的可执行文件
- 设置链接库
add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp)
target_link_libraries(turtle_tf_broadcaster ${catkin_LIBRARIES})
add_executable(turtle_tf_listener src/turtle_tf_listener.cpp)
target_link_libraries(turtle_tf_listener ${catkin_LIBRARIES})
<launch> <!-- 海龟仿真器 --> <node pkg="turtlesim" type="turtlesim_node" name="sim"/> <!--
键盘控制 --> <node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/> <!-- 两只海龟的tf广播 --> <node pkg="learning_tf" type="turtle_tf_broadcaster" args="/turtle1" name="turtle1_tf_broadcaster" /> <node pkg="learning_tf" type="turtle_tf_broadcaster" args="/turtle2" name="turtle2_tf_broadcaster" /> <!-- 监听tf广播,并且控制turtle2移动 --> <node pkg="learning_tf" type="turtle_tf_listener" name="listener" /> </launch>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-focvNJP8-1595044670422)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712113114228.png)]
3、Qt工具箱
4、Rviz可视化平台
5、Gazebo物理仿真环境
五、第二章作业
1、话题与服务编程
通过代码新生一只海龟,放置在(5,5)点,命名为“turtle2”;通过代码订阅turtle2的实时位置并在终端打印;控制turtle2实现旋转运动
#include <ros/ros.h>
#include <turtlesim/Spawn.h>
#include <geometry_msgs/Twist.h>
#include <turtlesim/Pose.h>
ros::Publisher turtle_vel;
void poseCallback(const turtlesim::PoseConstPtr& msg)
{
ROS_INFO("Turtle2 position:(%f,%f)",msg->x,msg->y);
}
int main(int argc,char** argv)
{
//初始化节点
ros::init(argc,argv,"turtle_control");
ros::NodeHandle node;
//通过服务调用,产生第二只乌龟turtle2
ros::service::waitForService("spawn");
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("spawn");
turtlesim::Spawn srv;
srv.request.x = 5;
srv.request.y = 5;
srv.request.name = "turtle2";
add_turtle.call(srv);
ROS_INFO("The turtle2 has been spawn!");
//订阅乌龟的pose信息
ros::Subscriber sub = node.subscribe("turtle2/pose", 10, &poseCallback);
//定义turtle的速度控制发布器
turtle_vel = node.advertise<geometry_msgs::Twist>("turtle2/cmd_vel",10);
ros::Rate rate(10.0);
while (node.ok())
{
geometry_msgs::Twist vel_msg;
vel_msg.angular.z = 1;
vel_msg.linear.x = 1;
turtle_vel.publish(vel_msg);
ros::spinOnce();
rate.sleep();
}
return 0;
}
第三讲:机器人系统设计
一、机器人的定义和组成
二、机器人系统构建
三、URDF机器人建模
1、什么是URDF
- Unified Robot Description Format,统一机器人描述格式
- ROS中一个非常重要的机器人模型描述格式
- 可以解析URDF文件中使用XML格式描述的机器人模型
- ROS同时也提供URDF文件的C++解析器
- :描述机器人link部分的外观参数
- :描述link的惯性参数
- :描述link的碰撞属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wklVOygn-1595044670423)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712160748142.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6PkoBmcw-1595044670424)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712171555125.png)]
:
- 描述机器人关节的运动学和动力学属性
- 包括关节运动的位置和速度限制
- 根据关节运动形式,可以将其分为六种类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YG6fGouD-1595044670425)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712171615287.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e56qu4op-1595044670426)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712171629742.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Usz1Ytr8-1595044670427)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712171706617.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUT2XIsC-1595044670428)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712171806904.png)]
- 完整机器人模型的最顶层标签
- 和标签都必须包含在标签内
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-88S1wbVn-1595044670429)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712172036587.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FiI1rPyM-1595044670429)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200712172043958.png)]
2、创建一个机器人建模的功能包
$ catkin_create_pkg mbot_description urdf xacro
- urdf:存放机器人模型的URDF或xacro文件
- meshes:放置URDF中引用的模型渲染文件
- launch:保存相关启动文件
- config:保存rviz的配置文件
<launch>
<param name="robot_description" textfile="$(find mbot_description)/urdf/mbot_base.urdf" />
<!-- 设置GUI参数,显示关节控制插件 -->
<param name="use_gui" value="true"/>
<!-- 运行joint_state_publisher节点,发布机器人的关节状态 -->
<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />
<!-- 运行robot_state_publisher节点,发布tf -->
<node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher" />
<!-- 运行rviz可视化界面,设置好的显示的插件,加载配置文件,在config文件夹下 -->
<node name="rviz" pkg="rviz" type="rviz" args="-d $(find mbot_description)/config/mbot_urdf.rviz" required="true" />
</launch>
- joint_state_publisher:发布每个joint(除fixed类型)的状态,而且可以通过UI界面对joint进行控制
- robot_state_publisher:将机器人各个links、joints之间的关系,通过TF的形式,整理成三维姿态信息发布
3、第一步:使用圆柱体创建一个车体模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYFRlT0a-1595044670430)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200713105315086.png)]
<?xml version="1.0" ?>
<robot name="mbot">
<!-- 一般都会把主体部分叫做base_link,这段代码用来描述机器人的底盘link -->
<link name="base_link">
<!-- 先从纯视觉上创建一个感官上的机器人模型,visual来定义底盘的外观属性 -->
<visual>
<!-- 设置起点坐标为界面的中心坐标,rpy是相对于三个坐标轴的旋转,单位是弧度 -->
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<!-- cylinder标签定义圆柱的半径和高 -->
<cylinder length="0.16" radius="0.20"/>
</geometry>
<!-- 使用material标签设置机器人底盘的颜色——黄色,其中的color便是黄色的RGBA值,a是颜色的透明度,0是完全不透明 -->
<material name="yellow">
<color rgba="1 0.4 0 1"/>
</material>
</visual>
</link>
</robot>
4、第二步:使用圆柱体创建左侧车轮
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0jbmK8UF-1595044670431)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200713105327377.png)]
<?xml version="1.0" ?>
<robot name="mbot">
<!-- 在左轮模型设置中,不需要位置偏移,放置在(0,0,0)位置即可,将joint放置到安装位置 -->
<joint name="left_wheel_joint" type="continuous">
<origin xyz="0 0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="left_wheel_link"/>
<!-- axis标签定义该joint的旋转轴是正y轴,轮子在运动时就会围绕y轴旋转 -->
<axis xyz="0 1 0"/>
</joint>
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
</robot>
5、第三步:使用圆柱体创建右侧后轮
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F1TXMAx9-1595044670432)(C:\Users\ymz\AppData\Roaming\Typora\typora-user-images\image-20200713105336464.png)]
<?xml version="1.0" ?>
<robot name="mbot">
<joint name="right_wheel_joint" type="continuous">
<origin xyz="0 -0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="right_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="right_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
</robot>