工作空间是一个存放工程开发相关文件的文件夹。所有文件放到一个文件系统下,ROS开发所有工程,称为工作空间。

一、创建工作空间

workspace工作空间:

文件夹:

src:    代码空间(所有工程包源码)

build: 编译空间(编译产生的中间文件)

devel:开发空间(较常用,编译完成后所生成的可执行文件)

install:安装空间(与devel作用基本相同在ros2中合成了一个文件夹)

工作空间结构:

创建工作空间:

编译工作空间:

编译后无措,基于c_make开发的封装ros用。不会编译源码,因为什么都没有添加,可以在哥哥文件夹下查看。

设置环境变量:

很重要,之后找不到安装包可能就是环境白能量没有安装好,找不到节点等问题。终端有zsh和bash,根据自己用的设置后缀。在终端设置后,环境变量只在本终端有效,换个终端之后就无效了,建议在home根目录下改。命令放到根目录下,

vi ~/.bashrc

在最后一行中添加:

source ~/catkin_ws/devel/setup.bash

按shift+wq!(小写与大写指令不同)  保存退出。

然后:

source ~/.bashrc

使设置生效。在打开一个终端查看环境变量即可发现环境变量已经存在。

以下为只有当前终端有环境变量。

检查环境变量:

到此工作空间就建立好了。

创建功能包

指令:

catkin_create_pkg <pakage_name> [depend1] [depend2] [depend3]

创建功能包:

cd ~/catkin_ws/src
catkin_create_pkg learning_communication std_msgs rospy roscpp

出现一系列创建就成功了。rospy :python依赖。 roscpp: c++依赖。

在catkin_ws/src下就已经成功创建了功能包,打开后会有一些文件和文件夹。

src会放置功能包代码,include放置功能包头文件。

package.xlm为功能包的一些描述,具体信息,其中有功能包的一些依赖。功能包依赖。

编译功能包:

cd ~/catkin_ws
catkin_make
source ~/catkin_ws/devel/setup.bash

编译后会发现新建的功能包。

在src目录下:同一个工作空间下,不允许存在同名功能包;不同工作空间下允许存在同名功能包。

当同一个系统中存在多个同名功能包时ROS的会如何运行,一什么样的顺序去运行,会优先选择那个工作空间中的功能包。

ROS工作空间的Overlaying机制,即工作空间的覆盖。

env:查看系统的环境变量。

env | grep ros  :过滤出与ros相关的环境变量。

在锁有环境变量中,其中ROS_PACKAGE_PATH=[路径1][路径2]

ros搜寻功能包的路径会由1向2,依次像后找,如果都找不到系统会报错。

设置的新路径会优先放置到最前端。

测试:

安装一个安装包,由于我用的是ROS Melodic,所以安装ros-melodic-roscpp-tutorials

sudo apt-get install ros-melodic-roscpp-tutorials

如果没安装过就显示安装了。

打开终端使用指令:

rospack find roscpp_tutorials

就会找到安装包路径。

在自己的工作空间新建一个同名的软件包roscpp_tutorials

source ~/.bashrc

使环境变量生效(如果是复制的,需要这样设置)

然后在打开查找安装包路径,就会变为自己所用工作空间的安装包。

二、ROS通信编程

话题编程、服务编程、动作编程

话题通讯模型:

话题编程流程:

一、创建发布者。

小知识:

创建文本文件,在ubuntu18中可能右键没有文本,因此需要我们手动添加。打开模板文档在其中打开终端输入:

sudo gedit txt

会出现一个文本,点保存即可。

如何定义话题消息--话题编程

(1)定义msg文件

(2)在package.xml添加功能包依赖

  1 <build_depend>message_generation</build_depend>
  2 
  3 <exec_depend>message_runtime</exec_depend> 

(3)在CMakeList.txt添加编译选项

  1 find_package(……message_generation)
  2 
  3 catkin_package(CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime)
  4 
  5 add_message_files(FILES Person.msg)
  6 
  7 generate_mesages(DEPENDENCIES std_msgs)

