機器人操作系統(tǒng)ROS:從入門到放棄(三) 發(fā)布接收不同消息2

上一講我們講了如何發(fā)布接收int,float和array類型的消息,這些也都算是c++自有的消息,被歸納在ROS中的std_msgs這個命名空間下(命名空間skr啥?).這講我們來看看如何發(fā)布接收ROS自己獨特的message類型:PoseStamped.Pose好理解,就是機器人的位姿(position and orientation),那么Stamped呢?Stamped表示時間戳(timestamped),這兒時間戳是指時間戳是指格林威治時間1970年01月01日00時00分00秒起至現(xiàn)在的總納秒.幾乎所有的計算機都可以使用這個時間,方便統(tǒng)一.所以PoseStamped記錄的是機器人的位姿加上記錄位姿的時間這么一種message.這個message被包含在geometry_msgs這個大類下.所以你可以先搜ROS, geometry message查看.第一個網(wǎng)頁應(yīng)該就是下面這個
http://wiki.ros.org/geometry_msgs
里面又有一大堆不同類型的消息,你以后都可能用到.但這兒我們就用PoseStamped舉例了,位姿加時間還是比較有代表性.
找到頁面中的PoseStamped并點擊進入,會發(fā)現(xiàn)下面的內(nèi)容

poseStampedDef.png

你如果想使用這個消息,那么看到這個頁面,根據(jù)我們之前講的內(nèi)容,你至少可以做下面這幾件事
1: 包含頭文件#include "geometry_msgs/PoseStamped.h",這樣我們才可以使用這個類型的消息
2:  通過geometry_msgs::PoseStamped msg定義一個叫msg的對象,該對象可以使用上圖中的header,pose兩個數(shù)據(jù)成員.即你可以通過msg.header,msg.pose來調(diào)用數(shù)據(jù)類型為std_msgs/Header,geometry_msgs/Pose的兩個數(shù)據(jù)成員.

但是那個header和pose具體是什么東西我們不清楚.但之前說了,任何復(fù)雜數(shù)據(jù)類型都是由簡單的組成的,頁面下的淺藍(lán)色字體是鏈接可以點進去,咱們先點擊std_msgs/Header看看他是個什么東西.你會看到下圖內(nèi)容.

headerDef.png

uint32和string就是基礎(chǔ)變量了.通過Compact Message Definition可以知道,如果我們通過std_msgs::Header msg_header創(chuàng)建了一個叫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,會不會出問題?有可能.這時候又要用到下面這個網(wǎng)站了
http://wiki.ros.org/msg
找到uint32對應(yīng)的c++定義是什么,發(fā)現(xiàn)是uint32_t,所以你直接用,uint32_t 定義 a,b是最好的.int是整型,uint32_t是無符號32位整型,兩者的范圍不一樣,這個可以自己上網(wǎng)查,但是a,b在他們交集的范圍內(nèi)就不會改變具體的數(shù)值.總之同理你可以給msg_header.frame_id賦值string類型的變量.
但這個time是個什么東西呢?標(biāo)準(zhǔn)數(shù)據(jù)類型可沒有這個玩意兒.不過上面的Raw Message Definition給出解釋了,stamp.sec返回從epoch開始的秒為單位的時間,stamp.nsec返回從stamp_sec開始的納秒時間.
msgs_heder.stamp調(diào)用stamp,stamp.sec調(diào)用sec得到epoch的時間,那么msgs_header.stamp.sec就可以獲取當(dāng)前的時間,秒為單位
通常我們肯定需要更精確的時間即包含納秒的,假設(shè)我們想把當(dāng)前的精確時間賦值給變量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)換為秒,第二行得到的是時間以秒為單位.第四行得到的時間是以ns為單位.

好了,header說了這么多,目前我們想要這兒的stamp記錄一個時間.最開始我們定義了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)前時間ros::Time::now(),返回的是time類型的變量.所以我們可以通過

msg.header.stamp = ros::Time::now()

來獲取當(dāng)前時間并儲存在這個message中.在賦值之后,如果想要取出我記錄那個位姿時的時間并且是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.headergeometry_msgs::PoseStamped的對象msg調(diào)用數(shù)據(jù)成員header,而msg_headerstd_msgs::Header的對象.
說完header,我們可以看msg的另一個數(shù)據(jù)成員pose了.同樣,我們點擊第一張圖片中的淺藍(lán)色字體geometry_msgs/Pose看一看它具體是由什么基本變量構(gòu)成的.點擊進去之后我們進入了Pose的定義的界面.Pose的數(shù)據(jù)成員是positionorientation,非常好理解,位置和方向.他們倆的數(shù)據(jù)類型分別是geometry_msgs/Pointgeometry_msgs/Quaternion.這倆也不是基本數(shù)據(jù)類型,你可以繼續(xù)點進去直到變量只有基本數(shù)據(jù)類型.這兒為了簡潔,我直接畫了個圖,把PoseStamped包含的成員一一列了出來.

geometry_msgs.png

