ROS基础教程
文章目錄
- 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)可分為三層
- 基于 TCP/UDP 繼續(xù)封裝的 TCPROS/UDPROS 通信系統(tǒng)
- 用于進(jìn)程間通信 Nodelet,為數(shù)據(jù)的實時性傳輸提供支持
- 大量的機(jī)器人開發(fā)實現(xiàn)庫,如:數(shù)據(jù)類型定義、坐標(biāo)變換、運動控制…
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 配置信息|-- packageROS 的文件系統(tǒng)本質(zhì)上都還是操作系統(tǒng)文件,因此可以使用 Linux 命令來操作這些文件。不過,在 ROS 中為了更好的用戶體驗,ROS 專門提供了一些類似于 Linux 的命令,這些命令較之于 Linux 原生命令,更為簡潔、高效。
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_graph1.4. Source
- 下面命令是使得能夠在任意終端都能使用 ROS
- 下面命令是使得能夠在任意終端都能使用當(dāng)前工作空間
1.5. helloworld
ROS 可是使用 C++ 或者 Python,但是具體選擇哪種語言需要視需求而定:C++ 運行效率高但是編碼效率低,而 Python 則反之?;诙呋パa的特點,ROS 設(shè)計者分別設(shè)計了 roscpp 與 rospy 庫,前者旨在成為 ROS 的高性能庫,而后者則一般用于對性能無要求的場景,旨在提高開發(fā)效率
1.5.1. C++ 實現(xiàn)
返回頂層工作空間目錄并編譯(生成 build 和 devel):catkin_make
執(zhí)行
命令行輸出: HelloWorld!
1.5.2. Python 實現(xiàn)
返回頂層工作空間目錄并編譯:catkin_make
執(zhí)行
輸出結(jié)果:HelloWorld!
另外,因為 Python 文件運行不需要編譯,所以在編寫完腳本后,可以直接運行節(jié)點
rosrun 包名 文件名.py或者直接運行 Python 文件
python 文件名.py2. 通信
機(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 提供了三種方式:
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 *.pyCMakeLists.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é)點。
運行結(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)入文件 |
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:=6666arg 還有更加高級,也更加靈活的用法:$(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 文件
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é)
- 上一篇: Mobile Terminal 316s
- 下一篇: Go 斐波那契闭包