学习代码讲解实例:

  1 /**
  2  * 该例程将发布chatter话题,消息类型String
  3  */
  4 
  5 #include <sstream>
  6 #include "ros/ros.h"
  7 #include "std_msgs/String.h"
  8 
  9 int main(int argc, char **argv)
 10 {
 11   // ROS节点初始化       节点名称(运行名称)
 12   ros::init(argc, argv, "talker");
 13 
 14   // 创建节点句柄
 15   ros::NodeHandle n;
 16 
 17   // 创建一个Publisher发布者,发布名为chatter(话题名)的topic,消息类型为std_msgs::String
 18   ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
 19 
 20   // 设置循环的频率   多久循环一次
 21   ros::Rate loop_rate(10);
 22 
 23   int count = 0;
 24   while (ros::ok())
 25   {
 26 	// 初始化std_msgs::String类型的消息
 27     std_msgs::String msg;
 28     std::stringstream ss;
 29     ss << "hello world " << count;
 30     msg.data = ss.str();
 31 
 32 	// 发布消息
 33     ROS_INFO("%s", msg.data.c_str());
 34     chatter_pub.publish(msg);
 35 
 36 	// 循环等待回调函数              在这里实际上没有意义后边还会讲
 37     ros::spinOnce();
 38 
 39 	// 按照循环频率延时         不写sleep系统会一直循环,cpu占用会很高
 40     loop_rate.sleep();
 41     ++count;
 42   }
 43 
 44   return 0;
 45 }

二、创建订阅者。(简单一些)

源码讲解:

  1 /**
  2  * 该例程将订阅chatter话题,消息类型String
  3  */
  4 
  5 #include "ros/ros.h"
  6 #include "std_msgs/String.h"
  7 
  8 // 接收到订阅的消息后,会进入消息回调函数
  9 void chatterCallback(const std_msgs::String::ConstPtr& msg)
 10 {
 11   // 将接收到的消息打印出来
 12   ROS_INFO("I heard: [%s]", msg->data.c_str());
 13 }
 14 
 15 int main(int argc, char **argv)
 16 {
 17   // 初始化ROS节点
 18   ros::init(argc, argv, "listener");
 19 
 20   // 创建节点句柄
 21   ros::NodeHandle n;
 22 
 23   // 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback   需要订阅的话题名为chatter,进入回调函数
 24   ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
 25 
 26   // 循环等待回调函数   一直等待消息发布,接收到后会进入回调函数
 27   ros::spin();
 28 
 29   return 0;
 30 }

(1)添加编译选项。(python就不用设置了)

在CmakeList.text中添加编译文件

add_dependencies是添加自己的第三方依赖。

  1 add_executable(talker src/talker.cpp)
  2 target_link_libraries(talker ${catkin_LIBRARIES})
  3 #add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)
  4 
  5 add_executable(listener src/listener.cpp)
  6 target_link_libraries(listener ${catkin_LIBRARIES})
  7 #add_dependencies(listener ${PROJECT_NAME}_generate_messages_cpp)

编译完成之后会产生可执行文件,在工作空间catkin_ws/devel/lib下会产生自己的功能包的可执行文件。

(2)运行可执行程序。

—》运行:(在三个终端)

  1 roscore
  2 rosrun learning_communication talker
  3 rosrun learning_communication listener

话题编程

(自定义话题消息)

(1)定义msg文件;(创建)

(2)在package.xml中添加功能依赖(最后)indigo 或更老的版本exec改为 run

  1   <build_depend>message_generation</build_depend>
  2   <exec_depend>message_runtime</exec_depend>

(3)在CMakeList.txt中添加编译选项

可以使用以下代码来查看自己的消息结构是否定义成功

1 rosmsg show Person

服务编程

(1)定义srv文件

(2)在package.xml中添加功能包依赖

(3)在CMakeList.txt中添加编译选项跟之前的一样

设置编译:

如何实现一个服务器:

服务端代码示例:

  1 /**
  2  * AddTwoInts Server
  3  */
  4 
  5 #include "ros/ros.h"
  6 #include "learning_communication/AddTwoInts.h"
  7 
  8 // service回调函数,输入参数req,输出参数res    三个---以上的是request  以下的是response
  9 bool add(learning_communication::AddTwoInts::Request  &req,
 10          learning_communication::AddTwoInts::Response &res)
 11 {
 12   // 将输入参数中的请求数据相加,结果放到应答变量中
 13   res.sum = req.a + req.b;
 14   ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
 15   ROS_INFO("sending back response: [%ld]", (long int)res.sum);
 16 
 17   return true;
 18 }
 19 
 20 int main(int argc, char **argv)
 21 {
 22   // ROS节点初始化
 23   ros::init(argc, argv, "add_two_ints_server");
 24 
 25   // 创建节点句柄
 26   ros::NodeHandle n;
 27 
 28   // 创建一个名为add_two_ints的server,注册回调函数add()  服务名  add为回调函数
 29   ros::ServiceServer service = n.advertiseService("add_two_ints", add);
 30 
 31   // 循环等待回调函数
 32   ROS_INFO("Ready to add two ints.");
 33   ros::spin();
 34 
 35   return 0;
 36 }
  1 /**
  2  * AddTwoInts Client
  3  */
  4 
  5 #include <cstdlib>
  6 #include "ros/ros.h"
  7 #include "learning_communication/AddTwoInts.h"
  8 
  9 int main(int argc, char **argv)
 10 {
 11   // ROS节点初始化
 12   ros::init(argc, argv, "add_two_ints_client");
 13 
 14   // 从终端命令行获取两个加数
 15   if (argc != 3)
 16   {
 17     ROS_INFO("usage: add_two_ints_client X Y");
 18     return 1;
 19   }
 20 
 21   // 创建节点句柄
 22   ros::NodeHandle n;
 23 
 24   // 创建一个client,请求add_two_int service,service消息类型是learning_communication::AddTwoInts
 25   ros::ServiceClient client = n.serviceClient<learning_communication::AddTwoInts>("add_two_ints");
 26 
 27   // 创建learning_communication::AddTwoInts类型的service消息
 28   learning_communication::AddTwoInts srv;
 29   srv.request.a = atoll(argv[1]);
 30   srv.request.b = atoll(argv[2]);
 31 
 32   // 发布service请求,等待加法运算的应答结果          调用call如果服务端没有回应,会卡在这儿,发生阻塞
 33   if (client.call(srv))
 34   {
 35     ROS_INFO("Sum: %ld", (long int)srv.response.sum);
 36   }
 37   else
 38   {
 39     ROS_ERROR("Failed to call service add_two_ints");
 40     return 1;
 41   }
 42 
 43   return 0;
 44 }

 

动作编程

通讯机制

(1)定义action文件

(2)在package.xml中添加功能包依赖

(3)在CMakeList.txt中添加编译选项跟之前的一样

加编译代码:

