第二讲:ROS基础

第二讲:ROS基础

一. 创建工作空间

1、什么是工作空间

​ 工作空间(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、话题编程

​ 话题编程流程:

  1. 创建发布者
  2. 创建订阅者
  3. 添加编译选项
  4. 运行可执行程序

如何实现一个发布者:

  • 初始化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文件运行:

<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:\Users\ymz\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_publisher.cpp

/**
 * 该例程将发布/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_subscriber.cpp

/**
 * 该例程将订阅/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. 创建服务器
  2. 创建客户端
  3. 添加编译选项
  4. 运行可执行程序

如何自定义服务请求与应答:

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的消息机制实现

Action的接口:

  • 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文件运行:

<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广播器:

  • 定义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监听器:

  • 定义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工具箱

  • 日志输出工具——rqt_console
  • 计算图可视化工具——rqt_graph
  • 数据绘图工具——rqt_plot
  • 参数动态配置工具——rqt_reconfigure

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的配置文件

display_mbot_base_urdf.launch:

<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>

    
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值