ROS工作及通信机制

ROS具有一套自己的工作流程,其文件体系依托在工作空间中,开发的流程如下图所示。

如果是 python 代码则不需要配置编译规则。

工作空间

工作空间(Workspace):存放工程开发相关文件的文件夹。类似一个IDE(例如Pycharm)新建一个工程,就是一个工作空间。包含4个文件夹:

  • src:代码空间(Source Space):放置功能包代码
  • build:编译空间(Build Space):编译过程中产生的中间文件,不用过多关注
  • devel:开发空间(Development Space):放置编译生成的可执行文件、库、脚本
  • install:安装空间(Install Space):存放可执行文件,与上面有一定重复

    创建工作空间

#创建工作空间指令
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace

我们先创建一个src文件夹,然后进入该文件夹
第3行catkin_init_workspace意为将当前文件夹变为工作空间,使其拥有工作空间的属性
注:“~/”意为当前用户名目录;“-p”意为递归创建目录,即直接创建多级目录。
src文件夹即代码空间,不能自行用别的名称代替

#编译空的工作空间
cd ~/catkin_ws/  
catkin_make #将src里的源码进行了编译
#生成了build和devel两个新文件夹,devel存放了编译完成的内容

#设置环境变量
source devel/setup.bash

#如果要生成install文件夹
catkin_make install

为了不每次打开终端都设置环境变量(即都执行一遍 $ source devel/setup.bash),可以把这句话添加到系统环境变量.bashrc 中。

  1. 通过 $ sudo vim ~/.bashrc 打开.bashrc 文件。
  2. 在文件最后添加:source ~/catkin_ws/devel/setup.bash
  3. 保存,关闭。

    创建功能包

  • 功能包是放置ROS源码的最小单元。

1, package文件夹为功能包,也就是我们开发ROS工程主要编辑的地方
2, package文件夹下package.xml文件主要是描述整个功能包的属性。
3, package文件夹下CMakeLists.txt文件主要作用是描述整个功能包的编译规则,依赖库等等。
4, package文件夹下的src文件夹为功能包源文件所在。
5, package文件夹下的include文件夹为功能包源文件的头文件所在。
6, package文件夹下的script文件夹为python脚本和bash脚本所在,即无需编译即可运行的文件。
7, package文件夹下的launch文件夹为launch文件所在
8, package文件夹下的msg文件夹为msg文件所在
9, package文件夹下的srv文件夹为srv文件所在
9, package文件夹下的action文件夹为action文件所在

  • 上面我们创建了一个空的工作空间,src文件夹里面没写东西,现在我们创建一个自己的功能包。 注意同一工作空间下,不允许存在同名功能包;不同工作空间下,允许存在同名功能包。
    指令格式:catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
    <package_name>为包名
    [depend]为依赖,即指明编译的时候需要ROS中的其他功能包,如需要调用python、C++库,就要指明rospy、roscpp。
    cd ~/catkin_ws/src 
    # 创建一个名叫learning_communication的功能包  
    # 这个功能包实现的接口有python接口,C++接口,标准定义的消息与服务接口
    catkin_create_pkg learning_communication rospy roscpp std_msgs std_srvs
    
    # 编译功能包
    cd ~/catkin_ws
    catkin_make
    source ~/catkin_ws/devel/setup.bash

    Topic 通信机制