程序示例:

  1 #include <ros/ros.h>
  2 #include <actionlib/server/simple_action_server.h>
  3 #include "learning_communication/DoDishesAction.h"
  4 
  5 typedef actionlib::SimpleActionServer<learning_communication::DoDishesAction> Server;
  6 
  7 // 收到action的goal后调用该回调函数
  8 void execute(const learning_communication::DoDishesGoalConstPtr& goal, Server* as)
  9 {
 10     ros::Rate r(1);
 11     learning_communication::DoDishesFeedback feedback;
 12 
 13     ROS_INFO("Dishwasher %d is working.", goal->dishwasher_id);
 14 
 15     // 假设洗盘子的进度,并且按照1hz的频率发布进度feedback
 16     for(int i=1; i<=10; i++)
 17     {
 18         feedback.percent_complete = i * 10;
 19         as->publishFeedback(feedback);
 20         r.sleep();
 21     }
 22 
 23     // 当action完成后,向客户端返回结果
 24     ROS_INFO("Dishwasher %d finish working.", goal->dishwasher_id);
 25     as->setSucceeded();
 26 }
 27 
 28 int main(int argc, char** argv)
 29 {
 30     ros::init(argc, argv, "do_dishes_server");
 31     ros::NodeHandle n;
 32 
 33     // 定义一个服务器
 34     Server server(n, "do_dishes", boost::bind(&execute, _1, &server), false);
 35 
 36     // 服务器开始运行
 37     server.start();
 38 
 39     ros::spin();
 40 
 41     return 0;
 42 }
  1 #include <actionlib/client/simple_action_client.h>
  2 #include "learning_communication/DoDishesAction.h"
  3 
  4 typedef actionlib::SimpleActionClient<learning_communication::DoDishesAction> Client;
  5 
  6 // 当action完成后会调用该回调函数一次
  7 void doneCb(const actionlib::SimpleClientGoalState& state,
  8         const learning_communication::DoDishesResultConstPtr& result)
  9 {
 10     ROS_INFO("Yay! The dishes are now clean");
 11     ros::shutdown();
 12 }
 13 
 14 // 当action激活后会调用该回调函数一次
 15 void activeCb()
 16 {
 17     ROS_INFO("Goal just went active");
 18 }
 19 
 20 // 收到feedback后调用该回调函数
 21 void feedbackCb(const learning_communication::DoDishesFeedbackConstPtr& feedback)
 22 {
 23     ROS_INFO(" percent_complete : %f ", feedback->percent_complete);
 24 }
 25 
 26 int main(int argc, char** argv)
 27 {
 28     ros::init(argc, argv, "do_dishes_client");
 29 
 30     // 定义一个客户端
 31     Client client("do_dishes", true);
 32 
 33     // 等待服务器端
 34     ROS_INFO("Waiting for action server to start.");
 35     client.waitForServer();
 36     ROS_INFO("Action server started, sending goal.");
 37 
 38     // 创建一个action的goal
 39     learning_communication::DoDishesGoal goal;
 40     goal.dishwasher_id = 1;
 41 
 42     // 发送action的goal给服务器端,并且设置回调函数
 43     client.sendGoal(goal,  &doneCb, &activeCb, &feedbackCb);
 44 
 45     ros::spin();
 46 
 47     return 0;
 48 }

三、实现分布式通信

分布式松耦合

(1)首先确定对方ip地址指令

ifconfig   #查看

(2)设置相互通信的IP地址

在主机终端输入:

sudo vi /etc/hosts

添加从机IP地址:

192.168.43.110     alex

在从机添加主机IP:

192.168.43.120    alex-1

设置完成后分别在主机与从机检查是否通信成功:

ping alex           #在主机ping从机
ping alex-1   #在从机ping主机

如果都能返回数据说明通信成功。

(3)设置ROS_MASTER_URI   (确定哪个是主机,让从机能找到master)11311是rosmaster的一个端口号,必须是这个。

vi ~/.bashrc
export ROS_MASTER_URI=http://alex:11311       #设置环境变量

注意:有些设置好之后可以ping成功但是还是不能用是因为你设置的不是计算机名字,可以在设置中设备详细信息查看但钱计算机的名称。

4、ROS中的关键组件

  (1)launch文件

     实现多个节点的启动。能自动启动roscore。

output:节点日志信息是否打印

respawn:节点失效失败会重新启动

required:如果这个节点是一个必须的节点,没有的话launch文件启动不冷

ns:命名空间

args:输入参数

  1  <launch>
  2     <!-- 海龟仿真器 -->
  3     <node pkg="turtlesim" type="turtlesim_node" name="sim"/>
  4 
  5     <!-- 键盘控制 -->
  6     <node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/>
  7 
  8     <!-- 两只海龟的tf广播 -->
  9     <node pkg="learning_tf" type="turtle_tf_broadcaster"
 10           args="/turtle1" name="turtle1_tf_broadcaster" />
 11     <node pkg="learning_tf" type="turtle_tf_broadcaster"
 12           args="/turtle2" name="turtle2_tf_broadcaster" />
 13 
 14     <!-- 监听tf广播,并且控制turtle2移动 -->
 15     <node pkg="learning_tf" type="turtle_tf_listener"
 16           name="listener" />
 17 
 18  </launch>

(2)TF坐标变换

