日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ROS基础教程

發(fā)布時間:2024/8/1 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ROS基础教程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 1. 簡介
    • 1.1. 系統(tǒng)架構(gòu)
    • 1.2. 文件系統(tǒng)
    • 1.3. 計算圖
    • 1.4. Source
    • 1.5. helloworld
      • 1.5.1. C++ 實現(xiàn)
      • 1.5.2. Python 實現(xiàn)
  • 2. 通信
    • 2.1. 話題通信
      • 2.1.1. 簡介
      • 2.1.2. 理論模型
      • 2.1.3. C++ 實現(xiàn)
      • 2.1.4. Python 實現(xiàn)
      • 2.1.5. spinOnce 和 spin
      • 2.1.6. 話題同步
      • 2.1.7. 自定義 msg
      • 2.1.8. 自定義變長 msg
    • 2.2. 服務(wù)通信
      • 2.2.1. 簡介
      • 2.2.2. 理論模型
      • 2.2.3. 話題和服務(wù)
      • 2.2.4. 自定義 srv
      • 2.2.5. C++ 實現(xiàn)
      • 2.2.6. Python 實現(xiàn)
    • 2.3. 參數(shù)服務(wù)器
  • 3. 運行
    • 3.1. launch
      • 3.1.1. 運行
      • 3.1.2. 標(biāo)簽
        • 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 (機(jī)器人操作系統(tǒng))

  • ROS 是適用于機(jī)器人的開源元操作系統(tǒng)
  • ROS 集成了大量的工具,庫,協(xié)議,提供類似 OS 所提供的功能,簡化對機(jī)器人的控制
  • ROS 還提供了用于在多臺計算機(jī)上獲取,構(gòu)建,編寫和運行代碼的工具和庫,ROS 在某些方面類似于“機(jī)器人框架”
  • ROS 設(shè)計者將 ROS 表述為 “ROS = Plumbing + Tools + Capabilities + Ecosystem”,即 ROS 是通訊機(jī)制、工具軟件包、機(jī)器人高層技能以及機(jī)器人生態(tài)系統(tǒng)的集合體

1.1. 系統(tǒng)架構(gòu)

