ROS中話題的訂閱與發佈+ROS環境下上位機與Arduino單片機通訊

準備工作

創建工作空間

$ mkdir -p ~/catkin_ws/src
$ cd ~/catkin_ws/src
$ catkin_init_workspace

進入代碼空間,使用catkin_create_pkg命令創建功能包:

$ cd ~/catkin_ws/src
$ catkin_create_pkg learning_communication std_msgs rospy roscpp

然後回到工作空間的根目錄下進行編譯,並且設置環境變量:

$ cd ~/catkin_ws
$ catkin_make
$ source ~/catkin_ws/devel/setup.bash

工作空間和功能包的創建這裏就不過多重複講了

創建Publisher

Publisher的主要作用是針對指定話題發佈特定數據類型的消息。我們嘗試使用代碼實現一個節點,節點中創建一個Publisher併發布字符串“Hello World”。
在功能包(暫命名爲learning——communication)的src文件夾裏建立一個talker.cpp文件,內容爲:
(此處註釋比較詳細,大家可以直接下載我整理好的功能包去運行:)

#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"
//爲了避免包含繁雜的ROS功能包頭文件,ros/ros.h已經幫我們包含了大部分ROS中通用的頭文件。節點會發布String類型的消息,所以需要先包含該消息類型的頭文件String.h。該頭文件根據String.msg的消息結構定義自動生成,我們也可以自定義消息結構,並生成所需要的頭文件。

int main(int argc, char **argv)
{
// ROS節點初始化:初始化ROS節點。該初始化的init函數包含三個參數,前兩個參數是命令行或launch文件輸入的參數,可以用來完成命名重映射等功能;第三個參數定義了Publisher節點的名稱,而且該名稱在運行的ROS中必須是獨一無二的,不允許同時存在相同名稱的兩個節點。
ros::init(argc, argv, "talker");

// 創建節點句柄,,方便對節點資源的使用和管理。
ros::NodeHandle n;

// 創建一個Publisher,發佈名爲chatter的topic,消息類型爲std_msgs::String,第二個參數表示消息發佈隊列的大小,當發佈消息的實際速度較慢時,Publisher會將消息存儲在一定空間的隊列中;如果消息數量超過隊列大小時,ROS會自動刪除隊列中最早入隊的消息。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
// 設置循環的頻率,單位是Hz,這裏設置的是10 Hz。當調用Rate:sleep()時,ROS節點會根據此處設置的頻率休眠相應的時間,以保證循環維持一致的時間週期。
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
//進入節點的主循環,在節點未發生異常的情況下將一直在循環中運行,一旦發生異常,ros:ok()就會返回false,跳出循環。

// 初始化std_msgs::String類型的消息,這裏我們使用了最爲簡單的String消息類型,該消息類型只有一個成員,即data,用來存儲字符串數據。
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();

// 發佈封裝完畢的消息msg。消息發佈後,Master會查找訂閱該話題的節點,並且幫助兩個節點建立連接,完成消息的傳輸。
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);

// 循環等待回調函數
ros::spinOnce();

// 按照循環頻率延時,現在Publisher一個週期的工作已經完成,可以讓節點休息一段時間,調用休眠函數,節點進入休眠狀態。當然,節點不可能一直休眠下去,別忘了之前設置了10Hz的休眠時間,節點休眠100ms後又會開始下一個週期的循環工作。
loop_rate.sleep();
++count;
}
return 0;
}

實現Publisher的流程可總結爲:
初始化ROS節點→在ROS Master註冊節點信息→按照一定的頻率發佈消息

創建Subscriber

創建一個Subscriber以訂閱Publisher節點發布的“Hello World”字符串
在功能包的src文件夾裏建立一個listener.cpp文件,內容爲:
(和talker.cpp中重複的代碼部分不再詳細註釋)

#include "ros/ros.h"
#include "std_msgs/String.h"

// 接收到訂閱的消息後,會進入消息回調函數
//回調函數是訂閱節點接收消息的基礎機制,當有消息到達時會自動以消息指針作爲參數,再調用回調函數,完成對消息內容的處理。如上是一個簡單的回調函數,用來接收Publisher發佈的String消息,並將消息數據打印出來。
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
// 將接收到的消息打印出來
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
// 初始化ROS節點
ros::init(argc, argv, "listener");
// 創建節點句柄
ros::NodeHandle n;

// 創建一個Subscriber,訂閱名爲chatter的話題,註冊回調函數chatterCallback
//訂閱節點首先需要聲明自己訂閱的消息話題,該信息會在ROS Master中註冊。Master會關注系統中是否存在發佈該話題的節點,如果存在則會幫助兩個節點建立連接,完成數據傳輸。
//NodeHandle:subscribe(),這裏爲n.subscribe,用來創建一個Subscriber。第一個參數即爲消息話題;第二個參數是接收消息隊列的大小,和發佈節點的隊列相似,當消息入隊數量超過設置的隊列大小時,會自動捨棄時間戳最早的消息;第三個參數是接收到話題消息後的回調函數。
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

// 循環等待回調函數,ros:spin()在ros:ok()返回false時退出。
ros::spin();
return 0;
}

實現Subscriber的流程可總結爲:
初始化ROS節點→訂閱需要的話題→循環等待話題消息,接收到消息後進入回調函數→回調函數中完成消息處理。
編譯功能包
節點的代碼已經完成,C++是一種編譯語言,在運行之前需要將代碼編譯成可執行文件,如果使用Python等解析語言編寫代碼,則不需要進行編譯,可以省去此步驟。
ROS中的編譯器使用的是CMake,編譯規則通過功能包中的CMakeLists.txt文件設置,使用catkin命令創建的功能包中會自動生成該文件,已經配置多數編譯選項,並且包含詳細的註釋,我們幾乎不用查看相關的說明手冊,稍作修改就可以編譯自己的代碼。
打開功能包中的CMakeLists.txt文件,找到以下配置項,去掉註釋並稍作修改:

include_directories(include ${catkin_INCLUDE_DIRS})
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)

對於這個功能包,主要用到了以下四種編譯配置項:
(1)include_directories
用於設置頭文件的相對路徑。全局路徑默認是功能包的所在目錄,比如功能包的頭文件一般會放到功能包根目錄下的include文件夾中,所以此處需要添加該文件夾。此外,該配置項還包含ROS catkin編譯器默認包含的其他頭文件路徑,比如ROS默認安裝路徑、Linux系統路徑等。
(2)add_executable
用於設置需要編譯的代碼和生成的可執行文件。第一個參數爲期望生成的可執行文件的名稱,後邊的參數爲參與編譯的源碼文件(cpp),如果需要多個代碼文件,則可在後面依次列出,中間使用空格進行分隔。
(3)target_link_libraries
用於設置鏈接庫。很多功能需要使用系統或者第三方的庫函數,通過該選項可以配置執行文件鏈接的庫文件,其第一個參數與add_executable相同,是可執行文件的名稱,後面依次列出需要鏈接的庫。此處編譯的Publisher和Subscriber沒有使用其他庫,添加默認鏈接庫即可。
(4)add_dependencies
用於設置依賴。在很多應用中,我們需要定義語言無關的消息類型,消息類型會在編譯過程中產生相應語言的代碼,如果編譯的可執行文件依賴這些動態生成的代碼,則需要使用add_dependencies添加${PROJECT_NAME}_generate_messages_cpp配置,即該功能包動態產生的消息代碼。該編譯規則也可以添加其他需要依賴的功能包。
以上編譯內容會幫助系統生成兩個可執行文件:talker和listener,放置在工作空間的~/catkin_ws/devel/lib/路徑下。
CMakeLists.txt修改完成後,在工作空間的根路徑下開始編譯:

$ cd ~/catkin_ws
$ catkin_make

運行Publisher與Subscriber

首先要設置環境變量:

$ cd ~/catkin_ws
$ source ./devel/setup.bash

也可以將環境變量的配置腳本添加到終端的配置文件中:

$ echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
$ source ~/.bashrc

設置好之後就可以進入啓動步驟了:

1.請出ROS master

$ roscore

2.啓動Publisher

$ rosrun learning_communication talker

在這裏插入圖片描述
3.啓動Subscriber

$ rosrun learning_communication listener

/在這裏插入圖片描述
//可以看到,消息已經實現同步的發佈和訂閱了
若關閉talker的話,會看待listener馬上停止

在這裏插入圖片描述
下面說一下ROS下 上位機與Arduino單片機通訊

ROS下 上位機與Arduino單片機通訊

對於Arduino IDE的配置安裝和串口通訊,請點這裏
Publisher示例
主要就是如何通過rosserial創建publisher
例子來自Arduino IDE,File->Example->ros_lib下的HelloWord,下面給大家簡單註釋一下

#include <ros.h>
#include <std_msgs/String.h> 

//創建節點句柄
ros::NodeHandle  nh;

// 聲明一個消息對象str_msg。其參數爲data內容爲消息內容。
std_msgs::String str_msg;

// 發佈一個話題,名字叫做chatter,消息內容
ros::Publisher chatter("chatter", &str_msg);

char hello[13] = "hello world!";

void setup()
{
  nh.initNode();
  nh.advertise(chatter);
}

void loop()
{
  str_msg.data = hello;
  chatter.publish( &str_msg );//發佈一個消息   話題.publish(&消息)
  nh.spinOnce();
  delay(1000);
}

接下來打開終端分別運行
1.把ros master請出來

roscore

2.新終端運行,/dev/ttyUSB0爲Arduino設備

rosrun rosserial_python serial_node.py /dev/ttyUSB0

3.顯示主題chatter,獲取Arduino板反饋的信息

rostopic echo chatter

Subscriber示例
主要就是如何通過rosserial創建subscriber,點亮Arduino上的LED燈
例子來自Arduino IDE,File->Example->ros_lib下的blink,下面給大家簡單註釋一下

/* 
 * rosserial Subscriber Example
 * Blinks an LED on callback
 */
//必需包含的ros頭文件和消息頭文件
#include <ros.h>
#include <std_msgs/Empty.h>

ros::NodeHandle  nh;

//創建回調函數messageCb,必需傳遞常量消息引用值作爲參數,這裏函數是messageCb。
//消息類型是std_msgs::Empty,消息名稱是toggle_msg
//在函數內,我們可以引用toggle_msg,但它是空的,就不必要了。
//Arduino每次收到信息就會點亮燈。
void messageCb( const std_msgs::Empty& toggle_msg){
  digitalWrite(13, HIGH-digitalRead(13));   // blink the led
}

//這裏實例化訂閱,有兩個參數,主題名toggle_led和回調函數名,標明主題的消息類型std_msgs::Empty
ros::Subscriber<std_msgs::Empty> sub("toggle_led", &messageCb );

void setup()
{ 
  //初始化ROS節點處理,並宣告所有的發佈或訂閱
  pinMode(13, OUTPUT);
  nh.initNode();
  nh.subscribe(sub);
}

void loop()
{  
  //在Arduino的loop函數,調用nh.spinOnce(),這樣所有的ROS回調函數就會被處理。
  nh.spinOnce();
  delay(1);
}

1.請出master

$ roscore

2.新終端運行,/dev/ttyUSB0爲Arduino設備

$ rosrun rosserial_python serial_node.py /dev/ttyUSB0

3.發佈主題,點亮Arduino板上的LED燈

$ rostopic pub toggle_led std_msgs/Empty --once
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章