学习ROS之ROS基础

目录

1.工作空间

1.1什么工作空间:

1.2创建工作空间

1.2.1生成工作空间:

1.2.2编译工作空间:

1.2.3设置环境变量:

1.3功能包

1.3.1创建功能包

1.3.2编译功能包

1.3.3环境变量

1.4工作空间的覆盖

2.ROS通信编程

2.1话题编程

2.1.1话题编程流程:

2.1.2实现一个发布者:

2.1.3实现一个订阅者:

2.1.4编译话题

2.1.5执行话题

2.1.6效果图

2.1.7自定义话题编程

2.2服务编程

2.2.1自定义服务请求与应答

注:添加的顺序问题,防止编译报错

2.2.2服务编程创建流程

2.2.3编译代码

2.2.4执行程序 & 效果图​

2.3动作编程

2.3.1动作编程

2.3.2Action的接口

2.3.3自定义动作编程

2.3.4实现一个动作服务器

2.3.5实现一个动作客户端

2.3.6编译代码

2.3.7运行效果图

3.实现分布式通信

3.1实现分布式的多机通信

3.1.1设置IP地址,确保底层链路的联通

3.1.2设置ROS_MASTER_URI

4.ROS中的关键组件

4.1 Launch文件

参数设置

重映射:更改之后原有命名的就不存在

嵌套

4.2TF坐标变换

4.3Qt工具箱

4.4Rviz可视化平台

4.5Gazebo物理仿真平台


1.工作空间

1.1什么工作空间:

工作空间是一个存放工程开发相关文件的文件夹
src:代码空间(Source Space)
build:编译空间(Build Sapce)
devel:开发空间(Development Space)
install:安装空间(Install Space)

1.2创建工作空间

1.2.1生成工作空间

mkdir -p ~/catkin_ws/src

1.2.2编译工作空间

cd ~/catkin_ws/
catkin_make

1.2.3设置环境变量

这里有两种方法:一种是临时生效,一种是永远生效。
       临时生效的意思是,执行完该命令后,你只能从当前的终端执行相关可执行文件,好处就是方便测试,比如你心血来潮等原因写了一个不重要的功能包,编译后生成了可执行文件,你单纯想试一试这个功能包写的怎么样,并没有长久使用的意思,就可以用临时生效的方法。对于新开启的终端无效。
       永久生效就是,每次开机之后,你可以从任意一个命令行中执行相关可执行文件。对于比较重要的功能包比较合适。或者你比较厌烦每次都得执行临时方法,就可以使用永久生效。

临时方法:在devel中有一个setup.bash的文件,用source命令执行即可。

source ~/catkin_ws/devel/setup.bash

永久方法:把临时方法那句话加到~/.bashrc文件中

echo "source ~/catkin_ws/devel/setup.bash">>~/.bashrc
source ~/.bashrc

1.3功能包

1.3.1创建功能包

cd ~/catkin_ws/src/
catkin_create_pkg package_name std_msgs rospy roscpp
//其中std_msgs rospy roscpp是package_name功能包的各类依赖
//格式为 catkin_create_pkg  [depend1] [depend2] [depend3] 
//同一工作空间下,不允许存在同名功能包
//不同的工作空间下,允许存在同名的功能包

1.3.2编译功能包

cd ~/workspace/
catkin_make

1.3.3环境变量

source ~/.bashrc

1.4工作空间的覆盖

工作空间的覆盖是怎么回事呢?
同一个工作空间下,不允许出现同名的功能包,否则无法编译。不同工作空间下,允许出现同名的功能包,但会出现工作空间覆盖的现象。
ros工作空间的路径记录在ROS_PACKAGE_PATH环境变量中,可以通过env命令查看计算机中的环境变量,再利用管道过滤出有关ros的环境变量。

env | grep ros

新添加的工作空间路径会自动放置在ROS_PACKAGE_PATH环境变量最前端。ros运行时,会按照顺序从前往后查找工作空间中是否存在指定的功能包。一旦找到指定的功能包就不再查找后面的工作空间。因此,如果我们的系统当中存在名称相同的功能包,就会出现工作空间向后覆盖的问题。当我们使用rospack find命令查找功能包的时候,只能查找到最新路径下的同名功能包,而一旦有第三方功能包依赖于原来的同名功能包,就有可能会出现问题。

 

2.ROS通信编程

2.1话题编程

2.1.1话题编程流程

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

2.1.2实现一个发布者

1.初始化ROS节点。
2.向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型。
3.按照一定频率循环发布消息。

/*
//该例程将发布chatter话题,消息类型为String

*/


#include
#include"ros/ros.h"	//包括了使用ROS系统最基本的头文件。
#include"std_msgs/String.h"