看這個圖就一目了然了.(免費用了人家的東西得打個廣告,這個圖是用一個叫ProcessOn的在線軟件畫出來的,該軟件用來畫流程圖,思維圖,結(jié)構(gòu)圖什么的非常方便).
Header部分已經(jīng)講過.咱們繼續(xù)看geometry/msgs/Pose類型的數(shù)據(jù)pose所包含的成員
1是geometry_msgs/Point類型的position,而position包含三個float64的變量x,y,z,這個很好理解了,怎么定義機器人的位置?三維坐標(biāo)x,y,z就可以了.
2是geometry_msgs/Quaternion類型的'oreintation',而oreintation包含四個float64的變量x,y,z,w,quaterion中文四元數(shù),是一個用來表示方向的東西.四元數(shù)的缺點是不很形象,不熟的人很難直接通過四元數(shù)在腦海里構(gòu)想出機器人目前到底是個什么朝向.我們一般用歐拉角表示方向時,一共有三個數(shù)roll,pitch,yaw,比較直觀,但是歐拉角表示方向時會遇到一個叫Gimbal Lock(萬向鎖)的尷尬問題,所以ROS里統(tǒng)一用四元數(shù)表示方向.ROS當(dāng)然提供了一些API把四元數(shù)轉(zhuǎn)換成歐拉角或者旋轉(zhuǎn)矩陣之類的,但是在儲存pose的時候,都統(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.直接用代碼來說明.接下來我們要建立一個叫pub_poseStamped.cpp的文件和sub_poseStamped.cpp的文件.第一個文件咱們來人工產(chǎn)生一些pose,恩,,,我相信很多同學(xué)手上沒有直接可以和ROS溝通的傳感器用的.產(chǎn)生了這些pose后我們把他們發(fā)布出去用接收器接收.
和之前一樣,我們先在pub_sub_test/src中創(chuàng)建一個名字叫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類型的變量,賦值了他們幾個就可以定義pose的orientation了.orientation是相同的數(shù),那么機器人就沒有旋轉(zhuǎn).
那么pose的position的x y z我們直接賦值了count,count在循環(huán)中遞增,那么XYZ都同時遞增且相同.如果我們畫一個三維坐標(biāo)軸XYZ的話,那么咱么這兒模擬的機器人的運動狀態(tài),相當(dāng)于機器人沿著坐標(biāo)軸對角線勻速直線行駛.
同樣位置再創(chuàng)建一個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添加編譯兩個文件的內(nèi)容.保存退出后使用catkin_make編譯(注意catkin_make這個命令要在catkin_ws這個目錄下使用的).

結(jié)果編譯出錯了?。″e誤大概長下面的樣子


error_poseStamped.png

讀不懂啥意思?。屑?xì)對比看看前面的內(nèi)容和你之前編譯pub_int8, pub_string等文件時有沒有什么出入?經(jīng)過仔細(xì)的檢查,發(fā)現(xiàn) 木有.所有步驟都是一毛一樣的.那么問題出在哪兒呢?
記得我們當(dāng)初創(chuàng)建pub_sub_test這個package的時候,我們是這么創(chuàng)建的

catkin_create_pkg pub_sub_test  std_msgs  roscpp rospy

我們說三個依賴項std_msgs roscpp rospy就好像C++的頭文件一樣,如果我們要使用ROS自帶的std_msgs這個包,我們就需要添加這個依賴項.那么現(xiàn)在問題很明顯了,我們?nèi)鄙偬砑?code>geometry_msgs這個依賴項.可以想象,如果當(dāng)初我們創(chuàng)建package時用的下面的內(nèi)容,就不會有什么問題了

catkin_create_pkg pub_sub_test  std_msgs geometry_msgs  roscpp rospy

可是我們package已經(jīng)創(chuàng)建了,又不能重新創(chuàng)建一個相同名字的package把依賴項加進去.那該怎么辦呢?
當(dāng)我們需要添加新的依賴項時,我們需要修改兩個文件,一個是package目錄下的CMakeLists.txt,另一個是位于同一位置的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時添加的依賴項,那么想都不用想啦,肯定要在后面添加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,只不過每個出現(xiàn)了三次,那么同樣,不用管什么意思,我們只需要按照這個文檔里相同的語法讓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>

保存退出.這時候再用catkin_make編譯,就成功了.改變上面兩個文檔的內(nèi)容就相當(dāng)于我們在創(chuàng)建package時添加了依賴項geometry_msgs.編譯成功之后,跑程序的方法和以前的例子一樣.

總結(jié)

這一講我們講述了一個稍微復(fù)雜一點的message類型:poseStamped.我們需要通過ROS wiki的幫助知道它包含什么數(shù)據(jù)成員,了解它包含的數(shù)據(jù)成員之后,利用類對象引用數(shù)據(jù)成員的方法(就像msg.pose.orientation)的方法,就可以調(diào)用或者賦值相應(yīng)類型的成員了.因為我們手上沒有傳感器,我們自己產(chǎn)生了double類型的變量賦值,從最底層float64 x y z之類的開始賦值,模擬機器人的運動.但其實如果一個好的定位傳感器自己有ROS的接口的話,很可能直接產(chǎn)生geometry_msgs/Point類型的變量,假設(shè)名字叫currentPosition,那么我們通過msg.pose.position = currentPosition給pose的位置賦值就可以了,因為pose.position也是geometry_msgs/Point類型的變量.這個大家接觸具體的傳感器就知道了.

另外這一講講了如何添加新的ROS依賴項.

那么目前我們總共聊了怎么發(fā)布string,int8,array和poseStamped.關(guān)于不同類型的消息,我們就講到這兒了.希望大家以后面對自己想要發(fā)布的不同類型的消息的時候,知道在ROS wiki中怎么找到并看懂相應(yīng)的資料.

下下講我們會介紹如何在類中發(fā)布和接收消息.在這之前呢,為了照顧到語言不那么熟的同學(xué),我們可能下講就先回顧一下C++類的內(nèi)容.還會提到命名空間和模版,并且和ROS中出現(xiàn)的語法對應(yīng)起來.語言熟悉的同學(xué)就可以跳過下一講了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容