ROS操作系统入门(https://www.bilibili.com/video/av24585414/?p=5)
一、系统工程目录图
catkin是ros定制的编译构建系统,对cmake的扩展
所有代码都存在catkin workspace
catkin_make初始化工作空间 (编译)
在终端中
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/ #回到工作空间
catkin_make
source ~/catkin_ws/devel/setup.bash # 编译完成后要source刷新环境
1.2、在这三个文件中只有src是写代码的地方
package是编译的基本单元
catkin编译的对象就是在src目录下一个又一个递归的package包
2、package
ros软件的基本组织形式;catkin编译的基本单元;一个package可以包含个可执行文件(节点)
├── CMakeLists.txt #package的编译规则(必须)
├── package.xml #package的描述信息(必须)
├── src/ #源代码文件
├── include/ #C++头文件
├── scripts/ #可执行脚本
├── msg/ #自定义消息
├── srv/ #自定义服务
├── models/ #3D模型文件
├── urdf/ #urdf文件
├── launch/ #launch文件
CMakeLists.txt: 定义package的包名、依赖、源文件、目标文件等编译规则,是package
不可少的成分
package.xml: 描述package的包名、版本号、作者、依赖等信息,是package不可少的成
分
src/: 存放ROS的源代码,包括C++的源码和(.cpp)以及Python的module(.py)
include/: 存放C++源码对应的头文件
scripts/: 存放可执行脚本,例如shell脚本(.sh)、Python脚本(.py)
msg/: 存放自定义格式的消息(.msg)
srv/: 存放自定义格式的服务(.srv)
models/: 存放机器人或仿真场景的3D模型(.sda, .stl, .dae等)
urdf/: 存放机器人的模型描述(.urdf或.xacro)
launch/: 存放launch文件(.launch或.xml)
一个package必须要有这两个文件CMakeLists.txt和 package.xml
2.2、CMakeLists.txt和 package.xml
2.2.1CMakeLists.txt
规定catkin编译的规则;
例如:源文件、依赖项、目标文件
具体的写法:
cmake_minimum_required() #CMake的版本号
project() #项目名称
find_package() #找到编译需要的其他CMake/Catkin package
catkin_python_setup() #catkin新加宏,打开catkin的Python Module的支持
add_message_files() #catkin新加宏,添加自定义Message/Service/Action文件
add_service_files()
add_action_files()
generate_message() #catkin新加宏,生成不同语言版本的msg/srv/action接口
catkin_package() #catkin新加宏,生成当前package的cmake配置,供依赖本包的其他软件包调用
add_library() #生成库
add_executable() #生成可执行二进制文件
add_dependencies()#定义目标文件依赖于其他目标文件,确保其他目标已被构建
target_link_libraries() #链接
catkin_add_gtest() #catkin新加宏,生成测试
install() #安装至本机
//
一般编译出问题多是CMakeLists.txt和它所在的依赖包
2.2.2、package.xml
定义package的属性
例如:报名、版本号、作者、依赖等
具体的写法:
根标记文件
3、通常我们需要改的地方是build和run这两个文件;
有时,有的package包下有manifest.xml(它是rosbuild编译系统(比较旧的编译系统)采用的包信息清单,累次catkin的package.xml)。
ros可执行代码文件一般有两种来源脚本(shell、python)和C++(头文件、源文件)
scripts路径用来放可执行脚本的文件(*.py、*.sh)
include文件放头文件(*.h)
src文件放源文件(*.cpp),有时还会出现python的文件脚本
通常还可以放一些
自定义通信格式
消息(msg)、服务(srv)、动作(action)
package可以放代码、通信格式、launch以及配置文件
launch文件(launch)、配置文件(yaml)(参数、设置等),配置文件一般是什么内容什么格式;
常有指令:
tree -L 1目录
build放置的是中间文件和缓存
devel放置的是生成的目标文件
创建package.xml文件catkin_create_pkg test1
若要创建文件并添加依赖包
catkin_create_pkg test roscpp rospy std_msgs nav_msgs
依赖 通信 导航
1、cd ~/catkin_ws/src
克隆github的文件
git clone https://github.com/DroidAITech/ROS-Academy-for-Begnners.git
安装教学包所需的依赖
$ cd ~/catkin_ws
$ rosdep install --from-paths src --ignore-src --rosdistro=kinetic -y
$ rosdep install --from-paths src --ignore-src --rosdistro=kinetic -y
3、编译并刷新环境
catkin_make
source ~/tutorial_ws/devel/setup.bash
(rospack list测试 )
rospack list | grep tutorial_ws(管道操作一下)
由于source生命周期很短,将这一段放到barshrc文件下
echo “source ~/tutorial_ws/devel/setup.bash” >> ~/.bashrc
vi .bashrc(查看bashrc文件)
/
4、rosls topic_demo/
可以直接定义到所要查看的目录
rosed topic_demo CMakeLists.txt
可以直接定义到包里面的文件
roscd robot_sim_demo/
可以直接cd到功能包底下
robot_sim_demos仿真程序
launch
models放置博物馆的场景
param放置参数
scripts放置python脚本
urdf存放机器人模型参数
5、Metapacakge
软件包管理(虚包),自身没有内容,却依赖了其他的软件包;
Metapacakge
名称 描述 链接
navigation
导航相关的功能包集 https://github.com/ros-planning/navigation
moveit
运动规划相关的(主要是机械臂)功能包集 https://github.com/ros-planning/moveit
image_pipeline
图像获取、处理相关的功能包集 https://github.com/ros-perception/image_common
vision_opencv
ROS与OpenCV交互的功能包集 https://github.com/ros-perception/vision_opencv
turtlebot Turtlebot
机器人相关的功能包集 https://github.com/turtlebot/turtlebot
pr2_robot pr2
机器人驱动功能包集 https://github.com/PR2/pr2_robot
软件包 |
内容 |
---|---|
robot_sim_demo |
机器人仿真程序,大部分示例会用到这个软件包 |
topic_demo |
topic通信,自定义msg,包括C++和python两个版本实现 |
service_demo |
service通信,自定义srv,分别以C++和python两种语言实现 |
action_demo |
action通信,自定义action,C++和python两种语言实现 |
param_demo |
param操作,分别以C++和python两种语言实现 |
msgs_demo |
演示msg、srv、action文件的格式规范 |
tf_demo |
tf相关API操作演示,tf示例包括C++和python两个版本 |
tf_follower |
制作mybot机器人 实现mybot跟随xbot的功能 |
urdf_demo |
创建机器人urdf模型,在RViz中显示 |
navigation_sim_demo |
导航演示工具包,包括AMCL, Odometry Navigation等演示 |
slam_sim_demo |
同步定位与建图演示,包括Gmapping, Karto, Hector等SLAM演示 |
robot_orbslam2_demo |
ORB_SLAM2的演示 |
ros_academy_for_beginners |
Metapacakge示例,依赖了本仓库所有的pacakge |
6、Master和Node
ROS通信架构(计算图级)
若要启动这些节点,需要节点管理器master
master(每个node启东市都要向master注册,管理弄得之间的通信)
的方法
roscore
启动Node(节点),一个node就是一个进程,pkg里的可执行运行的实例;
启动一个node $ rosrun [pkg_name] [node_name]
相当于roslaunch rikirobot bringup.launch
7、打开仿真模拟环境(https://www.imooc.com/article/252345)
SUMMARY
========
PARAMETERS #参数服务器
NODES
/xbot/
robot_state_publisher (robot_state_publisher/robot_state_publisher) #输出机器人自身状态
spawner (controller_manager/spawner) # 启动机器人的模型
/
cmd_vel_mux (nodelet/nodelet) # 做速度选择器的node节点
gazebo (gazebo_ros/gzserver) # 后端gazebo
gazebo_gui (gazebo_ros/gzclient) #前端展示
mobile_base_nodelet_manager (nodelet/nodelet) #控制底盘运动
urdf_spawner (gazebo_ros/spawn_model) # 与机器人自身结构有关系
显示 /camera/rgb/image_raw 这个 topic 的属性信息
topic 里包含了以下 3 种信息
信息类型
发布者
订阅者
itx@turtlebot:~$
rostopic info /camera/rgb/image_raw
Type: sensor_msgs/Image #
类型
Publishers: #
发布者
* /gazebo (http://10.180.179.209:41085/) #
这是
1
个
node
Subscribers: None #
接收者
由此可知,此时并没有订阅者。
4、用 image_view 作为订阅者接收消息
rosrun image_view image_view image:=/camera/rgb/image_raw
# image:=
这里
image
在下面
nodelet_plugins.xml
定义了
[ INFO] [1546616959.571473531, 7864.310000000]: Using transport "raw"
$ rosrun [--prefix cmd] [--debug] pkg_name node_name [ARGS] # rosrun
语法结构
查看一下 image_view 这个 package
itx@turtlebot:~$
roscd image_view/
itx@turtlebot:/opt/ros/kinetic/share/image_view$
ls
cmake nodelet_plugins.xml package.xml
itx@turtlebot:/opt/ros/kinetic/share/image_view$
cat nodelet_plugins.xml
Nodelet to view a sensor_msgs/Image topic
Nodelet to view a stereo_msgs/DisparityImage topic
可以看到新增了两个 node
itx@turtlebot:~$
rosnode list
/cmd_vel_mux
/gazebo
/gazebo_gui
/image_view_1546616674359947887 # image_view
/image_view_1546616958807306805 # image_view
/mobile_base_nodelet_manager
/rosout
/turtlebot_teleop_keyboard
/xbot/robot_state_publisher
/xbot/spawner
再次查看 topic info
itx@turtlebot:~$
rostopic info /camera/rgb/image_raw
Type: sensor_msgs/Image
Publishers:
* /gazebo (http://10.180.179.209:41085/)
Subscribers:
* /image_view_1546616958807306805 (http://10.180.179.209:43049/)
此时已经有 Subscribers 了。
8、通信方式
8.1、Topic(异步通信)
message(topic内容的数据类型;定义*.msg文件中)
基本的msg包括
bool、int8、int16、int32、int64(以及uint)
float32、float64、string
time、duration、header
可变长数组array[],固定长度数组array[C]
vector<…...>
操作演示:
1、打开模拟环境
roslaunch robot_sim_demo robot_spawn.launch
2、查看有topic
rostopic list
查看某个topic属性
rostopic info /camera/rgb/image_raw
接受摄像头topic
rosrun image_view image_view image:=/camera/rgb/image_raw
查看深度摄像图
rostopic info /camera/depth/image_raw
接受深度图
rosrun image_view image_view image:=/camera/depth/image_raw
rostopic list
查看速度信息
rosrun info /teleop_twist_keyboard
查看坐标轴
rosrun info /cmd_vel
rostopic echo /cmd_vel
rosmsg show geometry_msgs/Twist
查看系统所有的msg
rosmsg list
8.2、service
区别
topic是异步,service是同步
topic Publisher发布一个消息会直接执行后面的东西,而service会调用一个服务,他会一直等待那个结果;
通信模型:一个订阅发布,一个通信响应;topic多对多,service多对一;
8.3、srv
service`通信的数据格式;定义在*.srv文件中;
一问一答
srv文件只能
嵌套一个
msg文件
9、Parameter Server(参数服务器)
10、首先启动仿真
roslaunch robot_sim_demo robot_spawn.launch
若要查看service
rosservice list
若要查看node
rosservice info service的名称(gazebo/delete_light)
显示srv内容
rossrv show gazebo_msgs/DeleteLight
删除灯光
rosservice call /gazebo/delete_light "light_name: 'sun'"
查看rosparam
rosparam list
rosparam get /gazebo_gui/gravity_z
将当前参数存到一个文件中
rosparam dump test.yeal
ls
打开vi test.yaml
习惯将这些参数保存到test文件下,方便读取与应用;
rosparam load test.yaml(命令行加载)
11、Action
长时间导航
action通信的数据格式
定义在*.action文件中
feedback可以回传多次
result可以回传一次
位姿包含平移和旋转
机器人仿真工具
ode物理引擎
用于动力学、导航、感知等任务的模拟
Rviz
The Robot Visualization tool 可视化工具
方便监控和调试
rqt
可视化工具
rqt_graph :显示通信架构
rqt_plot:绘制曲线
rqt_consol:查看日志
打开仿真环境
在终端打开通信架构
rqt_graph
等未完善,可查看(https://www.bilibili.com/video/av24585414/?p=19)
13、rosbag
ROS命令行工具
记录和回放数据流(记录的消息保存在.bag文件)
rosbag record /camera/rgb/image_raw
14、Client Library
提供ROS编程库
例如:建立node、发布消息、调用服务。。。
roscpp----------------c++执行
rospy-----------------python开发
roslip
14.1、roscpp(https://docs.ros.org/api/roscpp/html/)
表示init函数是在ros环境下
NodeHandle是一个类
15、topic_demo
功能描述:两个node,一个发布模拟的GPS消息(格式为自定义,包括坐标和工作状态),另一个接收并处理该信息(计算到原点的距离)。
步骤:
首先建立package----定义好msg(文件中有所需信息)----连个节点分别放在cpp中talker.cpp----listener.cpp----修改这两个文件CmakeList.txt&package.xml
告诉编译系统
ROS的c++程序的头文件
#include
#include
int main(int argc,char **argv)
{
ros::init(argc, argv, “talker”); //用于解析ROS参数,第三个参数为本节点名
ros::NodeHandle nh; //实例化句柄,初始化node
//自定义gps msg
topic_demo::gps msg;
msg.x = 1.0;
msg.y = 1.0;
msg.state = “working”;
//创建publisher(参数设为1随时发送随时接收)
ros::Publisher pub = nh.advertise
//定义发布的频率
ros::Rate loop_rate(1.0);
//循环发布msg
while (ros::ok())
{
//以指数增长,每隔1秒更新一次
msg.x = 1.03 * msg.x ;
msg.y = 1.01 * msg.y;
ROS_INFO(“Talker: GPS: x = %f , y = %f ”, msg.x , msg.y);
//以1hz的频率发布msg
pub.publish(msg);
//根据前面定义的频率, sleep ls
loop_rate.sleep(); //根据前面的定义的loop_rate,设置ls的暂停
}
return 0;
}
//ROS头文件
#include
//包含自定义msg产生的头文件
#include
//ROS标准msg头文件
#include
void gpsCallback(const topic_demo::gps::ConstPtr &msg)
{
//计算离原点(0,0)的距离
std_msgs::Float32 distance;
distance.data = sqrt(pow(msg->x,2)+pow(msg->y,2));
//float distance = sqrt(pow(msg->x,2)+pow(msg->y,2));
ROS_INFO("Listener: Distance to origin = %f, state: %s",distance.data,msg->state.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("gps_info", 1, gpsCallback);
//ros::spin()用于调用所有可触发的回调函数。将进入循环,不会返回,类似于在循环里反复调用ros::spinOnce()。
ros::spin();
return 0;
}
16、roscpp
16.1、service_demo
功能描述:两个node,一个发布请求(格式自定义)另一个接受处理该信息,并返回信息;
步骤:
package——srv——server.cpp——client.cpp——CMakeList.txt%package.xml
// This is the C++ version server file of the service demo
//
// 加载必要文件,注意Service_demo的加载方式
# include "ros/ros.h"
# include "service_demo/Greeting.h"
# include "string"
// 定义请求处理函数
bool handle_function(service_demo::Greeting::Request &req,
service_demo::Greeting::Response &res)
{
// 此处我们对请求直接输出
ROS_INFO("Request from %s with age %d ", req.name.c_str(), req.age);
// 返回一个反馈,将response设置为"..."
res.feedback = "Hi " + req.name + ". I'm server!";
return true;
}
int main(int argc, char **argv)
{
// 初始化节点,命名为"greetings_server"
ros::init(argc, argv, "greetings_server");
// 定义service的server端,service名称为“greetings”,收到request请求之后传递给handle_function进行处理
ros::NodeHandle nh;
ros::ServiceServer service = nh.advertiseService("greetings", handle_function);
// 调用可
ros::spin();
return 0;
}
----------------------------------------------------------------------------------------------
开始编写client.cpp文件
// This is client of the service demo
// 包含必要文件,注意Service文件的包含方式,我们定义的srv文件为Greeting.srv,在包含时需要写成Greeting.h
# include "ros/ros.h"
# include "service_demo/Greeting.h"
int main(int argc, char **argv)
{
// 初始化,节点命名为"greetings_client"
ros::init(argc, argv, "greetings_client");
// 定义service客户端,service名字为“greetings”,service类型为Service_demo
ros::NodeHandle nh;
ros::ServiceClient client = nh.serviceClient
// 实例化srv,设置其request消息的内容,这里request包含两个变量,name和age,见Greeting.srv
service_demo::Greeting srv;
srv.request.name = "HAN";
srv.request.age = 20;
if (client.call(srv))
{
// 注意我们的response部分中的内容只包含一个变量response,另,注意将其转变成字符串
ROS_INFO("Response from server: %s", srv.response.feedback.c_str());
}
else
{
ROS_ERROR("Failed to call service Service_demo");
return 1;
}
return 0;
}
---------------------------------------------------------------------------------
16.2、param_demo参数服务器(相当于字典,用来存储变量)
两种API:ros::param和ros::NodeHandle
进行读写
param_demo_cpp.launch
param3: 3
param4: 4
param5: 5
17、rospy(Node、Topic、Service、Param、Time)
文件开头import rospy
rospy init_node(‘name’) # 注册和初始化node
get_master() # 获取master的句柄
is_shutdown() # 返回是否关闭
on_shotdown(fn) # 在node关闭时调用函数
get_node_uri() # 返回节点的uri
get_name() # 返回本节点的全名
get_namespace() # 返回本节点的名字空间
Topic相关
函数
wait_for_service(service, timeout=None) # 阻塞直到服务可用
service类:
_init_(self, name, service_class, handler) # 构造函数 提供服务
shutdown(self) # 成员函数 关闭服务
serviceProxy类
__init__(self, name, service_class) # 构造函数 服务的请求方
call(self, *args, **kwds) # 调用服务
__call__(self, *argd, **kwds) # 调用服务
Param相关
pylistener.py
------------------------------------------------------------------------
#!/usr/bin/env python
#coding=utf-8
import rospy
import math
#导入mgs到pkg中
from topic_demo.msg import gps
#回调函数输入的应该是msg
def callback(gps):
distance = math.sqrt(math.pow(gps.x, 2)+math.pow(gps.y, 2))
rospy.loginfo('Listener: GPS: distance=%f, state=%s', distance, gps.state)
def listener():
rospy.init_node('pylistener', anonymous=True)
#Subscriber函数第一个参数是topic的名称,第二个参数是接受的数据类型 第三个参数是回调函数的名称
rospy.Subscriber('gps_info', gps, callback)
rospy.spin()
if __name__ == '__main__':
listener()
-------------------------------------------------------------------------------------
pytalker.py
-------------------------------------------------------------------------
#!/usr/bin/env python
#coding=utf-8
import rospy
#倒入自定义的数据类型
from topic_demo.msg import gps
def talker():
#Publisher 函数第一个参数是话题名称,第二个参数 数据类型,现在就是我们定义的msg 最后一个是缓冲区的大小
#queue_size: None(不建议) #这将设置为阻塞式同步收发模式!
#queue_size: 0(不建议)#这将设置为无限缓冲区模式,很危险!
#queue_size: 10 or more #一般情况下,设为10 。queue_size太大了会导致数据延迟不同步。
pub = rospy.Publisher('gps_info', gps , queue_size=10)
rospy.init_node('pytalker', anonymous=True)
#更新频率是1hz
rate = rospy.Rate(1)
x=1.0
y=2.0
state='working'
while not rospy.is_shutdown():
#计算距离
rospy.loginfo('Talker: GPS: x=%f ,y= %f',x,y)
pub.publish(gps(state,x,y))
x=1.03*x
y=1.01*y
rate.sleep()
if __name__ == '__main__':
talker()
–--------------------------------------------------------------------------
service_demo
server_demo.py
-------------------------------------------------------------------------
#!/usr/bin/env python
# coding:utf-8
# 上面指定编码utf-8,使python能够识别中文
# 加载必需模块,注意service模块的加载方式,from 包名.srv import *
# 其中srv指的是在包根目录下的srv文件夹,也即srv模块
import rospy
from service_demo.srv import *
def server_srv():
# 初始化节点,命名为 "greetings_server"
rospy.init_node("greetings_server")
# 定义service的server端,service名称为"greetings", service类型为Greeting
# 收到的request请求信息将作为参数传递给handle_function进行处理
s = rospy.Service("greetings", Greeting, handle_function)
rospy.loginfo("Ready to handle the request:")
# 阻塞程序结束
rospy.spin()
# Define the handle function to handle the request inputs
def handle_function(req):
# 注意我们是如何调用request请求内容的,与前面client端相似,都是将其认为是一个对象的属性,通过对象调用属性,在我们定义
# 的Service_demo类型的service中,request部分的内容包含两个变量,一个是字符串类型的name,另外一个是整数类型的age
rospy.loginfo( 'Request from %s with age %d', req.name, req.age)
# 返回一个Service_demoResponse实例化对象,其实就是返回一个response的对象,其包含的内容为我们再Service_demo.srv中定义的
# response部分的内容,我们定义了一个string类型的变量,因此,此处实例化时传入字符串即可
return GreetingResponse("Hi %s. I' server!"%req.name)
# 如果单独运行此文件,则将上面定义的server_srv作为主函数运行
if __name__=="__main__":
server_srv()
------------------------------------------------------------------------------------------------
client_demo.py
-----------------------------------------------------------------------
#!/usr/bin/env python
# coding:utf-8
# 上面的第二句指定编码类型为utf-8,是为了使python能够识别中文
# 加载所需模块
import rospy
from service_demo.srv import *
def client_srv():
rospy.init_node('greetings_client')
# 等待有可用的服务 "greetings"
rospy.wait_for_service("greetings")
try:
# 定义service客户端,service名称为“greetings”,service类型为Greeting
greetings_client = rospy.ServiceProxy("greetings",Greeting)
# 向server端发送请求,发送的request内容为name和age,其值分别为"HAN", 20
# 注意,此处发送的request内容与service文件中定义的request部分的属性是一致的
#resp = greetings_client("HAN",20)
resp = greetings_client.call("HAN",20)
# 打印处理结果,注意调用response的方法,类似于从resp对象中调取response属性
rospy.loginfo("Message From server:%s"%resp.feedback)
except rospy.ServiceException, e:
rospy.logwarn("Service call failed: %s"%e)
# 如果单独运行此文件,则将上面函数client_srv()作为主函数运行
if __name__=="__main__":
client_srv()
--------------------------------------------------------------------------------
18、tf(TransForm)
坐标转换的标准、话题、工具、接口
tf tree /tf roscpp
rospy
一个link(坐标系)对应一个frame
难!!!!!!!!!!!!!!!!!!!!!!
18.1、TF in C++
19、urdf文件(统一机器人的结构)
20、SLAM和Map(路径规划)
待续。。。。。。