ROS入门(一)——基本概念和话题

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安装

    1. 添加ROS软件源。

      $ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
    2. 添加密钥。

      $ sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
    3. 更新软件源。

      $ sudo apt-get update
    4. 安装ROS。

      $ sudo apt-get install ros-melodic-desktop-full
      $ sudo apt-get dist-upgrade
    5. 初始化rosdep。

      $ sudo rosdep init
      $ rosdep update
    6. 设置环境变量。

      $ echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc
    7. 安装rosinstall。

      $ sudo apt install python-rosinstall python-rosinstall-generator python-wstool build-essential
    8. 例程。

      $ 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

出处:https://www.cnblogs.com/iwehdio/p/12721349.html

——————来源于结束于否定之否定。——————