int main(int argc, char** argv)
{
	ros::init(argc, argv, "talker");
	
	ros::NodeHandle n;	//为处理的节点创建了一个句柄,第一个创建的节点句柄将会初始化这个节点,最后一个销毁的节点将会释放节点所使用的所有资源。

	//告诉主机,我们将会在一个名字为chatter的话题上发布一个std_msgs/String类型的消息,
	//这就使得主机告诉了所有订阅了chatter话题的节点,我们将在这个话题上发布数据。
	//第二个参数是发布队列的大小,它的作用是缓冲。当我们发布消息很快的时候,它将能缓冲1000条信息。如果慢了的话就会覆盖前面的信息。

	  ros::Publisher chatter_pub = n.advertise("chatter", 1000);  
	//NodeHandle::advertise()将会返回ros::Publisher对象,该对象有两个作用,
	//首先是它包括一个publish()方法可以在制定的话题上发布消息,其次,当超出范围之外的时候就会自动的处理。

	ros::Rate loop_rate(10);
	
	//一个ros::Rate对象允许你制定循环的频率。它将会记录从上次
	//调用Rate::sleep()到现在为止的时间,并且休眠正确的时间。在这个例子中,设置的频率为10hz。

	int count = 0;

	while(ros::ok())
	{
	//默认情况下,roscpp将会安装一个SIGINT监听,它使当Ctrl-C按下时,ros::ok()将会返回false。
	//ros::ok()在以下几种情况下也会返回false:(1)按下Ctrl-C时(2)我们被一个同名同姓的节点从网络中踢出
	//(3)ros::shutdown()被应用程序的另一部分调用(4)所有的ros::NodeHandles都被销毁了。一旦ros::ok()返回false,所有的ROS调用都会失败。	
	

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


		//发布消息
		ROS_INFO("%s", msg.data.c_str());
		chatter_pub.publish(msg);

		ros::spinOnce();
		
		//在这个简单的程序中调用ros::spinOnce();是不必要的,因为我们没有收到任何的回调信息。
		//然而如果你为这个应用程序添加一个订阅者,并且在这里没有调用ros::spinOnce(),你的回调函数将不会被调用。所以这是一个良好的风格。

		loop_rate.sleep();

		//休眠一下,使程序满足前面所设置的10hz的要求。 		
		count++;
	
	}
	return 0;
}


2.1.3实现一个订阅者:

1.初始化ROS节点
2.订阅需要的话题
3.循环等待话题消息,接受到消息后进入回调函数
4.在回调函数中完成消息处理

#include "ros/ros.h"  
    #include "std_msgs/String.h"  
      
    /**  
     * This tutorial demonstrates simple receipt of messages over the ROS system.  
     */  

