机器人ROS系统学习随笔->1《ROS基础》
工作空间是一个存放工程开发相关文件的文件夹。所有文件放到一个文件系统下,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> 4
(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) 8
学习代码讲解实例:
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 } 37
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 } 45
通讯机制
(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 } 43
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 } 49
三、实现分布式通信
分布式松耦合
(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 }; 42
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 }; 57
(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
扩展资料: