上一講我們講了如何發(fā)布接收int粗截,float和array類型的消息惋耙,這些也都算是c++自有的消息,被歸納在ROS中的std_msgs這個(gè)命名空間下(命名空間skr啥熊昌?).這講我們來看看如何發(fā)布接收ROS自己獨(dú)特的message類型:PoseStamped.Pose好理解绽榛,就是機(jī)器人的位姿(position and orientation),那么Stamped呢浴捆?Stamped表示時(shí)間戳(timestamped)蒜田,這兒時(shí)間戳是指時(shí)間戳是指格林威治時(shí)間1970年01月01日00時(shí)00分00秒起至現(xiàn)在的總納秒.幾乎所有的計(jì)算機(jī)都可以使用這個(gè)時(shí)間,方便統(tǒng)一.所以PoseStamped記錄的是機(jī)器人的位姿加上記錄位姿的時(shí)間這么一種message.這個(gè)message被包含在geometry_msgs這個(gè)大類下.所以你可以先搜ROS, geometry message查看.第一個(gè)網(wǎng)頁應(yīng)該就是下面這個(gè)
http://wiki.ros.org/geometry_msgs
里面又有一大堆不同類型的消息选泻,你以后都可能用到.但這兒我們就用PoseStamped舉例了冲粤,位姿加時(shí)間還是比較有代表性.
找到頁面中的PoseStamped
并點(diǎn)擊進(jìn)入美莫,會發(fā)現(xiàn)下面的內(nèi)容
你如果想使用這個(gè)消息,那么看到這個(gè)頁面梯捕,根據(jù)我們之前講的內(nèi)容厢呵,你至少可以做下面這幾件事
1: 包含頭文件
#include "geometry_msgs/PoseStamped.h"
,這樣我們才可以使用這個(gè)類型的消息2: 通過
geometry_msgs::PoseStamped msg
定義一個(gè)叫msg的對象傀顾,該對象可以使用上圖中的header,pose
兩個(gè)數(shù)據(jù)成員.即你可以通過msg.header
,msg.pose
來調(diào)用數(shù)據(jù)類型為std_msgs/Header
,geometry_msgs/Pose
的兩個(gè)數(shù)據(jù)成員.
但是那個(gè)header和pose具體是什么東西我們不清楚.但之前說了襟铭,任何復(fù)雜數(shù)據(jù)類型都是由簡單的組成的,頁面下的淺藍(lán)色字體是鏈接可以點(diǎn)進(jìn)去短曾,咱們先點(diǎn)擊std_msgs/Header
看看他是個(gè)什么東西.你會看到下圖內(nèi)容.
uint32和string就是基礎(chǔ)變量了.通過
Compact Message Definition
可以知道寒砖,如果我們通過std_msgs::Header msg_header
創(chuàng)建了一個(gè)叫msg_header類型的對象的話,那么它可以通過msg_header.seq調(diào)用類型為unit32的成員seq
.也就是說嫉拐,代碼里你可以用類似于
int a = 1 , b;
msgs_header.seq = 1;
//or
b = msg_header.seq;
這種代碼給msgs_header.seq
賦值或者把它賦值給其他整型變量.可能你要問了哩都,seq定義的不是uint32么,我對a,b的定義就是int婉徘,會不會出問題漠嵌?有可能.這時(shí)候又要用到下面這個(gè)網(wǎng)站了
http://wiki.ros.org/msg
找到uint32對應(yīng)的c++定義是什么,發(fā)現(xiàn)是uint32_t
盖呼,所以你直接用儒鹿,uint32_t 定義 a,b是最好的.int是整型,uint32_t是無符號32位整型几晤,兩者的范圍不一樣约炎,這個(gè)可以自己上網(wǎng)查,但是a,b在他們交集的范圍內(nèi)就不會改變具體的數(shù)值.總之同理你可以給msg_header.frame_id
賦值string類型的變量.
但這個(gè)time是個(gè)什么東西呢锌仅?標(biāo)準(zhǔn)數(shù)據(jù)類型可沒有這個(gè)玩意兒.不過上面的Raw Message Definition
給出解釋了章钾,stamp.sec
返回從epoch開始的秒為單位的時(shí)間,stamp.nsec
返回從stamp_sec
開始的納秒時(shí)間.
msgs_heder.stamp調(diào)用stamp热芹,stamp.sec調(diào)用sec得到epoch的時(shí)間贱傀,那么msgs_header.stamp.sec就可以獲取當(dāng)前的時(shí)間,秒為單位
通常我們肯定需要更精確的時(shí)間即包含納秒的伊脓,假設(shè)我們想把當(dāng)前的精確時(shí)間賦值給變量store_time
府寒,那么代碼類似于下面這樣.
double store_time;
store_time = msg_header.stamp.sec + 1e-9*msg_header.stamp.nsec; //unit s
//or
store_time = msgs_header.stamp.sec * 1e9 + msg_header.stamp.nsec; //unit ns
nsec的單位是納秒,我們要乘以1e-9才能轉(zhuǎn)換為秒报腔,第二行得到的是時(shí)間以秒為單位.第四行得到的時(shí)間是以ns為單位.
好了株搔,header說了這么多,目前我們想要這兒的stamp記錄一個(gè)時(shí)間.最開始我們定義了geometry_msgs::PoseStamped msg
.
msg包含數(shù)據(jù)成員header纯蛾,而heaer作為std_msgs/Header
的數(shù)據(jù)成員包含stamp纤房,那么我們可以通過msg.header.stamp調(diào)用數(shù)據(jù)類型為time的成員stamp,為什么能這么做翻诉,我們下一張會講到.ROS可以很方便獲取當(dāng)前時(shí)間ros::Time::now()
炮姨,返回的是time類型的變量.所以我們可以通過
msg.header.stamp = ros::Time::now()
來獲取當(dāng)前時(shí)間并儲存在這個(gè)message中.在賦值之后捌刮,如果想要取出我記錄那個(gè)位姿時(shí)的時(shí)間并且是double類型呢?根據(jù)上面的內(nèi)容舒岸,很容易可以得到.
double store_time;
store_time = msg.header.stamp.sec + 1e-9*msg.header.stamp.nsec; //unit s
//or
store_time = msg.header.stamp.sec * 1e9 + msg.header.stamp.nsec; //unit ns
注意不要以為msg.header 等于msg_header哈...只是上面的例子我恰巧給std_msgs::Header
對象取名叫msg_header罷了.張三李四都可以.msg.header
是geometry_msgs::PoseStamped
的對象msg調(diào)用數(shù)據(jù)成員header绅作,而msg_header
是std_msgs::Header
的對象.
說完header,我們可以看msg的另一個(gè)數(shù)據(jù)成員pose
了.同樣蛾派,我們點(diǎn)擊第一張圖片中的淺藍(lán)色字體geometry_msgs/Pose
看一看它具體是由什么基本變量構(gòu)成的.點(diǎn)擊進(jìn)去之后我們進(jìn)入了Pose的定義的界面.Pose的數(shù)據(jù)成員是position
和orientation
俄认,非常好理解,位置和方向.他們倆的數(shù)據(jù)類型分別是geometry_msgs/Point
和geometry_msgs/Quaternion
.這倆也不是基本數(shù)據(jù)類型洪乍,你可以繼續(xù)點(diǎn)進(jìn)去直到變量只有基本數(shù)據(jù)類型.這兒為了簡潔眯杏,我直接畫了個(gè)圖,把PoseStamped包含的成員一一列了出來.
看這個(gè)圖就一目了然了.(免費(fèi)用了人家的東西得打個(gè)廣告典尾,這個(gè)圖是用一個(gè)叫ProcessOn的在線軟件畫出來的役拴,該軟件用來畫流程圖糊探,思維圖钾埂,結(jié)構(gòu)圖什么的非常方便).
Header部分已經(jīng)講過.咱們繼續(xù)看geometry/msgs/Pose類型的數(shù)據(jù)
pose
所包含的成員1是geometry_msgs/Point類型的
position
,而position
包含三個(gè)float64的變量x,y,z
科平,這個(gè)很好理解了褥紫,怎么定義機(jī)器人的位置?三維坐標(biāo)x,y,z就可以了.2是geometry_msgs/Quaternion類型的'oreintation'瞪慧,而
oreintation
包含四個(gè)float64的變量x,y,z,w
髓考,quaterion中文四元數(shù),是一個(gè)用來表示方向的東西.四元數(shù)的缺點(diǎn)是不很形象弃酌,不熟的人很難直接通過四元數(shù)在腦海里構(gòu)想出機(jī)器人目前到底是個(gè)什么朝向.我們一般用歐拉角表示方向時(shí)氨菇,一共有三個(gè)數(shù)roll,pitch,yaw,比較直觀妓湘,但是歐拉角表示方向時(shí)會遇到一個(gè)叫Gimbal Lock(萬向鎖)的尷尬問題查蓉,所以ROS里統(tǒng)一用四元數(shù)表示方向.ROS當(dāng)然提供了一些API把四元數(shù)轉(zhuǎn)換成歐拉角或者旋轉(zhuǎn)矩陣之類的,但是在儲存pose的時(shí)候榜贴,都統(tǒng)一用四元數(shù)儲存.如果不清楚四元數(shù)的同學(xué)可以網(wǎng)上查一下豌研,很多資料.數(shù)據(jù)的結(jié)構(gòu)說完了,怎么使用呢唬党?和
msg.header.stamp.sec
調(diào)用int32類型的成員sec一樣鹃共,咱們可以用msg.pose.position.x
調(diào)用或者賦值float64的成員x.直接用代碼來說明.接下來我們要建立一個(gè)叫pub_poseStamped.cpp
的文件和sub_poseStamped.cpp
的文件.第一個(gè)文件咱們來人工產(chǎn)生一些pose,恩驶拱,霜浴,,我相信很多同學(xué)手上沒有直接可以和ROS溝通的傳感器用的.產(chǎn)生了這些pose后我們把他們發(fā)布出去用接收器接收.和之前一樣蓝纲,我們先在
pub_sub_test/src
中創(chuàng)建一個(gè)名字叫pub_poseStamped.cpp
的文件.在文件中寫入下面的內(nèi)容.
#include "ros/ros.h"
#include "geometry_msgs/PoseStamped.h" //include posestamp head file
#include <cmath>//for sqrt() function
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker");
ros::NodeHandle n;
ros::Publisher chatter_pub = n.advertise<geometry_msgs::PoseStamped>("chatter", 10); //initialize chatter
ros::Rate loop_rate(10);
//generate pose by ourselves.
double positionX, positionY, positionZ;
double orientationX, orientationY, orientationZ, orientationW;
//We just make the robot has fixed orientation. Normally quaternion needs to be normalized, which means x^2 + y^2 + z^2 +w^2 = 1
double fixedOrientation = 0.1;
orientationX = fixedOrientation ;
orientationY = fixedOrientation ;
orientationZ = fixedOrientation ;
orientationW = sqrt(1.0 - 3.0*fixedOrientation*fixedOrientation);
double count = 0.0;
while (ros::ok())
{
//We just make the position x,y,z all the same. The X,Y,Z increase linearly
positionX = count;
positionY = count;
positionZ = count;
geometry_msgs::PoseStamped msg;
//assign value to poseStamped
//First assign value to "header".
ros::Time currentTime = ros::Time::now();
msg.header.stamp = currentTime;
//Then assign value to "pose", which has member position and orientation
msg.pose.position.x = positionX;
msg.pose.position.y = positionY;
msg.pose.position.z = positionY;
msg.pose.orientation.x = orientationX;
msg.pose.orientation.y = orientationY;
msg.pose.orientation.z = orientationZ;
msg.pose.orientation.w = orientationW;
ROS_INFO("we publish the robot's position and orientaion");
ROS_INFO("the position(x,y,z) is %f , %f, %f", msg.pose.position.x, msg.pose.position.y, msg.pose.position.z);
ROS_INFO("the orientation(x,y,z,w) is %f , %f, %f, %f", msg.pose.orientation.x, msg.pose.orientation.y, msg.pose.orientation.z, msg.pose.orientation.w);
ROS_INFO("the time we get the pose is %f", msg.header.stamp.sec + 1e-9*msg.header.stamp.nsec);
std::cout<<"\n \n"<<std::endl; //add two more blank row so that we can see the message more clearly
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
咱們在發(fā)布器的代碼中fixedOrientation
的變量阴孟,賦值0.1房铭,然后分別賦值給創(chuàng)建的double類型變量orientationX,Y,Z,W
.在循環(huán)中,orientationX,Y,Z,W
在分別賦值給我們創(chuàng)建的msg的成員變量msg.pose.orientation.x y z w
.為什么能這么賦值温眉?通過上面數(shù)據(jù)結(jié)構(gòu)那張圖我們了解到的msg.pose.orientation.x y z w
都是float64類型的變量缸匪,賦值了他們幾個(gè)就可以定義pose的orientation了.orientation是相同的數(shù),那么機(jī)器人就沒有旋轉(zhuǎn).
那么pose的position的x y z我們直接賦值了count类溢,count在循環(huán)中遞增凌蔬,那么XYZ都同時(shí)遞增且相同.如果我們畫一個(gè)三維坐標(biāo)軸XYZ的話,那么咱么這兒模擬的機(jī)器人的運(yùn)動(dòng)狀態(tài)闯冷,相當(dāng)于機(jī)器人沿著坐標(biāo)軸對角線勻速直線行駛.
同樣位置再創(chuàng)建一個(gè)sub_poseStamped.cpp
.寫入下面內(nèi)容.
#include "ros/ros.h"
#include "geometry_msgs/PoseStamped.h"
void chatterCallback(const geometry_msgs::PoseStamped::ConstPtr& msg) //Note it is geometry_msgs::PoseStamped, not std_msgs::PoseStamped
{
ROS_INFO("I heard the pose from the robot");
ROS_INFO("the position(x,y,z) is %f , %f, %f", msg->pose.position.x, msg->pose.position.y, msg->pose.position.z);
ROS_INFO("the orientation(x,y,z,w) is %f , %f, %f, %f", msg->pose.orientation.x, msg->pose.orientation.y, msg->pose.orientation.z, msg->pose.orientation.w);
ROS_INFO("the time we get the pose is %f", msg->header.stamp.sec + 1e-9*msg->header.stamp.nsec);
std::cout<<"\n \n"<<std::endl; //add two more blank row so that we can see the message more clearly
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("chatter", 10, chatterCallback);
ros::spin();
return 0;
}
寫完之后砂心,我們同樣打開位于pub_sub_test
目錄下的CMakeLists.txt添加編譯兩個(gè)文件的內(nèi)容.保存退出后使用catkin_make編譯(注意catkin_make這個(gè)命令要在catkin_ws這個(gè)目錄下使用的).
結(jié)果編譯出錯(cuò)了!蛇耀!錯(cuò)誤大概長下面的樣子
讀不懂啥意思氨绲.仔細(xì)對比看看前面的內(nèi)容和你之前編譯pub_int8, pub_string
等文件時(shí)有沒有什么出入?經(jīng)過仔細(xì)的檢查纺涤,發(fā)現(xiàn) 木有.所有步驟都是一毛一樣的.那么問題出在哪兒呢译暂?
記得我們當(dāng)初創(chuàng)建pub_sub_test
這個(gè)package的時(shí)候,我們是這么創(chuàng)建的
catkin_create_pkg pub_sub_test std_msgs roscpp rospy
我們說三個(gè)依賴項(xiàng)std_msgs roscpp rospy
就好像C++的頭文件一樣撩炊,如果我們要使用ROS自帶的std_msgs這個(gè)包外永,我們就需要添加這個(gè)依賴項(xiàng).那么現(xiàn)在問題很明顯了,我們?nèi)鄙偬砑?code>geometry_msgs這個(gè)依賴項(xiàng).可以想象拧咳,如果當(dāng)初我們創(chuàng)建package時(shí)用的下面的內(nèi)容伯顶,就不會有什么問題了
catkin_create_pkg pub_sub_test std_msgs geometry_msgs roscpp rospy
可是我們package已經(jīng)創(chuàng)建了,又不能重新創(chuàng)建一個(gè)相同名字的package把依賴項(xiàng)加進(jìn)去.那該怎么辦呢骆膝?
當(dāng)我們需要添加新的依賴項(xiàng)時(shí)祭衩,我們需要修改兩個(gè)文件,一個(gè)是package目錄下的CMakeLists.txt阅签,另一個(gè)是位于同一位置的package.xml
打開CMakeLists.txt掐暮,發(fā)現(xiàn)就在最前面幾行,有下面的內(nèi)容.
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
)
括號中的內(nèi)容正好一一對應(yīng)我們創(chuàng)建package時(shí)添加的依賴項(xiàng)愉择,那么想都不用想啦劫乱,肯定要在后面添加geometry_msgs,變成下面的樣子锥涕,保存退出.
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
geometry_msgs
)
添加完成后衷戈,保存退出.之后打開位于同一目錄下的package.xml
.(直接雙擊打開可能不能修改其中內(nèi)容,還是用gedit什么的打開).打開之后层坠,在文檔下方殖妇,你可以看到一下內(nèi)容
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
啊哈,又有std_msgs, rospy, roscpp
破花,只不過每個(gè)出現(xiàn)了三次谦趣,那么同樣疲吸,不用管什么意思,我們只需要按照這個(gè)文檔里相同的語法讓geometry_msgs
出現(xiàn)三次就行了.更改之后該文件同樣位置變成下面的內(nèi)容
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>geometry_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<build_export_depend>geometry_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>geometry_msgs</exec_depend>
保存退出.這時(shí)候再用catkin_make編譯前鹅,就成功了.改變上面兩個(gè)文檔的內(nèi)容就相當(dāng)于我們在創(chuàng)建package時(shí)添加了依賴項(xiàng)geometry_msgs
.編譯成功之后摘悴,跑程序的方法和以前的例子一樣.
總結(jié)
這一講我們講述了一個(gè)稍微復(fù)雜一點(diǎn)的message類型:poseStamped.我們需要通過ROS wiki的幫助知道它包含什么數(shù)據(jù)成員,了解它包含的數(shù)據(jù)成員之后舰绘,利用類對象引用數(shù)據(jù)成員的方法(就像msg.pose.orientation)的方法蹂喻,就可以調(diào)用或者賦值相應(yīng)類型的成員了.因?yàn)槲覀兪稚蠜]有傳感器,我們自己產(chǎn)生了double類型的變量賦值捂寿,從最底層float64 x y z之類的開始賦值口四,模擬機(jī)器人的運(yùn)動(dòng).但其實(shí)如果一個(gè)好的定位傳感器自己有ROS的接口的話,很可能直接產(chǎn)生geometry_msgs/Point
類型的變量秦陋,假設(shè)名字叫currentPosition
蔓彩,那么我們通過msg.pose.position = currentPosition
給pose的位置賦值就可以了,因?yàn)?code>pose.position也是geometry_msgs/Point
類型的變量.這個(gè)大家接觸具體的傳感器就知道了.
另外這一講講了如何添加新的ROS依賴項(xiàng).
那么目前我們總共聊了怎么發(fā)布string,int8,array和poseStamped.關(guān)于不同類型的消息驳概,我們就講到這兒了.希望大家以后面對自己想要發(fā)布的不同類型的消息的時(shí)候赤嚼,知道在ROS wiki中怎么找到并看懂相應(yīng)的資料.
下下講我們會介紹如何在類中發(fā)布和接收消息.在這之前呢,為了照顧到語言不那么熟的同學(xué)抡句,我們可能下講就先回顧一下C++類的內(nèi)容.還會提到命名空間和模版探膊,并且和ROS中出現(xiàn)的語法對應(yīng)起來.語言熟悉的同學(xué)就可以跳過下一講了.