ROS基础教程

发布时间:2025-12-09 19:02:44 浏览次数:5

文章目录

  • 1. 简介
    • 1.1. 系统架构
    • 1.2. 文件系统
    • 1.3. 计算图
    • 1.4. Source
    • 1.5. helloworld
      • 1.5.1. C++ 实现
      • 1.5.2. Python 实现
  • 2. 通信
    • 2.1. 话题通信
      • 2.1.1. 简介
      • 2.1.2. 理论模型
      • 2.1.3. C++ 实现
      • 2.1.4. Python 实现
      • 2.1.5. spinOnce 和 spin
      • 2.1.6. 话题同步
      • 2.1.7. 自定义 msg
      • 2.1.8. 自定义变长 msg
    • 2.2. 服务通信
      • 2.2.1. 简介
      • 2.2.2. 理论模型
      • 2.2.3. 话题和服务
      • 2.2.4. 自定义 srv
      • 2.2.5. C++ 实现
      • 2.2.6. Python 实现
    • 2.3. 参数服务器
  • 3. 运行
    • 3.1. launch
      • 3.1.1. 运行
      • 3.1.2. 标签
        • 3.1.2.1. launch
        • 3.1.2.2. node
        • 3.1.2.3. include
        • 3.1.2.4. remap
        • 3.1.2.5. param
        • 3.1.2.6. rosparam
        • 3.1.2.7. arg
        • 3.1.2.8. group

1. 简介

ROS 全称为 Robot Operating System (机器人操作系统)

  • ROS 是适用于机器人的开源元操作系统
  • ROS 集成了大量的工具,库,协议,提供类似 OS 所提供的功能,简化对机器人的控制
  • ROS 还提供了用于在多台计算机上获取,构建,编写和运行代码的工具和库,ROS 在某些方面类似于“机器人框架”
  • ROS 设计者将 ROS 表述为 “ROS = Plumbing + Tools + Capabilities + Ecosystem”,即 ROS 是通讯机制、工具软件包、机器人高层技能以及机器人生态系统的集合体

1.1. 系统架构