示例代码:

  1 #include <ros/ros.h>
  2 #include <tf/transform_broadcaster.h>
  3 #include <turtlesim/Pose.h>
  4 
  5 std::string turtle_name;
  6 
  7 void poseCallback(const turtlesim::PoseConstPtr& msg)
  8 {
  9     // tf广播器
 10     static tf::TransformBroadcaster br;
 11 
 12     // 根据乌龟当前的位姿,设置相对于世界坐标系的坐标变换
 13     tf::Transform transform;
 14     transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );
 15     tf::Quaternion q;
 16     q.setRPY(0, 0, msg->theta);
 17     transform.setRotation(q);
 18 
 19     // 发布坐标变换
 20     br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));
 21 }
 22 
 23 int main(int argc, char** argv)
 24 {
 25     // 初始化节点
 26     ros::init(argc, argv, "my_tf_broadcaster");
 27     if (argc != 2)
 28     {
 29         ROS_ERROR("need turtle name as argument");
 30         return -1;
 31     };
 32     turtle_name = argv[1];
 33 
 34     // 订阅乌龟的pose信息
 35     ros::NodeHandle node;
 36     ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);
 37 
 38     ros::spin();
 39 
 40     return 0;
 41 };
  1 #include <ros/ros.h>
  2 #include <tf/transform_listener.h>
  3 #include <geometry_msgs/Twist.h>
  4 #include <turtlesim/Spawn.h>
  5 
  6 int main(int argc, char** argv)
  7 {
  8     // 初始化节点
  9     ros::init(argc, argv, "my_tf_listener");
 10 
 11     ros::NodeHandle node;
 12 
 13     // 通过服务调用,产生第二只乌龟turtle2
 14     ros::service::waitForService("spawn");
 15     ros::ServiceClient add_turtle =
 16     node.serviceClient<turtlesim::Spawn>("spawn");
 17     turtlesim::Spawn srv;
 18     add_turtle.call(srv);
 19 
 20     // 定义turtle2的速度控制发布器
 21     ros::Publisher turtle_vel =
 22     node.advertise<geometry_msgs::Twist>("turtle2/cmd_vel", 10);
 23 
 24     // tf监听器
 25     tf::TransformListener listener;
 26 
 27     ros::Rate rate(10.0);
 28     while (node.ok())
 29     {
 30         tf::StampedTransform transform;
 31         try
 32         {
 33             // 查找turtle2与turtle1的坐标变换
 34             listener.waitForTransform("/turtle2", "/turtle1", ros::Time(0), ros::Duration(3.0));
 35             listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform);
 36         }
 37         catch (tf::TransformException &ex)
 38         {
 39             ROS_ERROR("%s",ex.what());
 40             ros::Duration(1.0).sleep();
 41             continue;
 42         }
 43 
 44         // 根据turtle1和turtle2之间的坐标变换,计算turtle2需要运动的线速度和角速度
 45         // 并发布速度控制指令,使turtle2向turtle1移动
 46         geometry_msgs::Twist vel_msg;
 47         vel_msg.angular.z = 4.0 * atan2(transform.getOrigin().y(),
 48                                         transform.getOrigin().x());
 49         vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
 50                                       pow(transform.getOrigin().y(), 2));
 51         turtle_vel.publish(vel_msg);
 52 
 53         rate.sleep();
 54     }
 55     return 0;
 56 };

(3)Qt工具箱

日志信息可视化工具

rqt_console   :节点发出的警告错误鞥节点信息

rqt_graph  :  显示节点图

rqt_plot  :  画出图,变量的图,数据绘图

rqt_reconfigure : 参数动态配置工具,数据会保存在master,其他节点会查。在这里边改了参数,整个节点都会更新。

(4)Rviz可视化平台

  基于rqt开发的,用来显示数据的平台。可视化显示。三维可视化工具。

1 rosrun rviz rviz

在add中添加自己想要监控的数据。

(5)Gazeb物理仿真环境

    三位物理仿真平台,开源且免费。带有物理特性。

    启动空世界gazebo环境。直接gazebo也可以打开

1 roslaunch gazebo_ros empty_word.launch

扩展资料:

 

__EOF__