发布者Publisher

  • 初始化ROS节点
  • 向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型
  • 创建消息数据
  • 按照一定的频率循环发布消息
    /**  
     * 该例程将发布chatter话题,消息类型String  
     */  
      
    // 字符串的流的数据转换  
    #include <sstream>  
    // 包含ros头文件,一般都会写  
    #include "ros/ros.h"  
    // 后面会用到ROS中string的消息类型  
    #include "std_msgs/String.h"  
      
    int main(int argc, char **argv)  
    {  
        // ROS节点初始化,声明节点名,这个名字不能和其他节点重名  
        ros::init(argc, argv, "string_publisher");  
      
        // 创建节点句柄,完成ROS核心资源的管理。  
        // 在节点初始化和关闭一节中,使用句柄管理节点的内部引用,使启动和关闭一个节点变得简单。  
      
        ros::NodeHandle n;  
      
        // 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String。  
        // 在ros::Publisher类下实例化一个chatter_pub对象。  
        // 1000表示缓冲区大小,超过1000会把时间戳最老的删除掉。  
        // advertise( ) 返回一个 Publisher 对象。 通过调用对象的 publish( )函数可以在这个topic上发布 message。  
        ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);  
    	  
        // 设置循环的频率  
        // loop_rate()是ros::Rate类可定制频率的函数,接受一个int型参数,通过调用主函数末尾的上来sleep()函数,将该参数置为发布频率。  
        ros::Rate loop_rate(10);  
      
        int count = 0;  
          
        // ros::ok()会设置一个SIGINT监听,函数返回True,仅当以下四种情况发生时,该函数返回False:  
       // 1. SIGINT被触发(Ctrl+c)  
       // 2.被另一同名节点踢出  
       // 3.函数ros::shutdown()被程序另一部分调用  
       // 4.程序所有句柄被销毁  
        while (ros::ok())  
        {  
            // 初始化std_msgs::String类型的消息  
            std_msgs::String msg;  
            // 初始化字符串流  
            std::stringstream ss;  
            // 把信息放到字节流里  
            ss << "hello world " << count;  
            // msg中的data是存储信息的,直接赋值  
            msg.data = ss.str();  
      
            // 发布消息  
            // 输出一个字符串变量,ROS_INFO其实就是printf  
            // c_str():将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址。  
            ROS_INFO("%s", msg.data.c_str());  
            // 通过调用对象的 publish( )函数发布数据  
            chatter_pub.publish(msg);  
      
            // 按照循环频率延时  
            loop_rate.sleep();  
            ++count;  
        }  
      
        return 0;  
    }
    这个cpp文件创建在src文件夹下,有了源码之后需要在CMakeLists.txt中添加编译规则:
    # 设置需要编译的代码和生成的可执行文件  
    # 让string_publish.cpp这个cpp文件编译生成string_publish  
    add_executable(string_publisher src/string_publisher.cpp)  
    # 设置string_publisher与库catkin_LIBRARIES的连接  
    target_link_libraries(string_publisher ${catkin_LIBRARIES})
    
    # 后面的Subscriber也是如此
    add_executable(string_subscriber src/string_subscriber.cpp)  
    target_link_libraries(string_subscriber ${catkin_LIBRARIES})
    
    回到工作空间目录,执行编译.
    $ cd ~/catkin_ws  
    $ catkin_make  
      
    # 配置系统环境变量后这步可以省略  
    $ source devel/setup.bash  
      
    $ roscore  
    $ rosrun learning_communication string_publisher  
    $ rosrun learning_communication string_subscriber
  • python的源代码,放在功能包文件夹下的scripts目录下的。
    # 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist
    
    import rospy
    from geometry_msgs.msg import Twist
    
    def velocity_publisher():
    	# ROS节点初始化
        rospy.init_node('velocity_publisher', 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_publisher()
        except rospy.ROSInterruptException:
            pass

需要打开py文件的可执行权限

订阅者Subscriber

  • 初始化ROS节点
  • 订阅需要的话题
  • 循环等待话题消息,接收到消息后进入回调函数
  • 在回调函数中完成消息处理
    /**  
     * 该例程将订阅chatter话题,消息类型String  
     */  
       
    // ros头文件  
    #include "ros/ros.h"  
    // 后面会用到ROS中string的消息类型  
    #include "std_msgs/String.h"  
      
    // 接收到订阅的消息后,会进入消息回调函数  
    // 定义一个指针常量的引用msg  
    void chatterCallback(const std_msgs::String::ConstPtr& msg)  
    {  
        // 将接收到的消息打印出来  
        ROS_INFO("I heard: [%s]", msg->data.c_str());  
    }  
      
    int main(int argc, char **argv)  
    {  
        // 初始化ROS节点  
        ros::init(argc, argv, "string_subscriber");  
      
        // 创建节点句柄  
        ros::NodeHandle n;  
      
        // 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback  
        ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);  
      
        // 循环等待回调函数  
        // spin本身的循环条件就是ros::ok()  
        ros::spin();  
      
        return 0;  
    }
    
    配置编译规则和环境变量,进行编译
  • python版本
# 该例程将订阅/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文件来自定义话题消息。在功能包根目录下新建文件夹msg,并touch Person.msg,文件内容

string name  
uint8 age  
uint8 sex  
  
uint8 unknown = 0  
uint8 male    = 1  
uint8 female  = 2

在package.xml中添加功能包依赖:

<build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>

build_depend为编译依赖,这里依赖的是一个会动态产生message的功能包
exer_depend为执行依赖,这里依赖的是一个动态runtime运行的功能包

在CMakeLists.txt中添加编译选项:

  1. find_package(…… message_generation)
  2. 添加:add_message_files(FILES PersonMsg.msg)generate_messages(DEPENDENCIES std_msgs)(前一句话是编译生成头文件,后一句话是调用了 ros 的标准数据类型)
  3. catkin_package(…… message_runtime)
    回到根目录,编译catkin_make,编译完成后,我们可以在devel/include/learning_topic/ 下找到这个C++的头文件

Service通信机制


详细步骤懒得摘录了,跟Topic机制有一定的类似。直接见ROS学习笔记 03 ROS通信编程 | 孙健耕的博客


   转载规则


《ROS工作及通信机制》 CHQ 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录