ROS 的系統(tǒng)架構(gòu)可分為三層

  • OS 層,也即經(jīng)典意義的操作系統(tǒng)。ROS 只是元操作系統(tǒng),需要依托真正意義的操作系統(tǒng),目前兼容性最好的是 Linux 的 Ubuntu,Mac、Windows 也支持 ROS 的較新版本
  • 中間層。是 ROS 封裝的關(guān)于機(jī)器人開發(fā)的中間件,比如:
    • 基于 TCP/UDP 繼續(xù)封裝的 TCPROS/UDPROS 通信系統(tǒng)
    • 用于進(jìn)程間通信 Nodelet,為數(shù)據(jù)的實時性傳輸提供支持
    • 大量的機(jī)器人開發(fā)實現(xiàn)庫,如:數(shù)據(jù)類型定義、坐標(biāo)變換、運動控制…
  • 應(yīng)用層。功能包,以及功能包內(nèi)的節(jié)點,比如: master、turtlesim的控制與運動節(jié)點。
  • 1.2. 文件系統(tǒng)

    WorkSpace --- 自定義的工作空間|--- build:編譯空間,用于存放CMake和catkin的緩存信息、配置信息和其他中間文件。|--- devel:開發(fā)空間,用于存放編譯后生成的目標(biāo)文件,包括頭文件、動態(tài)&靜態(tài)鏈接庫、可執(zhí)行文件等。|--- src: 源碼|-- CMakeLists.txt: 編譯的基本配置|-- package:功能包(ROS 基本單元)包含多個節(jié)點、庫與配置文件,包名所有字母小寫,只能由字母、數(shù)字與下劃線組成|-- CMakeLists.txt 配置編譯規(guī)則,比如源文件、依賴項、目標(biāo)文件|-- package.xml 包信息,比如:包名、版本、作者、依賴項...(以前版本是 manifest.xml)|-- scripts 存儲 python 文件|-- src 存儲 C++ 源文件|-- include 頭文件|-- msg 消息通信格式文件|-- srv 服務(wù)通信格式文件|-- action 動作格式文件|-- launch 可一次性運行多個節(jié)點 |-- config 配置信息|-- package

    ROS 的文件系統(tǒng)本質(zhì)上都還是操作系統(tǒng)文件,因此可以使用 Linux 命令來操作這些文件。不過,在 ROS 中為了更好的用戶體驗,ROS 專門提供了一些類似于 Linux 的命令,這些命令較之于 Linux 原生命令,更為簡潔、高效。

  • # 創(chuàng)建新的ROS功能包 catkin_create_pkg 自定義包名 依賴包# 安裝 ROS 功能包 sudo apt install xxx
  • # 刪除某個功能包 sudo apt purge xxx
  • # 列出所有功能包 rospack list# 查找某個功能包是否存在,如果存在返回安裝路徑 rospack find 包名# 進(jìn)入某個功能包 roscd 包名# 列出某個包下的文件 rosls 包名# 搜索某個功能包 apt search xxx
  • # 修改功能包文件(需要安裝 vim) rosed 包名 文件名
  • 執(zhí)行
  • # 必須運行 roscore 才能使 ROS 節(jié)點進(jìn)行通信,他是 ROS 的系統(tǒng)先決條件節(jié)點和程序的集合。運行 roscore 會運行 ros master、ros 參數(shù)服務(wù)器、rosout 日志節(jié)點 roscore # 或(指定端口號) roscore -p xxxx# 運行指定的ROS節(jié)點 rosrun 包名 可執(zhí)行文件名# 執(zhí)行某個包下的 launch 文件 roslaunch 包名 launch文件名

    1.3. 計算圖

    ROS 文件結(jié)構(gòu)是磁盤上 ROS 程序的存儲結(jié)構(gòu),是靜態(tài)的,而 ros 程序運行之后,不同的節(jié)點之間是錯綜復(fù)雜的,ROS 中提供了一個實用的工具:rqt_graph

    rqt_graph 能夠創(chuàng)建一個顯示當(dāng)前系統(tǒng)運行情況的動態(tài)圖形。ROS 分布式系統(tǒng)中不同進(jìn)程需要進(jìn)行數(shù)據(jù)交互,計算圖可以以點對點的網(wǎng)絡(luò)形式表現(xiàn)數(shù)據(jù)交互過程。rqt_graph 是 rqt 程序包中的一部分

    運行 rat_graph 可通過下面命令執(zhí)行:

    rosrun rqt_graph rqt_graph # 或者直接輸入 rqt_graph

    1.4. Source

    • 下面命令是使得能夠在任意終端都能使用 ROS
    echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc
    • 下面命令是使得能夠在任意終端都能使用當(dāng)前工作空間
    echo "source ~/工作空間/devel/setup.bash" >> ~/.bashrc source ~/.bashrc

    1.5. helloworld

    ROS 可是使用 C++ 或者 Python,但是具體選擇哪種語言需要視需求而定:C++ 運行效率高但是編碼效率低,而 Python 則反之?;诙呋パa的特點,ROS 設(shè)計者分別設(shè)計了 roscpp 與 rospy 庫,前者旨在成為 ROS 的高性能庫,而后者則一般用于對性能無要求的場景,旨在提高開發(fā)效率

    1.5.1. C++ 實現(xiàn)

  • 進(jìn)入 ros 包的 src 目錄編輯源文件
  • #include "ros/ros.h"int main(int argc, char *argv[]) {//執(zhí)行 ros 節(jié)點初始化,輸入所有參數(shù)并對節(jié)點命名。注意,節(jié)點名稱是一個標(biāo)識符,需要保證在 ROS 網(wǎng)絡(luò)拓?fù)渲形ㄒ籸os::init(argc,argv,"hello");//創(chuàng)建 ros 節(jié)點句柄。注意,因為一個進(jìn)程上運行一個節(jié)點,所以句柄是自動綁定到當(dāng)前的進(jìn)程,即節(jié)點上的ros::NodeHandle n;//控制臺輸出 hello worldROS_INFO("hello world!");return 0; }
  • 編輯 ros 包下的 Cmakelist.txt 文件
  • add_executable(可執(zhí)行文件名src/步驟3的源文件名.cpp ) target_link_libraries(可執(zhí)行文件名${catkin_LIBRARIES} )
  • 返回頂層工作空間目錄并編譯(生成 build 和 devel):catkin_make

  • 執(zhí)行

  • 在一個終端輸入命令啟動 ROS 核心:roscore
  • 再在另一個終端輸入命令刷新環(huán)境變量并運行節(jié)點:
  • source ./devel/setup.bash rosrun 包名 可執(zhí)行文件名

    命令行輸出: HelloWorld!

    1.5.2. Python 實現(xiàn)

  • 進(jìn)入 ros 包的 scripts 目錄編輯 python 文件(C++是進(jìn)入同級的 src 目錄)
  • #! /usr/bin/env pythonimport rospyif __name__ == "__main__":rospy.init_node("Hello")rospy.loginfo("HelloWorld!")
  • 為 python 文件添加可執(zhí)行權(quán)限
  • chmod +x 文件名.py
  • 編輯 ros 包下的 CamkeList.txt 文件
  • catkin_install_python(PROGRAMS scripts/文件名.pyDESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} )
  • 返回頂層工作空間目錄并編譯:catkin_make

  • 執(zhí)行

  • 在一個終端輸入命令啟動 ROS 核心:roscore
  • 再在另一個終端輸入命令刷新環(huán)境變量并運行節(jié)點:
  • source ./devel/setup.bash rosrun 包名 文件名.py

    輸出結(jié)果:HelloWorld!

    另外,因為 Python 文件運行不需要編譯,所以在編寫完腳本后,可以直接運行節(jié)點

    rosrun 包名 文件名.py

    或者直接運行 Python 文件

    python 文件名.py

    2. 通信

    機(jī)器人是一種高度復(fù)雜的系統(tǒng)性實現(xiàn),在機(jī)器人上可能集成各種傳感器(雷達(dá)、攝像頭、GPS…)以及運動控制實現(xiàn),為了解耦合,在 ROS 中每一個功能點都是一個單獨的進(jìn)程,每一個進(jìn)程都是獨立運行的。更確切的講,ROS 是進(jìn)程(也稱為 Nodes)的分布式框架。 因為這些進(jìn)程甚至還可分布于不同主機(jī),不同主機(jī)協(xié)同工作,從而分散計算壓力。而為了實現(xiàn)不同進(jìn)程之間的通信,ROS 提供了三種方式:

  • 話題通信(發(fā)布訂閱模式)
  • 服務(wù)通信(請求響應(yīng)模式)
  • 參數(shù)服務(wù)器(參數(shù)共享模式)
  • 2.1. 話題通信

    2.1.1. 簡介

    • ROS 中使用最多的通信方式
    • ROS 中的異步通信方式
    • 通過 TCP/IP 通信
    • Node 之間通過 publish-subscribe 機(jī)制通信
    • 能夠進(jìn)行多對多通信
    • 常用于連續(xù)、高頻的數(shù)據(jù)發(fā)布:激光雷達(dá)、里程計發(fā)布數(shù)據(jù)
    • Message:
      • topic 內(nèi)容的數(shù)據(jù)格式
      • 定義在 .msg 文件中
      • 可以看作是一個結(jié)構(gòu)體或者類

    2.1.2. 理論模型

    話題通信理論模型中涉及到三個角色。其中,ROS Master 負(fù)責(zé)保管 Talker 和 Listener 注冊的信息,并匹配話題相同的 Talker 與 Listener,幫助 Talker 與 Listener 建立連接,連接建立后,Talker 可以發(fā)布消息,且發(fā)布的消息會被 Listener 訂閱

    2.1.3. C++ 實現(xiàn)

    發(fā)布方

    #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;//泛型: 發(fā)布的消息類型//參數(shù)1: 要發(fā)布到的話題//參數(shù)2: 隊列中最大保存的消息數(shù),超出此閥值時,先進(jìn)的先銷毀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("發(fā)送消息: %s", msg.data.c_str());//根據(jù)前面制定的發(fā)送頻率自動休眠 休眠時間 = 1/頻率;r.sleep();count++;//循環(huán)結(jié)束前,讓 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()) {//循環(huán)調(diào)用回調(diào)函數(shù)ros::spinOnce();r.sleep();}return 0; }

    接受信息一般使用 ros::spin() 或者 ros::spinOnce()。兩者區(qū)別在于,ros::spin() 在調(diào)用后不會再返回,而 ros::spinOnce() 后者在調(diào)用后還可以繼續(xù)執(zhí)行之后的程序。因此,ros::spin() 函數(shù)一般不會出現(xiàn)在循環(huán)中,后面除了 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 實現(xiàn)

    發(fā)布方

    #! /usr/bin/env python import rospy from 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("發(fā)送信息:%s",msg.data)count += 1

    訂閱方

    #! /usr/bin/env python import rospy from 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()

    添加可執(zhí)行權(quán)限

    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 是忙等待,一旦接受緩沖區(qū)有數(shù)據(jù)就會立即處理,這樣處理信息的速度較快,但是忙等待會占用一定硬件資源。而 spinOnce 處理接受緩存區(qū)的速度由訂閱節(jié)點循環(huán)的頻率決定,并且每次都直接處理緩存區(qū)里的所有數(shù)據(jù)。

    注意,在 Python 實現(xiàn)中,或者說在 rospy 中并沒有 spinOnce,這是因為 rospy 中 spin 的作用與 roscpp 不同,這里的 spin 只是用來確保不讓程序在節(jié)點關(guān)閉前退出,而不想 roscpp 中的那樣會影響處理信息的回調(diào)函數(shù),所以 rospy 中只需要 spin 就可以了。

    但是這樣就會產(chǎn)生一個問題,如果想實現(xiàn)在循環(huán)中處理接受信息調(diào)用回調(diào)函數(shù),并執(zhí)行發(fā)布消息的任務(wù),則可以通過新開一個線程來解決循環(huán)問題,但是如果不需要循環(huán),則可以直接在訂閱的回調(diào)函數(shù)中發(fā)布信息

    #!/usr/bin/env python #-*- coding: utf-8 -*-import rospy import thread, time from geometry_msgs.msg import Twist from std_srvs.srv import Trigger, TriggerResponsepubCommand = False turtle_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. 話題同步

    注意,只有多個主題都有數(shù)據(jù)的時候才可以觸發(fā)回調(diào)函數(shù)。如果其中一個主題的發(fā)布節(jié)點崩潰了,則整個回調(diào)函數(shù)永遠(yuǎn)無法觸發(fā)回調(diào);并且,當(dāng)多個主題頻率一致的時候也無法保證回調(diào)函數(shù)的頻率等于訂閱主題的頻率,一般會很低。實際測試訂閱兩個需要同步的主題頻率為 50Hz 和 50Hz,而回調(diào)函數(shù)的頻率只有 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-8 import rospy,math,random import numpy as np import message_filters from std_msgs.msg import String from sensor_msgs.msg import LaserScan from laser_geometry import LaserProjection from 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 通信協(xié)議中,數(shù)據(jù)載體是一個較為重要組成部分,ROS 中通過 std_msgs 封裝了一些原生的數(shù)據(jù)類型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,這些數(shù)據(jù)一般只包含一個 data 字段,結(jié)構(gòu)的單一意味著功能上的局限性,當(dāng)傳輸一些復(fù)雜的數(shù)據(jù),如激光雷達(dá)的信息時,可以使用自定義的消息類型

    msgs 只是簡單的文本文件,每行具有字段類型和字段名稱,可以使用的字段類型有:

    int8, int16, int32, int64 (或者無符號類型: uint*) float32, float64 string time, duration other msg files variable-length array[] and fixed-length array[C]

    ROS 中還有一種特殊類型:Header,標(biāo)頭包含時間戳和 ROS 中常用的坐標(biāo)幀信息

    首先在與功能包 src 同級的 msg 目錄下編輯自定義消息文件 Person.msg

    string name uint16 age float64 height

    然后編輯配置文件

    package.xml

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

    CMakeLists.txt(也可以在 msg 文件夾內(nèi)再定義一個 CMake 文件,用來統(tǒng)一加入消息文件)

    find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgs # 需要加入 message_generation,必須有 std_msgsmessage_generation )# 配置 msg 源文件 add_message_files(FILESPerson.msg )# 生成消息時依賴于 std_msgs generate_messages(DEPENDENCIESstd_msgs )#執(zhí)行時依賴 catkin_package(CATKIN_DEPENDS roscpp rospy std_msgs message_runtime )

    最后進(jìn)行編譯,會生成供 C++ 和 Python 使用的中間文件,此時調(diào)用這些中間文件就能發(fā)送自定義消息

    • C++:.../工作空間/devel/include/包名/xxx.h。調(diào)用時執(zhí)行#include "功能包包名/xxx.h"
    • Python:.../工作空間/devel/lib/python3/dist-packages/包名/msg。調(diào)用時執(zhí)行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. 服務(wù)通信

    2.2.1. 簡介

    • ROS 中的同步通信方式
    • 通過 TCP/IP 通信
    • Node 之間通過 request-reply 機(jī)制通信
    • 能夠進(jìn)行多對一通信
    • 常用于偶爾調(diào)用的功能/具體的任務(wù):開關(guān)傳感器、拍照、逆接計算
    • srv:
      • service 通信的數(shù)據(jù)格式
      • 定義在 .srv 文件中

    2.2.2. 理論模型

    2.2.3. 話題和服務(wù)

    在一個簡單字符串通信任務(wù)中,使用話題時平均延遲為 0.7ms,但是使用服務(wù)時平均延遲為 3ms。考慮到服務(wù)通信中 client 與 server 之間維持的是一個持久連接,那么服務(wù)通信應(yīng)該要比話題通信快。但實際上,服務(wù)通信中 client 發(fā)出請求后需要先等 server 喚醒,而 server 喚醒時間就會占整體時間的 80%,即實際上因為服務(wù)通信是通過持久性連接發(fā)送請求的,所以服務(wù)通信的請求發(fā)送時間要比話題通信快,但是服務(wù)通信中服務(wù)喚醒的時間要更大,因此整體上服務(wù)通信延遲要比話題通信大

    2.2.4. 自定義 srv

    不同于話題通信可以只發(fā)送某個字段作為消息,服務(wù)通信包含請求和響應(yīng)兩個過程,因此使用服務(wù)通信前必須先定義服務(wù)類型

    首先在與功能包 src 同級的 srv 目錄下編輯自定義消息文件。其中,請求和響應(yīng)使用---分割

    # 客戶端請求時發(fā)送的兩個數(shù)字 int32 num1 int32 num2 --- # 服務(wù)器響應(yīng)發(fā)送的數(shù)據(jù) int32 sum

    編輯 package.xml 文件,添加編譯依賴與執(zhí)行依賴

    <build_depend>message_generation</build_depend><exec_depend>message_runtime</exec_depend><!-- exce_depend 以前對應(yīng)的是 run_depend 現(xiàn)在非法-->

    編輯 CMakeLists.txt 相關(guān)配置

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

    最后進(jìn)行編譯,會生成供 C++ 和 Python 使用的中間文件,此時調(diào)用這些中間文件就能發(fā)送自定義消息

    • C++:.../工作空間/devel/include/包名/xxx.h。調(diào)用時執(zhí)行#include "功能包包名/xxx.h"
    • Python:.../工作空間/devel/lib/python3/dist-packages/包名/srv。調(diào)用時執(zhí)行from 功能包包名.srv import xxx

    2.2.5. C++ 實現(xiàn)

    服務(wù)端

    #include "ros/ros.h" #include "demo03_server_client/AddInts.h"// bool 返回值由于標(biāo)志是否處理成功 bool doReq(demo03_server_client::AddInts::Request& req,demo03_server_client::AddInts::Response& resp) {int num1 = req.num1;int num2 = req.num2;ROS_INFO("服務(wù)器接收到的請求數(shù)據(jù)為:num1 = %d, num2 = %d",num1, num2);//邏輯處理if (num1 < 0 || num2 < 0) {ROS_ERROR("提交的數(shù)據(jù)異常:數(shù)據(jù)不可以為負(fù)數(shù)");return false;}//如果沒有異常,那么相加并將結(jié)果賦值給 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("服務(wù)已經(jīng)啟動....");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("請?zhí)峤粌蓚€整數(shù)");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();// 組織請求數(shù)據(jù)demo03_server_client::AddInts ai;ai.request.num1 = atoi(argv[1]);ai.request.num2 = atoi(argv[2]);// 發(fā)送請求,返回 bool 值,標(biāo)記是否成功bool flag = client.call(ai);// 處理響應(yīng)if (flag) {ROS_INFO("請求正常處理,響應(yīng)結(jié)果:%d",ai.response.sum);}else {ROS_ERROR("請求處理失敗....");return 1;}return 0; }

    在客戶端發(fā)送請求前添加: client.waitForExistence(); 或: ros::service::waitForService("AddInts");。這是一個阻塞式函數(shù),只有服務(wù)啟動成功后才會繼續(xù)執(zhí)行。client.call() 也是一個阻塞式函數(shù)

    2.2.6. Python 實現(xiàn)

    服務(wù)端

    #! /usr/bin/env pythonimport rospy from demo03_server_client.srv import AddInts,AddIntsRequest,AddIntsResponsedef doReq(req):sum = req.num1 + req.num2rospy.loginfo("提交的數(shù)據(jù):num1 = %d, num2 = %d, sum = %d",req.num1, req.num2, sum)resp = AddIntsResponse(sum)return respif __name__ == "__main__":rospy.init_node("addints_server_p")# 創(chuàng)建服務(wù)對象server = rospy.Service("AddInts",AddInts,doReq)rospy.spin()

    客戶端

    #! /usr/bin/env pythonimport rospy from demo03_server_client.srv import * import sysif __name__ == "__main__":if len(sys.argv) != 3:rospy.logerr("請正確提交參數(shù)")sys.exit(1)rospy.init_node("AddInts_Client_p")# 創(chuàng)建請求對象client = rospy.ServiceProxy("AddInts",AddInts)# 方式1:# rospy.wait_for_service("AddInts")# 方式2client.wait_for_service()# 發(fā)送請求,接收并處理響應(yīng)# 方式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("響應(yīng)結(jié)果:%d",resp.sum)

    2.3. 參數(shù)服務(wù)器

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

    3. 運行

    3.1. launch

    3.1.1. 運行

    一個程序中可能需要啟動多個節(jié)點,比如:ROS 內(nèi)置的小烏龜案例,如果要控制烏龜運動,要啟動多個窗口,分別啟動 roscore、烏龜界面節(jié)點、鍵盤控制節(jié)點。如果每次都調(diào)用 rosrun 逐一啟動,顯然效率低下,因此官方給出的優(yōu)化策略是使用 launch 文件,可以一次性啟動多個 ROS 節(jié)點。

  • 進(jìn)入 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 命令會在內(nèi)部自動執(zhí)行 roscore 命令)
  • roslaunch 包名 launch文件名

    運行結(jié)果:一次性啟動了多個節(jié)點

    3.1.2. 標(biāo)簽

    3.1.2.1. launch

    launch 標(biāo)簽規(guī)定了一片區(qū)域,所有的 launch 文件都由 <launch> 開頭,由 </launch> 結(jié)尾,所有的描述標(biāo)簽都要寫在 <launch> 和 </launch>之間

    <launch> ... </launch>

    3.1.2.2. node

    node 標(biāo)簽里包括了 ROS 中節(jié)點的名稱屬性 name、該節(jié)點所在的包名 pkg、節(jié)點的類型 type 以及日志輸出位置屬性 output

    屬性屬性作用
    name=“NODE_NAME”為節(jié)點指派名稱,這將會覆蓋掉 ros::init() 定義的 node_name
    pkg=“PACKAGE_NAME”節(jié)點所在的包名
    type=“FILE_NAME”執(zhí)行文件的名稱。如果是用 Python 編寫則為 xxx.py,如果是 C++ 則為生成的可執(zhí)行文件名
    output=“screen”終端輸出轉(zhuǎn)儲在當(dāng)前的控制臺上,而不是在日志文件中。在節(jié)點內(nèi)有日志輸出時該屬性才會起作用
    respawn=“true”當(dāng) roslaunch 啟動完所有該啟動的節(jié)點之后,會監(jiān)測每一個節(jié)點,保證它們正常的運行狀態(tài)。對于任意節(jié)點,當(dāng)它終止時,roslaunch 會將該節(jié)點重啟
    required=“true”當(dāng)被此屬性標(biāo)記的節(jié)點終止時,roslaunch 會將其他的節(jié)點一并終止。注意此屬性不可以與 respawn=“true” 一起描述同一個節(jié)點
    launch-prefix = “command-prefix”相當(dāng)于在執(zhí)行啟動命令時加上一段命令前綴
    ns = “NAME_SPACE”在自定義的命名空間里運行節(jié)點

    3.1.2.3. include

    該標(biāo)簽可以導(dǎo)入另一個 roslaunch XML 文件到當(dāng)前文件

    屬性屬性作用
    file ="$(find pkg-name)/path/filename.xml"指明想要包含進(jìn)來的文件
    ns=“NAME_SPACE”相對NAME_SPACE命名空間導(dǎo)入文件
    <include file="$(find demo)/launch/demo.launch" ns="demo_namespace"> ... </include>

    3.1.2.4. remap

    ROS 支持 topic 的重映射,remap 標(biāo)簽里包含一個 original-name 和一個 new-name,及原名稱和新名稱

    比如現(xiàn)在拿到一個節(jié)點,這個節(jié)點訂閱了 “/chatter” 話題,然而自己寫的節(jié)點只能發(fā)布到 “/demo/chatter” 話題,由于這兩個 topic 的消息類型是一致的,想讓這兩個節(jié)點進(jìn)行通訊,那么可以在 launch 文件中這樣寫:

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

    這樣就可以直接把 /chatter 重映射到 /demo/chatter,讓兩個節(jié)點進(jìn)行通訊

    如果 remap 標(biāo)簽寫在與 node 元素的同一級,而且在 launch 元素內(nèi)的最頂層。 那么這個重映射將會作用于 launch 文件中所有的節(jié)點

    3.1.2.5. param

    param 標(biāo)簽的作用相當(dāng)于命令行中的 rosparam set。比如現(xiàn)在在參數(shù)服務(wù)器中添加一個名為 demo_param,值為 666 的參數(shù)

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

    3.1.2.6. rosparam

    rosparam 標(biāo)簽允許從 YAML 文件中一次性導(dǎo)入大量參數(shù)

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

    3.1.2.7. arg

    arg 標(biāo)簽用來在 launch 文件中定義參數(shù),arg 和 param 在 ROS 里有根本性的區(qū)別,就像局部變量和全局變量的區(qū)別一樣。arg 不儲存在參數(shù)服務(wù)器中,不能提供給節(jié)點使用,只能在 launch 文件中使用。param 則是儲存在參數(shù)服務(wù)器中,可以被節(jié)點使用。

    <arg name="demo"/>

    像上面這樣,就簡單地聲明了一個參數(shù),名叫 demo,但是聲明不等于定義,在賦值之后參數(shù)才能夠發(fā)揮作用。

    <arg name="demo" value="666"/> <arg name="demo" default="666"/>

    以上是兩種簡單的賦值方法,兩者的區(qū)別是使用后者賦值的參數(shù)可以在命令行中像下面這樣被修改,前者則不行。

    roslaunch demo demo.launch demo:=6666

    arg 還有更加高級,也更加靈活的用法:$(arg arg_name)
    當(dāng) $(arg arg_name) 出現(xiàn)在 launch 文件任意位置時,將會自動替代為所給參數(shù)的值

    3.1.2.8. group

    group 標(biāo)簽可以將若干個節(jié)點同時劃分進(jìn)某個工作空間

    <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 標(biāo)簽還可以做到對 node 的批量管理。比如可以同時終止在同一個 group 中的節(jié)點

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

    第一種情況,當(dāng) if 屬性的值為 0 的時候?qū)雎缘?<group></group> 之間的標(biāo)簽

    第二種恰好相反,當(dāng) if 屬性的值為 1 的時候?qū)雎缘?<group></group> 之間的標(biāo)簽

    但是通常不會直接用 1 或 0 來定義 if 標(biāo)簽。因為這樣不夠靈活。
    通常會搭配 $(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

    總結(jié)

    以上是生活随笔為你收集整理的ROS基础教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。