//当一个消息到达chatter话题时,这个回调函数将会被调用。 	
    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);  

	//订阅chatter话题,当一个新的消息到达时,ROS将会调用chatterCallback()函数。
	//第二个参数是对列的长度,如果我们处理消息的速度不够快,会将收到的消息缓冲下来,一共可以缓冲1000条消息,满1000之后,后面的到达的消息将会覆盖前面的消息。
	//NodeHandle::subscribe()将会返回一个ros::Subscriber类型的对象,当订阅对象被销毁以后,它将会自动从chatter话题上撤销。

      
      /**  
       * 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();  

	//ros::spin()进入了一个循环,可以尽快的调用消息的回调函数。不要担心,如果它没有什么事情可做时,它也不会浪费太多的CPU。当ros::ok()返回false时,ros::spin()将会退出。这就意味着,当ros::shutdown()被调用,或按下CTRL+C等情况,都可以退出。
      
      return 0;  
    }  

2.1.4编译话题

进入到功能包目录下,打开CMakeLists.txt.
在 CMakeLists.txt 文件末尾加入几条语句:

add_executable(talker src/talker.cpp)#设置需要编译的代码和生成的可执行文件
target_link_libraries(talker ${catkin_LIBRARIES})#设置链接库
add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)
#设置依赖,本例程中不加也可以。可以确保自定义消息的头文件在被使用之前已经被生成。
#因为 catkin 把所有的 package 并行的编译,
#所以如果你要使用其他 catkin 工作空间中其他 package 的消息,你同样也需要添加对他们各自生成的消息文件的依赖。
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)

保存,退出。

cd ~/catkin
catkin_make
source ~/.bashrc

2.1.5执行话题

//1.在终端命令行编写该命令,启动master
roscore 

//2.启动发布者节点
rosrun learning_communication talker

//3.启动订阅者节点
rosrun learning_communication listener

2.1.6效果图

学习ROS之ROS基础_第1张图片

2.1.7自定义话题编程

定义msg文件:
在功能包目录下新建msg文件夹。
在msg文件夹下新建Person.msg文件。
在文件Person.msg中输入以下内容并保存。

string name
uint8 sex
uint8 age
uint8 unknown = 0
uint8 male = 1
uint8 female = 2

添加功能包依赖,打开功能包目录下的package.xml:(光标处)

message_generation
message_runtime

学习ROS之ROS基础_第2张图片

在CMakeLists.txt添加编译选项:

打开CMakeLists.txt

//第一步,查找到
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
)
//添加  message_generation进入变成
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
//第二步,查找到
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES learning_communication
#  CATKIN_DEPENDS roscpp rospy std_msgs
#  DEPENDS system_lib
)
//让这段代码变为
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES learning_communication
   CATKIN_DEPENDS roscpp rospy std_msgs message_runtime 
#  DEPENDS system_lib
)
//第三步,复制下面这段代码,放入
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)

回到工作空间目录下:

cd ~/catkin_ws
//编译
catkin_make
//显示
rosmsg show Person

效果图:

学习ROS之ROS基础_第3张图片

2.2服务编程

本次我们将创建一个简单的service节点("add_two_ints_server"),该节点将接收到两个整形数字,并返回它们的和。
再创建一个client节点("add_two_ints_client"),该节点发送两个整形数字,并接收他们的和。

2.2.1自定义服务请求与应答

定义src文件:

学习ROS之ROS基础_第4张图片

在package.xml中添加功能包依赖:(若前面已添加相同的依赖,则无需再添加)

message_generation
message_runtime

在CMakeLists.txt添加编译选项:(若前面已添加相同的依赖,则无需再添加)

• find_package( ...... message_generation)
• catkin_package(CATKIN_DEPENDS geometry_msgs roscpp
rospy std_msgs message_runtime)
• add_service_files(FILES AddTwoInts.srv)

注:添加的顺序问题,防止编译报错

报错:

学习ROS之ROS基础_第5张图片

正确:

学习ROS之ROS基础_第6张图片

2.2.2服务编程创建流程

1.创建服务器
2.创建客户端
3.添加编译选项
4.运行可执行程序

服务中的Server类似于话题中的Subscriber,流程如下:
1.初始化ros节点
2.创建Server实例
3.循环等待服务请求,进入回调函数
4.在回调函数中完成服务功能的处理并反馈应答数据。

/**
 * AddTwoInts 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();    //https://www.cnblogs.com/liu-fa/p/5925381.html

  return 0;
}

服务中的Client类似于话题中的Publisher,流程如下:
1.初始化ROS节点
2.创建一个Client实力
3.发布服务请求数据
4.等待Server处理之后的应答结果

/**
 * AddTwoInts Client
 */
 
#include 
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"

int main(int argc, char **argv)
{
  // ROS节点初始化
  ros::init(argc, argv, "add_two_ints_client");
  
  // 从终端命令行获取两个加数
  //之所以是3,是因为第一个参数是路径名,即该文件的绝对路径
  if (argc != 3)
  {
    ROS_INFO("usage: add_two_ints_client X Y");
    return 1;
  }

  // 创建节点句柄
  ros::NodeHandle n;
  
  // 创建一个client,请求add_two_int service,service消息类型是learning_communication::AddTwoInts
  ros::ServiceClient client = n.serviceClient("add_two_ints");
  
  // 创建learning_communication::AddTwoInts类型的service消息
  learning_communication::AddTwoInts srv;
  
  srv.request.a = atoll(argv[1]);
  srv.request.b = atoll(argv[2]);
  
  // 发布service请求,等待加法运算的应答结果
  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;
}

2.2.3编译代码

1.设置需要编译的代码和生成的可执行文件
2.设置链接库
3.设置依赖

add_executable(server src/server.cpp)
target_link_libraries(server ${catkin_LIBRARIES})
add_dependencies(server ${PROJECT_NAME}_gencpp)
add_executable(client src/client.cpp)
target_link_libraries(client ${catkin_LIBRARIES})
add_dependencies(client ${PROJECT_NAME}_gencpp)

2.2.4执行程序 & 效果图
学习ROS之ROS基础_第7张图片

2.3动作编程

2.3.1动作编程

1.一种问答通信机制
2.带有连续反馈
3.可以在任务过程中止运行
4.基于ROS的消息机制实现

2.3.2Action的接口

1.goal    发布任务目标
2.cancel    请求取消任务
3.status    通知客户端当前的状态
4.feedback    周期反馈任务运行的监控数据
5.result    向客户端发送任务的执行结果,只发布一次

2.3.3自定义动作编程

定义action文件:在/home/coscj/catkin_ws/src/learning_communication/action/DoDishes.action

#目标信息
uint32 dishwasher_id
---
#结果信息
uint32 total_dishes_cleaned
---
#周期反馈信息
float32 percent_complete