ROS 的系统架构可分为三层

  • OS 层,也即经典意义的操作系统。ROS 只是元操作系统,需要依托真正意义的操作系统,目前兼容性最好的是 Linux 的 Ubuntu,Mac、Windows 也支持 ROS 的较新版本
  • 中间层。是 ROS 封装的关于机器人开发的中间件,比如:
    • 基于 TCP/UDP 继续封装的 TCPROS/UDPROS 通信系统
    • 用于进程间通信 Nodelet,为数据的实时性传输提供支持
    • 大量的机器人开发实现库,如:数据类型定义、坐标变换、运动控制…
  • 应用层。功能包,以及功能包内的节点,比如: master、turtlesim的控制与运动节点。
  • 1.2. 文件系统

    WorkSpace --- 自定义的工作空间|--- build:编译空间,用于存放CMake和catkin的缓存信息、配置信息和其他中间文件。|--- devel:开发空间,用于存放编译后生成的目标文件,包括头文件、动态&静态链接库、可执行文件等。|--- src: 源码|-- CMakeLists.txt: 编译的基本配置|-- package:功能包(ROS 基本单元)包含多个节点、库与配置文件,包名所有字母小写,只能由字母、数字与下划线组成|-- CMakeLists.txt 配置编译规则,比如源文件、依赖项、目标文件|-- package.xml 包信息,比如:包名、版本、作者、依赖项...(以前版本是 manifest.xml)|-- scripts 存储 python 文件|-- src 存储 C++ 源文件|-- include 头文件|-- msg 消息通信格式文件|-- srv 服务通信格式文件|-- action 动作格式文件|-- launch 可一次性运行多个节点 |-- config 配置信息|-- package

    ROS 的文件系统本质上都还是操作系统文件,因此可以使用 Linux 命令来操作这些文件。不过,在 ROS 中为了更好的用户体验,ROS 专门提供了一些类似于 Linux 的命令,这些命令较之于 Linux 原生命令,更为简洁、高效。

  • # 创建新的ROS功能包catkin_create_pkg 自定义包名 依赖包# 安装 ROS 功能包sudo apt install xxx
  • # 删除某个功能包sudo apt purge xxx
  • # 列出所有功能包rospack list# 查找某个功能包是否存在,如果存在返回安装路径rospack find 包名# 进入某个功能包roscd 包名# 列出某个包下的文件rosls 包名# 搜索某个功能包apt search xxx
  • # 修改功能包文件(需要安装 vim)rosed 包名 文件名
  • 执行
  • # 必须运行 roscore 才能使 ROS 节点进行通信,他是 ROS 的系统先决条件节点和程序的集合。运行 roscore 会运行 ros master、ros 参数服务器、rosout 日志节点roscore # 或(指定端口号)roscore -p xxxx# 运行指定的ROS节点rosrun 包名 可执行文件名# 执行某个包下的 launch 文件roslaunch 包名 launch文件名

    1.3. 计算图

    ROS 文件结构是磁盘上 ROS 程序的存储结构,是静态的,而 ros 程序运行之后,不同的节点之间是错综复杂的,ROS 中提供了一个实用的工具:rqt_graph

    rqt_graph 能够创建一个显示当前系统运行情况的动态图形。ROS 分布式系统中不同进程需要进行数据交互,计算图可以以点对点的网络形式表现数据交互过程。rqt_graph 是 rqt 程序包中的一部分

    运行 rat_graph 可通过下面命令执行:

    rosrun rqt_graph rqt_graph# 或者直接输入rqt_graph

    1.4. Source

    • 下面命令是使得能够在任意终端都能使用 ROS
    echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrcsource ~/.bashrc
    • 下面命令是使得能够在任意终端都能使用当前工作空间
    echo "source ~/工作空间/devel/setup.bash" >> ~/.bashrcsource ~/.bashrc

    1.5. helloworld

    ROS 可是使用 C++ 或者 Python,但是具体选择哪种语言需要视需求而定:C++ 运行效率高但是编码效率低,而 Python 则反之。基于二者互补的特点,ROS 设计者分别设计了 roscpp 与 rospy 库,前者旨在成为 ROS 的高性能库,而后者则一般用于对性能无要求的场景,旨在提高开发效率

    1.5.1. C++ 实现

  • 进入 ros 包的 src 目录编辑源文件
  • #include "ros/ros.h"int main(int argc, char *argv[]) {//执行 ros 节点初始化,输入所有参数并对节点命名。注意,节点名称是一个标识符,需要保证在 ROS 网络拓扑中唯一ros::init(argc,argv,"hello");//创建 ros 节点句柄。注意,因为一个进程上运行一个节点,所以句柄是自动绑定到当前的进程,即节点上的ros::NodeHandle n;//控制台输出 hello worldROS_INFO("hello world!");return 0;}
  • 编辑 ros 包下的 Cmakelist.txt 文件
  • add_executable(可执行文件名src/步骤3的源文件名.cpp)target_link_libraries(可执行文件名${catkin_LIBRARIES})
  • 返回顶层工作空间目录并编译(生成 build 和 devel):catkin_make

  • 执行

  • 在一个终端输入命令启动 ROS 核心:roscore
  • 再在另一个终端输入命令刷新环境变量并运行节点:
  • source ./devel/setup.bashrosrun 包名 可执行文件名

    命令行输出: HelloWorld!

    1.5.2. Python 实现

  • 进入 ros 包的 scripts 目录编辑 python 文件(C++是进入同级的 src 目录)
  • #! /usr/bin/env pythonimport rospyif __name__ == "__main__":rospy.init_node("Hello")rospy.loginfo("HelloWorld!")
  • 为 python 文件添加可执行权限
  • chmod +x 文件名.py
  • 编辑 ros 包下的 CamkeList.txt 文件
  • catkin_install_python(PROGRAMS scripts/文件名.pyDESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
  • 返回顶层工作空间目录并编译:catkin_make

  • 执行

  • 在一个终端输入命令启动 ROS 核心:roscore
  • 再在另一个终端输入命令刷新环境变量并运行节点:
  • source ./devel/setup.bashrosrun 包名 文件名.py

    输出结果:HelloWorld!

    另外,因为 Python 文件运行不需要编译,所以在编写完脚本后,可以直接运行节点

    rosrun 包名 文件名.py

    或者直接运行 Python 文件

    python 文件名.py

    2. 通信

    机器人是一种高度复杂的系统性实现,在机器人上可能集成各种传感器(雷达、摄像头、GPS…)以及运动控制实现,为了解耦合,在 ROS 中每一个功能点都是一个单独的进程,每一个进程都是独立运行的。更确切的讲,ROS 是进程(也称为 Nodes)的分布式框架。 因为这些进程甚至还可分布于不同主机,不同主机协同工作,从而分散计算压力。而为了实现不同进程之间的通信,ROS 提供了三种方式:

  • 话题通信(发布订阅模式)
  • 服务通信(请求响应模式)
  • 参数服务器(参数共享模式)
  • 2.1. 话题通信

    2.1.1. 简介

    • ROS 中使用最多的通信方式
    • ROS 中的异步通信方式
    • 通过 TCP/IP 通信
    • Node 之间通过 publish-subscribe 机制通信
    • 能够进行多对多通信
    • 常用于连续、高频的数据发布:激光雷达、里程计发布数据
    • Message:
      • topic 内容的数据格式
      • 定义在 .msg 文件中
      • 可以看作是一个结构体或者类

    2.1.2. 理论模型

    话题通信理论模型中涉及到三个角色。其中,ROS Master 负责保管 Talker 和 Listener 注册的信息,并匹配话题相同的 Talker 与 Listener,帮助 Talker 与 Listener 建立连接,连接建立后,Talker 可以发布消息,且发布的消息会被 Listener 订阅

    2.1.3. C++ 实现

    发布方

    #include "ros/ros.h"#include "std_msgs/String.h" //普通文本类型的消息#include <sstream>int main(int argc, char *argv[]) {setlocale(LC_ALL, "");ros::init(argc,argv,"talker");ros::NodeHandle nh;//泛型: 发布的消息类型//参数1: 要发布到的话题//参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);std_msgs::String msg;std::string msg_front = "Hello!";int count = 0;//逻辑(一秒10次)ros::Rate r(10);while (ros::ok()) {std::stringstream ss;ss << msg_front << count;msg.data = ss.str();pub.publish(msg);ROS_INFO("发送消息: %s", msg.data.c_str());//根据前面制定的发送频率自动休眠 休眠时间 = 1/频率;r.sleep();count++;//循环结束前,让 count 自增}return 0;}

    订阅方

    #include "ros/ros.h"#include "std_msgs/String.h"void doMsg(const std_msgs::String::ConstPtr& msg_p) {ROS_INFO("接受信息: %s",msg_p->data.c_str());}int main(int argc, char *argv[]) {setlocale(LC_ALL,"");ros::init(argc,argv,"listener");ros::NodeHandle nh;ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter", 10, doMsg);while (ros::ok()) {//循环调用回调函数ros::spinOnce();r.sleep();}return 0;}

    接受信息一般使用 ros::spin() 或者 ros::spinOnce()。两者区别在于,ros::spin() 在调用后不会再返回,而 ros::spinOnce() 后者在调用后还可以继续执行之后的程序。因此,ros::spin() 函数一般不会出现在循环中,后面除了 return 0; 外不能有其他语句

    CMakeLists.txt

    add_executable(Hello_pubsrc/Hello_pub.cpp)add_executable(Hello_subsrc/Hello_sub.cpp)target_link_libraries(Hello_pub${catkin_LIBRARIES})target_link_libraries(Hello_sub${catkin_LIBRARIES})

    2.1.4. Python 实现

    发布方

    #! /usr/bin/env pythonimport rospyfrom std_msgs.msg import Stringif __name__ == "__main__":rospy.init_node("talker_p")pub = rospy.Publisher("chatter", String, queue_size=10)msg = String()msg_front = "hello"count = 0rate = rospy.Rate(10)while not rospy.is_shutdown():msg.data = msg_front + str(count)pub.publish(msg)rate.sleep()rospy.loginfo("发送信息:%s",msg.data)count += 1

    订阅方

    #! /usr/bin/env pythonimport rospyfrom std_msgs.msg import Stringdef doMsg(msg):rospy.loginfo("接受信息:%s", msg.data)if __name__ == "__main__":rospy.init_node("listener_p")sub = rospy.Subscriber("chatter", String, doMsg, queue_size=10)rospy.spin()

    添加可执行权限

    chmod +x *.py

    CMakeLists.txt

    catkin_install_python(PROGRAMSscripts/talker_p.pyscripts/listener_p.pyDESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})

    2.1.5. spinOnce 和 spin

    spin 是忙等待,一旦接受缓冲区有数据就会立即处理,这样处理信息的速度较快,但是忙等待会占用一定硬件资源。而 spinOnce 处理接受缓存区的速度由订阅节点循环的频率决定,并且每次都直接处理缓存区里的所有数据。

    注意,在 Python 实现中,或者说在 rospy 中并没有 spinOnce,这是因为 rospy 中 spin 的作用与 roscpp 不同,这里的 spin 只是用来确保不让程序在节点关闭前退出,而不想 roscpp 中的那样会影响处理信息的回调函数,所以 rospy 中只需要 spin 就可以了。

    但是这样就会产生一个问题,如果想实现在循环中处理接受信息调用回调函数,并执行发布消息的任务,则可以通过新开一个线程来解决循环问题,但是如果不需要循环,则可以直接在订阅的回调函数中发布信息

    #!/usr/bin/env python#-*- coding: utf-8 -*-import rospyimport thread, timefrom geometry_msgs.msg import Twistfrom std_srvs.srv import Trigger, TriggerResponsepubCommand = Falseturtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)def command_thread():while True:if pubCommand:vel_msg = Twist()vel_msg.linear.x = 0.5vel_msg.angular.z = 0.2turtle_vel_pub.publish(vel_msg)time.sleep(0.1)def commandCallback(req):global pubCommandpubCommand = bool(1 - pubCommand)return TriggerResponse(1, "Change turtle command state!")def turtle_command_server():rospy.init_node('turtle_command_server')s = rospy.Service('/turtle_command', Trigger, commandCallback)thread.start_new_thread(command_thread, ())rospy.spin()if __name__ == "__main__":turtle_command_server()

    2.1.6. 话题同步

    注意,只有多个主题都有数据的时候才可以触发回调函数。如果其中一个主题的发布节点崩溃了,则整个回调函数永远无法触发回调;并且,当多个主题频率一致的时候也无法保证回调函数的频率等于订阅主题的频率,一般会很低。实际测试订阅两个需要同步的主题频率为 50Hz 和 50Hz,而回调函数的频率只有 24Hz 左右。

    C++

    #include <ros/ros.h>#include "sensor_msgs/LaserScan.h"#include "geometry_msgs/PoseWithCovarianceStamped.h"#include <message_filters/subscriber.h>#include <message_filters/synchronizer.h>#include <message_filters/sync_policies/approximate_time.h>#include <message_filters/time_synchronizer.h>#include <boost/thread/thread.hpp>#include <iostream>void multi_callback(const sensor_msgs::LaserScanConstPtr& scan, const geometry_msgs::PoseWithCovarianceStampedConstPtr& pose){std::cout << "同步完成!" << std::endl;}int main(int argc, char** argv){ros::init(argc, argv, "multi_receiver");ros::NodeHandle n;message_filters::Subscriber<sensor_msgs::LaserScan> subscriber_laser(n,"scan",1000,ros::TransportHints().tcpNoDelay());message_filters::Subscriber<geometry_msgs::PoseWithCovarianceStamped> subscriber_pose(n,"car_pose",1000,ros::TransportHints().tcpNoDelay());typedef message_filters::sync_policies::ApproximateTime<sensor_msgs::LaserScan, geometry_msgs::PoseWithCovarianceStamped> syncPolicy;//message_filters::TimeSynchronizer<sensor_msgs::LaserScan,geometry_msgs::PoseWithCovarianceStamped> sync(subscriber_laser, subscriber_pose, 10);message_filters::Synchronizer<syncPolicy> sync(syncPolicy(10), subscriber_laser, subscriber_pose); sync.registerCallback(boost::bind(&multi_callback, _1, _2));std::cout << "hahah" << std::endl;ros::spin();return 0;}

    Python

    #!/usr/bin/env python# coding=UTF-8import rospy,math,randomimport numpy as npimport message_filtersfrom std_msgs.msg import Stringfrom sensor_msgs.msg import LaserScanfrom laser_geometry import LaserProjectionfrom geometry_msgs.msg import PoseWithCovarianceStampeddef multi_callback(Subcriber_laser,Subcriber_pose):print "同步完成!"if __name__ == '__main__':rospy.init_node('multi_receive',anonymous=True)subcriber_laser = message_filters.Subscriber('scan', LaserScan, queue_size=1)subcriber_pose = message_filters.Subscriber('car_pose', PoseWithCovarianceStamped, queue_size=1)sync = message_filters.ApproximateTimeSynchronizer([subcriber_laser, subcriber_pose],10,0.1,allow_headerless=True)sync.registerCallback(multi_callback)rospy.spin()

    2.1.7. 自定义 msg

    在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,如激光雷达的信息时,可以使用自定义的消息类型

    msgs 只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:

    int8, int16, int32, int64 (或者无符号类型: uint*)float32, float64stringtime, durationother msg filesvariable-length array[] and fixed-length array[C]

    ROS 中还有一种特殊类型:Header,标头包含时间戳和 ROS 中常用的坐标帧信息

    首先在与功能包 src 同级的 msg 目录下编辑自定义消息文件 Person.msg

    string nameuint16 agefloat64 height

    然后编辑配置文件

    package.xml

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

    CMakeLists.txt(也可以在 msg 文件夹内再定义一个 CMake 文件,用来统一加入消息文件)

    find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgs # 需要加入 message_generation,必须有 std_msgsmessage_generation)# 配置 msg 源文件add_message_files(FILESPerson.msg)# 生成消息时依赖于 std_msgsgenerate_messages(DEPENDENCIESstd_msgs)#执行时依赖catkin_package(CATKIN_DEPENDS roscpp rospy std_msgs message_runtime)

    最后进行编译,会生成供 C++ 和 Python 使用的中间文件,此时调用这些中间文件就能发送自定义消息

    • C++:.../工作空间/devel/include/包名/xxx.h。调用时执行#include "功能包包名/xxx.h"
    • Python:.../工作空间/devel/lib/python3/dist-packages/包名/msg。调用时执行from 功能包包名.msg import xxx

    2.1.8. 自定义变长 msg

    ---------------------------------------imgData.msgint32 upperLeftint32 lowerRightstring colorstring cameraID---------------------------------------imgDataArray.msgimgData[] images---------------------------------------demo_pub.cppros::Publisher pub = n.advertise<my_pkg::imgDataArray>("test", 1000);my_pkg::imgData data;my_pkg::imgDataArray msg;data.upperleft=0; data.lowerRight=100; data.color="red"; data.cameraID="one";msg.images.push_back(data);data.upperleft=1; data.lowerRight=101; data.color="blue"; data.cameraID="two";msg.images.push_back(data);pub.publish(msg);---------------------------------------demo_sub.cppvoid subCB(const my_pkg::imgDataArray::ConstPtr& msg) {for (int i=0; i<msg->images.size; ++i) {const my_pkg::imgData &data = msg->images[i];ROS_INFO_STREAM("UL: " << data.upperleft << "UR: " << data.upperright << "color: " << data.color << "ID: " << data.cameraID);}}int main(int argc, char **argv) {...ros::Subscriber sub = n.subscribe("test", 1000, subCB);ros::spin();}

    2.2. 服务通信

    2.2.1. 简介

    • ROS 中的同步通信方式
    • 通过 TCP/IP 通信
    • Node 之间通过 request-reply 机制通信
    • 能够进行多对一通信
    • 常用于偶尔调用的功能/具体的任务:开关传感器、拍照、逆接计算
    • srv:
      • service 通信的数据格式
      • 定义在 .srv 文件中

    2.2.2. 理论模型

    2.2.3. 话题和服务

    在一个简单字符串通信任务中,使用话题时平均延迟为 0.7ms,但是使用服务时平均延迟为 3ms。考虑到服务通信中 client 与 server 之间维持的是一个持久连接,那么服务通信应该要比话题通信快。但实际上,服务通信中 client 发出请求后需要先等 server 唤醒,而 server 唤醒时间就会占整体时间的 80%,即实际上因为服务通信是通过持久性连接发送请求的,所以服务通信的请求发送时间要比话题通信快,但是服务通信中服务唤醒的时间要更大,因此整体上服务通信延迟要比话题通信大

    2.2.4. 自定义 srv

    不同于话题通信可以只发送某个字段作为消息,服务通信包含请求和响应两个过程,因此使用服务通信前必须先定义服务类型

    首先在与功能包 src 同级的 srv 目录下编辑自定义消息文件。其中,请求和响应使用---分割

    # 客户端请求时发送的两个数字int32 num1int32 num2---# 服务器响应发送的数据int32 sum

    编辑 package.xml 文件,添加编译依赖与执行依赖

    <build_depend>message_generation</build_depend><exec_depend>message_runtime</exec_depend><!-- exce_depend 以前对应的是 run_depend 现在非法-->

    编辑 CMakeLists.txt 相关配置

    find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgsmessage_generation)# 需要加入 message_generation,必须有 std_msgsadd_service_files(FILESAddInts.srv)generate_messages(DEPENDENCIESstd_msgs)

    最后进行编译,会生成供 C++ 和 Python 使用的中间文件,此时调用这些中间文件就能发送自定义消息

    • C++:.../工作空间/devel/include/包名/xxx.h。调用时执行#include "功能包包名/xxx.h"
    • Python:.../工作空间/devel/lib/python3/dist-packages/包名/srv。调用时执行from 功能包包名.srv import xxx

    2.2.5. C++ 实现

    服务端

    #include "ros/ros.h"#include "demo03_server_client/AddInts.h"// bool 返回值由于标志是否处理成功bool doReq(demo03_server_client::AddInts::Request& req,demo03_server_client::AddInts::Response& resp) {int num1 = req.num1;int num2 = req.num2;ROS_INFO("服务器接收到的请求数据为:num1 = %d, num2 = %d",num1, num2);//逻辑处理if (num1 < 0 || num2 < 0) {ROS_ERROR("提交的数据异常:数据不可以为负数");return false;}//如果没有异常,那么相加并将结果赋值给 respresp.sum = num1 + num2;return true;}int main(int argc, char *argv[]) {setlocale(LC_ALL,"");ros::init(argc,argv,"AddInts_Server");ros::NodeHandle nh;ros::ServiceServer server = nh.advertiseService("AddInts",doReq);ROS_INFO("服务已经启动....");ros::spin();return 0;}

    客户端

    #include "ros/ros.h"#include "demo03_server_client/AddInts.h"int main(int argc, char *argv[]) {setlocale(LC_ALL,"");if (argc != 3) {ROS_ERROR("请提交两个整数");return 1;}ros::init(argc,argv,"AddInts_Client");ros::NodeHandle nh;ros::ServiceClient client = nh.serviceClient<demo03_server_client::AddInts>("AddInts");//方式1ros::service::waitForService("AddInts");//方式2// client.waitForExistence();// 组织请求数据demo03_server_client::AddInts ai;ai.request.num1 = atoi(argv[1]);ai.request.num2 = atoi(argv[2]);// 发送请求,返回 bool 值,标记是否成功bool flag = client.call(ai);// 处理响应if (flag) {ROS_INFO("请求正常处理,响应结果:%d",ai.response.sum);}else {ROS_ERROR("请求处理失败....");return 1;}return 0;}

    在客户端发送请求前添加: client.waitForExistence(); 或: ros::service::waitForService("AddInts");。这是一个阻塞式函数,只有服务启动成功后才会继续执行。client.call() 也是一个阻塞式函数

    2.2.6. Python 实现

    服务端

    #! /usr/bin/env pythonimport rospyfrom demo03_server_client.srv import AddInts,AddIntsRequest,AddIntsResponsedef doReq(req):sum = req.num1 + req.num2rospy.loginfo("提交的数据:num1 = %d, num2 = %d, sum = %d",req.num1, req.num2, sum)resp = AddIntsResponse(sum)return respif __name__ == "__main__":rospy.init_node("addints_server_p")# 创建服务对象server = rospy.Service("AddInts",AddInts,doReq)rospy.spin()

    客户端

    #! /usr/bin/env pythonimport rospyfrom demo03_server_client.srv import *import sysif __name__ == "__main__":if len(sys.argv) != 3:rospy.logerr("请正确提交参数")sys.exit(1)rospy.init_node("AddInts_Client_p")# 创建请求对象client = rospy.ServiceProxy("AddInts",AddInts)# 方式1:# rospy.wait_for_service("AddInts")# 方式2client.wait_for_service()# 发送请求,接收并处理响应# 方式1# resp = client(3,4)# 方式2# resp = client(AddIntsRequest(1,5))# 方式3req = AddIntsRequest()# req.num1 = 100# req.num2 = 200 req.num1 = int(sys.argv[1])req.num2 = int(sys.argv[2]) resp = client.call(req)rospy.loginfo("响应结果:%d",resp.sum)

    2.3. 参数服务器

    rosparam:https://blog.csdn.net/u014695839/article/details/78348600

    3. 运行

    3.1. launch

    3.1.1. 运行

    一个程序中可能需要启动多个节点,比如:ROS 内置的小乌龟案例,如果要控制乌龟运动,要启动多个窗口,分别启动 roscore、乌龟界面节点、键盘控制节点。如果每次都调用 rosrun 逐一启动,显然效率低下,因此官方给出的优化策略是使用 launch 文件,可以一次性启动多个 ROS 节点。

  • 进入 ros 包的 launch 目录编辑 launch 文件:
  • <launch><node pkg="helloworld" type="demo_hello" name="hello" output="screen" /><node pkg="turtlesim" type="turtlesim_node" name="t1"/><node pkg="turtlesim" type="turtle_teleop_key" name="key1" /></launch>
  • 运行 launch 文件(此时 roslaunch 命令会在内部自动执行 roscore 命令)
  • roslaunch 包名 launch文件名

    运行结果:一次性启动了多个节点

    3.1.2. 标签

    3.1.2.1. launch

    launch 标签规定了一片区域,所有的 launch 文件都由 <launch> 开头,由 </launch> 结尾,所有的描述标签都要写在 <launch> 和 </launch>之间

    <launch>...</launch>

    3.1.2.2. node

    node 标签里包括了 ROS 中节点的名称属性 name、该节点所在的包名 pkg、节点的类型 type 以及日志输出位置属性 output

    属性属性作用
    name=“NODE_NAME”为节点指派名称,这将会覆盖掉 ros::init() 定义的 node_name
    pkg=“PACKAGE_NAME”节点所在的包名
    type=“FILE_NAME”执行文件的名称。如果是用 Python 编写则为 xxx.py,如果是 C++ 则为生成的可执行文件名
    output=“screen”终端输出转储在当前的控制台上,而不是在日志文件中。在节点内有日志输出时该属性才会起作用
    respawn=“true”当 roslaunch 启动完所有该启动的节点之后,会监测每一个节点,保证它们正常的运行状态。对于任意节点,当它终止时,roslaunch 会将该节点重启
    required=“true”当被此属性标记的节点终止时,roslaunch 会将其他的节点一并终止。注意此属性不可以与 respawn=“true” 一起描述同一个节点
    launch-prefix = “command-prefix”相当于在执行启动命令时加上一段命令前缀
    ns = “NAME_SPACE”在自定义的命名空间里运行节点

    3.1.2.3. include

    该标签可以导入另一个 roslaunch XML 文件到当前文件

    属性属性作用
    file ="$(find pkg-name)/path/filename.xml"指明想要包含进来的文件
    ns=“NAME_SPACE”相对NAME_SPACE命名空间导入文件
    <include file="$(find demo)/launch/demo.launch" ns="demo_namespace">...</include>

    3.1.2.4. remap

    ROS 支持 topic 的重映射,remap 标签里包含一个 original-name 和一个 new-name,及原名称和新名称

    比如现在拿到一个节点,这个节点订阅了 “/chatter” 话题,然而自己写的节点只能发布到 “/demo/chatter” 话题,由于这两个 topic 的消息类型是一致的,想让这两个节点进行通讯,那么可以在 launch 文件中这样写:

    <remap from="chatter" to="demo/chatter"/>

    这样就可以直接把 /chatter 重映射到 /demo/chatter,让两个节点进行通讯

    如果 remap 标签写在与 node 元素的同一级,而且在 launch 元素内的最顶层。 那么这个重映射将会作用于 launch 文件中所有的节点

    3.1.2.5. param

    param 标签的作用相当于命令行中的 rosparam set。比如现在在参数服务器中添加一个名为 demo_param,值为 666 的参数

    <param name="demo_param" type="int" value="666"/>

    3.1.2.6. rosparam

    rosparam 标签允许从 YAML 文件中一次性导入大量参数

    <rosparam command="load" file="$(find pkg-name)/path/name.yaml"/>

    3.1.2.7. arg

    arg 标签用来在 launch 文件中定义参数,arg 和 param 在 ROS 里有根本性的区别,就像局部变量和全局变量的区别一样。arg 不储存在参数服务器中,不能提供给节点使用,只能在 launch 文件中使用。param 则是储存在参数服务器中,可以被节点使用。

    <arg name="demo"/>

    像上面这样,就简单地声明了一个参数,名叫 demo,但是声明不等于定义,在赋值之后参数才能够发挥作用。

    <arg name="demo" value="666"/><arg name="demo" default="666"/> roslaunch demo demo.launch demo:=6666

    arg 还有更加高级,也更加灵活的用法:$(arg arg_name)
    当 $(arg arg_name) 出现在 launch 文件任意位置时,将会自动替代为所给参数的值

    3.1.2.8. group

    group 标签可以将若干个节点同时划分进某个工作空间

    <group ns="demo_1"><node name="demo_1" pkg="demo_1" type="demo_pub_1" output="screen"/><node name="demo_1" pkg="demo_1" type="demo_sub_1" output="screen"/></group><group ns="demo_2"><node name="demo_2" pkg="demo_2" type="demo_pub_2" output="screen"/><node name="demo_2" pkg="demo_2" type="demo_sub_2" output="screen"/></group>

    group 标签还可以做到对 node 的批量管理。比如可以同时终止在同一个 group 中的节点

    <group if="1-or-0">………………</group><group unless="1-or-0">………………</group>

    第一种情况,当 if 属性的值为 0 的时候将会忽略掉 <group></group> 之间的标签

    第二种恰好相反,当 if 属性的值为 1 的时候将会忽略掉 <group></group> 之间的标签

    但是通常不会直接用 1 或 0 来定义 if 标签。因为这样不够灵活。
    通常会搭配 $(arg arg_name) 来使用 demo.launch 文件

    <launch><include file="include.launch"><arg name="demo_arg" value="1"/></include></launch>

    include.launch文件

    <launch><arg name="demo_arg"/><group if="$(demo_arg)"><node name="demo" pkg="demo" type="demo_pub" output="screen"/><node name="demo" pkg="demo" type="demo_sub" output="screen"/></group></launch>

    https://zhuanlan.zhihu.com/p/157526418

    需要做网站?需要网络推广?欢迎咨询客户经理 13272073477