ROS入门(一)——基本概念和话题
ROS入门(一)——基本概念和话题
iwehdio的博客园:https://www.cnblogs.com/iwehdio/
1、环境搭建
-
虚拟机 VMware
VMware Workstation Pro 15.0.0 Build 10134415
https://download3.vmware.com/software/wkst/file/VMware-workstation-full-15.0.0-10134415.exe -
操作系统 Ubuntu 18.04.4 LTS 桌面64位
http://mirrors.aliyun.com/ubuntu-releases/18.04.4/ -
ROS安装
-
添加ROS软件源。
$ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
-
添加密钥。
$ sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
-
更新软件源。
$ sudo apt-get update
-
安装ROS。
$ sudo apt-get install ros-melodic-desktop-full $ sudo apt-get dist-upgrade
-
初始化rosdep。
$ sudo rosdep init $ rosdep update
-
设置环境变量。
$ echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc
-
安装rosinstall。
$ sudo apt install python-rosinstall python-rosinstall-generator python-wstool build-essential
-
例程。
$ roscore $ rosrun turtlesim turtlesim_node $ rosrun turtlesim turtle_teleop_key
-
-
ubuntu 下的 Linux 命令:
- Ubuntu 终端快捷键:ctrl + alt + T。
- 查看当前路径:pwd 。
- 切换 / 进入路径:cd 路径 。
- 返回上一级目录:cd .. 。
- 创建新文件夹:mkdir 文件夹 。
- 查看路径下的文件:ls 。
- 创建新文件:touch 文件名 。
- 剪切文件到新路径:mv 文件名 新路径 。
- 复制文件到新路径:cp 文件名 新路径 。
- 删除文件:rm 文件名 。
- 删除文件夹:rm -r 文件夹 。
- 提升当前用户权限:sudo 。
- 帮助指令:--help 。
-
在 linux 下运行 C++ 文件:
- 首先在终端进入文件目录下。
- 编译:
g++ 文件名.cpp -o 编译文件名
。 - 运行:
./编译文件名
。 - 如果是运行 python 文件直接:
python 文件名.py
。 - 注意 ROS 下的是 python2 版本。
2、ROS核心概念
-
ROS = 通信机制 + 开发工具 + 应用功能 + 生态系统,目的是提高机器人开发中软件的复用率。
-
节点与节点管理器:
-
话题通信(异步通信,无反馈):
-
服务通信(同步通信,有反馈):
-
话题和服务的区别:
-
参数(存储在参数服务器中):适合存储静态数据。
-
ROS文件系统:
3、ROS基本使用
- 命令行工具:
- 单击 tab 可补全命令,双击 tab 可显示信息。
roscore
:启动节点服务器。rqt_graph
:显示系统计算图。rosnode 具体指令
:显示系统中的节点信息,后接具体指令。- list:获取所有的节点列表。
- info /节点名:查看某个节点的信息。
rostopic 具体指令
:显示系统中的话题信息,后接具体指令。- list:获取所有的话题列表。
- pub /话题名 消息类型 具体数据:获取话题传输的数据。
- pub 后可跟 -r 频率 ,表示一秒中发布的次数,不设置则话题只会发布一次。
rosmsg show 消息类型
:显示小数类型的具体数据格式。rosservice 具体指令
:显示系统中的服务信息,后接具体指令。- list:获取所有的话题列表。
- call /服务名 服务内容:设置服务内容,调用服务,返回服务名称。
rosbag 具体指令
:使用话题存储数据。- record -a -o 文件名:记录数据。-a 表示记录所有数据,-o 表示将数据存储到指定文件名的压缩包中。
- play 文件名:复现记录的数据。
- ROS 工作空间:
- src:代码空间。源代码和功能包等。
- build:编译空间。编译中间文件。
- devel:开发空间。可执行文件。
- install:安装空间。安装结果。
- 创建工作空间:
mkdir -p ~/工作空间名/src
。cd ~/工作空间名/src
。catkin_init_workspace
。
- 编译工作空间:
cd ~/工作空间名/
。编译目录要回到工作空间根目录。catkin_make
。后加 install 会产生 install 文件夹。
- 设置环境变量:
source devel/setup.bash
。
- 检测环境变量:
echo $ROS_PACKAGE_PATH
。
- 创建功能包:
catkin_create_pkg 功能包名 依赖包
。比如依赖 C++、python 要写 roscpp、rospy,依赖标准消息格式要写 std_msgs。- 创建功能包要在 src 目录中操作。
- 编译功能包:
- 回到工作空间根目录下编译工作空间。
- 同一个工作空间中不允许存在同名功能包。不同工作空间中可以存在同名功能包。
- 配置文件:
- package.xml:功能包的相关信息和依赖包。
- CMakeLists.txt:描述功能包的编译规则。
3、话题的实现
-
发布者 Publisher 的实现:
-
创建功能包(工作空间src目录下):
catkin_create_pkg learning_topic roscpp rospy std_msgs geometry_msgs turtlesim
。 -
初始化 ROS 节点,赋予节点名称。
-
向 ROS Master 注册节点信息,设置消息类型,传入话题名称和队列(用于缓冲数据)长度。
-
创建消息数据,并循环发布。
-
C++ 实现:
-
首先需要将文件放入功能包下的目录。
-
配置 CMakeLists.txt 中的编译规则。
- 在 Build 模块下,参数空格分隔。
- 设置生成好可执行文件名和需要编译的代码:
add_executable(velocity_publisher src/velocity_publisher.cpp)
。 - 设置生成的可执行文件所链接的库:
target_link_libraries(velocity_publisher ${catkin_LIBRARIES})
。
-
回到工作空间的根目录下。
-
编译工作空间。
$ catkin_make $ source devel/setup.bash
- 可将 source devel/setup.bash 添加到 home 下的隐藏文件 .bashrc 的最底部作为环境变量。这样不必每次都调用该指令。
-
运行功能包下的 C++ 文件。
$ roscore $ rosrun turtlesim turtlesim_node $ rosrun learning_topic velocity_publisher
-
-
C++ 源代码:
/** * 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist */ #include <ros/ros.h> #include <geometry_msgs/Twist.h> int main(int argc, char **argv) { // ROS节点初始化 ros::init(argc, argv, "velocity_publisher"); // 创建节点句柄 ros::NodeHandle n; // 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10 ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10); // 设置循环的频率 ros::Rate loop_rate(10); int count = 0; while (ros::ok()) { // 初始化geometry_msgs::Twist类型的消息 geometry_msgs::Twist vel_msg; vel_msg.linear.x = 0.5; vel_msg.angular.z = 0.2; // 发布消息 turtle_vel_pub.publish(vel_msg); ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", vel_msg.linear.x, vel_msg.angular.z); // 按照循环频率延时 loop_rate.sleep(); } return 0; }
-
-
Python 实现:
-
不需要设置编译,但要确认 .py 文件的属性中,可被作为可执行文件运行。
-
其他流程类似,注意最后运行时一定要在文件名后加 .py 后缀。
-
在 Python 中 ROS 节点初始化中的节点名、函数名要一致。
-
"/turtle1/cmd_vel" 是 turtlesim 默认订阅的的运动控制话题。
-
Python 源代码:
#!/usr/bin/env python # -*- coding: utf-8 -*- # 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist import rospy from geometry_msgs.msg import Twist def velocity_publisher2(): # ROS节点初始化 rospy.init_node('velocity_publisher2', anonymous=True) # 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10 turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10) #设置循环的频率 rate = rospy.Rate(10) while not rospy.is_shutdown(): # 初始化geometry_msgs::Twist类型的消息 vel_msg = Twist() vel_msg.linear.x = 0.5 vel_msg.angular.z = 0.2 # 发布消息 turtle_vel_pub.publish(vel_msg) rospy.loginfo("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", vel_msg.linear.x, vel_msg.angular.z) # 按照循环频率延时 rate.sleep() if __name__ == '__main__': try: velocity_publisher2() except rospy.ROSInterruptException: pass
-
-
-
订阅者 Subsceiber 的实现:
-
初始化 ROS 节点并赋予节点名。
-
定于所需要的话题名、队列长度和回调函数。
-
循环等待话题消息,接收到消息后进入回调函数。
-
在回调函数中进行消息处理。
-
"/turtle1/pose" 是 turtlesim 默认发布的的运动姿态话题。
-
Python 实现:
#!/usr/bin/env python # -*- coding: utf-8 -*- # 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose import rospy from turtlesim.msg import Pose def poseCallback(msg): rospy.loginfo("Turtle pose: x:%0.6f, y:%0.6f", msg.x, msg.y) def pose_subscriber(): # ROS节点初始化 rospy.init_node('pose_subscriber', anonymous=True) # 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback rospy.Subscriber("/turtle1/pose", Pose, poseCallback) # 循环等待回调函数 rospy.spin() if __name__ == '__main__': pose_subscriber()
-
-
话题消息的定义与使用(自定义话题消息):
-
创建 msg 文件夹并在其下创建 Person.msg 文件(
touch Person.msg
),并消息格式为:string name uint8 sex uint8 age uint8 unknown = 0 uint8 male = 1 uint8 female = 2
-
在 package.xml 中添加功能包依赖。即编译依赖 message_generation,运行依赖 message_runtime。
<build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>
-
在 CMakeLists.txt 中添加编译选项。
-
在 find_package() 中添加
message_generation
。 -
定义消息接口,在 Declare ROS messages, services and actions 下。
add_message_files(FILES Person.msg) generate_messages(DEPENDENCIES std_msgs)
- 第一行表示将 Person.msg 作为一个消息接口,第二行表示该消息接口的依赖。
-
在 catkin_package 中添加运行时依赖。
catkin_package( CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs turtlesim message_runtime )
- 去掉 CATKIN_DEPENDS 前的注释,并在其后添加 message _runtime。
-
-
创建发布者和订阅者(C++实现):
-
发布者:
/** * 该例程将发布/person_info话题,自定义消息类型learning_topic::Person */ #include <ros/ros.h> #include "learning_topic/Person.h" int main(int argc, char **argv) { // ROS节点初始化 ros::init(argc, argv, "person_publisher"); // 创建节点句柄 ros::NodeHandle n; // 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10 ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10); // 设置循环的频率 ros::Rate loop_rate(1); int count = 0; while (ros::ok()) { // 初始化learning_topic::Person类型的消息 learning_topic::Person person_msg; person_msg.name = "Tom"; person_msg.age = 18; person_msg.sex = learning_topic::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_info话题,自定义消息类型learning_topic::Person */ #include <ros/ros.h> #include "learning_topic/Person.h" // 接收到订阅的消息后,会进入消息回调函数 void personInfoCallback(const learning_topic::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; }
-
-
在 CMakeLists.txt 中设置编译规则。处理之前的设置可执行文件和编译代码、设置链接库以外,还需要添加依赖项。
add_executable(person_publisher src/person_publisher.cpp) target_link_libraries(person_publisher ${catkin_LIBRARIES}) add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp) add_executable(person_subscriber src/person_subscriber.cpp) target_link_libraries(person_subscriber ${catkin_LIBRARIES}) add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)
-
再进行编译并运行发布者和订阅者。
$ cd ~/workspace0 $ catkin_make $ roscore $ rosrun learning_topic person_subscriber $ rosrun learning_topic person_publisher
-
如果在建立连接后,关闭 ROS Master(即roscore命令行),二者的连接不会中断。
-
-
创建发布者和订阅者(Python实现):
-
发布者:
#!/usr/bin/env python # -*- coding: utf-8 -*- # 该例程将发布/person_info话题,自定义消息类型learning_topic::Person import rospy from learning_topic.msg import Person def velocity_publisher(): # ROS节点初始化 rospy.init_node('person_publisher', anonymous=True) # 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10 person_info_pub = rospy.Publisher('/person_info', Person, queue_size=10) #设置循环的频率 rate = rospy.Rate(10) while not rospy.is_shutdown(): # 初始化learning_topic::Person类型的消息 person_msg = Person() person_msg.name = "Tom"; person_msg.age = 18; person_msg.sex = Person.male; # 发布消息 person_info_pub.publish(person_msg) rospy.loginfo("Publsh person message[%s, %d, %d]", person_msg.name, person_msg.age, person_msg.sex) # 按照循环频率延时 rate.sleep() if __name__ == '__main__': try: velocity_publisher() except rospy.ROSInterruptException: pass
-
订阅者:
#!/usr/bin/env python # -*- coding: utf-8 -*- # 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person import rospy from learning_topic.msg import Person def personInfoCallback(msg): rospy.loginfo("Subcribe Person Info: name:%s age:%d sex:%d", msg.name, msg.age, msg.sex) def person_subscriber(): # ROS节点初始化 rospy.init_node('person_subscriber', anonymous=True) # 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback rospy.Subscriber("/person_info", Person, personInfoCallback) # 循环等待回调函数 rospy.spin() if __name__ == '__main__': person_subscriber()
-
无论是 C++ 还是 Python 都要记得引入生成的 Person 消息类型。
-
参考:古月ROS入门21讲:https://www.bilibili.com/video/BV1zt411G7Vn?p=21
iwehdio的博客园:https://www.cnblogs.com/iwehdio/