修改 ~/catkin_ws/src/learning_communication/package.xml

  actionlib
  actionlib
  actionlib_msgs
  actionlib_msgs

修改 ~/catkin_ws/src/learning_communication/CMakeLists.txt

find_package(catkin REQUIRED   actionlib_msgs  actionlib)
add_action_files( DIRECTORY  action  FILES  DoDishes.action )
generate_messages( DEPENDENCIES  std_msgs  actionlib_msgs )
#注意不要直接粘帖进去,编译会报错

编译:判断前面的修改有没有问题,降低错误率

cd ~/catkin_ws
catkin_make

2.3.4实现一个动作服务器

1.初始化ROS节点
2.创建动作服务器实例
3.启动服务器,等待动作请求
4.在回调函数中完成动作服务功能的处理,并反馈进度信息
5.动作完成,发送结束信息
创建动作服务端 ~/catkin_ws/src/learning_communication/src/DoDishes_server.cpp

#include 
#include 
#include "learning_communication/DoDishesAction.h"

typedef actionlib::SimpleActionServer 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();//休眠1s
    }

    // 当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;
}

2.3.5实现一个动作客户端

1.初始化ROS节点
2.创建动作客户端实例
3.连接动作服务端
4.发送动作目标
5.根据不同类型的服务端反馈处理回调函数
创建动作客户端 ~/catkin_ws/src/learning_communication/src/DoDishes_client.cpp

#include 
#include "learning_communication/DoDishesAction.h"

typedef actionlib::SimpleActionClient 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_INFO("Action server started, sending goal.");

    // 创建一个action的goal
    learning_communication::DoDishesGoal goal;
    goal.dishwasher_id = 1;

    // 发送action的goal给服务器端,并且设置回调函数
    client.sendGoal(goal,  &doneCb, &activeCb, &feedbackCb);

    ros::spin();

    return 0;
}

2.3.6编译代码

在CMakeLists.txt修改:

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

编译工作空间:

cd ~/catkin_ws
catkin_make

2.3.7运行效果图

学习ROS之ROS基础_第8张图片

 

3.实现分布式通信

ROS是一种分布式软件框架,节点之间通过松耦合的方式进行组合,在很多应用场景下,节点可以运行在不同的计算平台上,通过Topic、Service进行通信。但是“一山不容二虎”,ROS只允许存在一个Master,在多机系统中Master只能运行在一个机器上,其他机器需要通过 ssh 的方式和Master取得联系。所以在多级ROS系统中需要一些配置。
我们以两台计算机为例,介绍分布式多机通信的配置步骤,其中计算机hcx-pc作为主机运行Master,计算机raspi2
作为从机运行节点。

3.1实现分布式的多机通信

3.1.1设置IP地址,确保底层链路的联通

首先需要确定ROS多机系统中的所有计算机处于同一网络,然后分别在计算机hcx-pc、raspi2上使用ifconfig命令查看计算机的局域网IP地址。
分别在两台计算机系统的/etc/hosts文件中加入对方的IP地址和对应的计算机名:

# @hcx-pc,/etc/hosts
192.168.31.14      raspi2

# @raspi2,/etc/hosts
192.168.31.198      hcx-pc

设置完毕后,分别在两台计算机上使用ping命令测试网络是否连通。

# @hcx-pc
ping  raspi2

# @raspi2
ping  hcx-pc

如果双向网络都畅通,就说明底层网络的通信已经没有问题,接下来设置ROS相关的环境变量。

3.1.2设置ROS_MASTER_URI

因为系统中只能存在一个Master,所以从机raspi2需要知道Master的位置。ROS Master的位置可以使用环境变量ROS_MASTER_URI进行定义,在从机raspi2上使用如下命令设置ROS_MASTER_URI:

export ROS_MASTER_URI=http://hcx-pc:11311

但是以上设置只能在输入的终端中生效,为了让所有打开的终端都能识别,最好使用如下命令将环境变量的设置加入终端额配置文件中。

echo ''export ROS_MASTER_URI=http://hcx-pc:11311'' >> ~/.bashrc

学习ROS之ROS基础_第9张图片

 

4.ROS中的关键组件

4.1 Launch文件

学习ROS之ROS基础_第10张图片

参数设置

学习ROS之ROS基础_第11张图片

重映射:更改之后原有命名的就不存在

学习ROS之ROS基础_第12张图片

嵌套

学习ROS之ROS基础_第13张图片

4.2TF坐标变换

4.3Qt工具箱

4.4Rviz可视化平台

4.5Gazebo物理仿真平台

注:以上很多零碎知识是参考了很多博客,只是把他们按照课程的顺序归纳一起,方便复习,也记录坑。

你可能感兴趣的:(学习ROS)