Opensplice的概述


之前搞過一段時間的Opensplice,感覺工具沒有RTI的多,而且不付費工具並沒有全公開。一點總結分享給大家,同時以防自己忘記。

1. 下載

官網(http://www.prismtech.com/dds-community)下載速度非常慢(大約10k/s),我用的事ubuntu16.04,自己下載的opensplice分享一下。鏈接: https://pan.baidu.com/s/1VLr90VLgfkEJ8iTsi8jUNg 提取碼: mbqr

2. 安裝

按照官網的指導安裝。我將下載文件保存到~/目錄。首先解壓:

$ tar -xzf VortexOpenSplice-6.7.180404OSS-HDE-x86_64.linux-gcc5.4.0-glibc2.23-installer.tar.gz

解壓收會多出一個HDE目錄。官網的安裝教程中說要執行一次替換:

sed -i 's/@@INSTALLDIR@@/$PWD/g' HDE/x86_64.linux/release.com

我打開我的release.com看了一下,沒有包含@@INSTALLDIR@@,估計在這個版本的沒有包含,其他版本中有。所以不需要運行上面的命令。

release.com中包含了許多環境變量,比如:OSPL_HOME、PATH、LD_LIBRARY_PATH、CPATH、OSPL_TMPL_PATH、VORTEX_DIR。需要source一下:

$ source /home/leon/HDE/x86_64.linux/release.com

每次啟動一個都需要執行一次,為了方便,將這個命令添加到.bashrc中。每次啟動一個terminal,會出現一個<<< Vortex OpenSplice HDE Release 6.7.180404OSS For x86_64.linux, Date 2018-04-04 >>>,可以在release.com中,將這行去掉。

3. opensplice的架構(自己的簡單理解,不一定十分准確)

用過ros的人都知道,ros的每個node通過master查詢對方的url,然后通過url訪問各節點的信息。也就是說節點間傳輸信息依靠的是url,並非信息本身。如下圖

                                              圖1 ROS

 

opensplice的架構是,首先定義好每個信息(topic),將信息存儲到GDS(Global Data Space)中,然后動態的分配publisher與subscriber。雖然數據在程序執行之初已經靜態分配,但可以動態的分配publisher與subscriber。從而實現去中心化,可以部分實現動態建立拓撲結構。如下圖。

                                                                                                  圖2 OpenSplice

4. 建立參與者

從圖2可以看出,所有的topic存儲在全局數據空間(GDS,Global Data Space)中,並且相應的數據已經建立完畢,那么需要定義訪問對象,即誰來讀取,誰來寫入。在這里說明一下,雖然GDS中的信息已經定義完畢,但是由publisher與subscriber是動態分配的。

如果按照ioscpp2標准,生成的參與者的代碼應為:

// Creates a domain participant in the domain identified by the number: 18.
dds::domain::DomainParticipant dp(18);

5. 設置Topic。

Topic支持很多vendors,比如IDL、XML、protobuf等。這里重點介紹IDL。

(1)Topic是由一個type、一個唯一的name和一系列的services組成。

Topic type是由IDL(Interface Definition Language[1])表示,如下代碼就是一個簡單的DataScope.idl文件。

module DataComm {
struct DataCommType { short id; float data1; float data2; }; #pragma keylist DataCommType id };

.idl文件看起來像是C語言中的結構體。DataComm是指idl的范圍(scope);DataCommType是指idl的數據內容(包含的信息);#pragma keylist指明了DataCommType的鍵值(keys),每個鍵值將標識特定的數據流; 更准確地說,在DDS中,每個鍵值都標識一個Topic實例。在

(2)將鍵值生成特定的代碼

這里opensplice已經有一整套工具用於將IDL生成指定語言的代碼:idlpp

[ -h ]
[ -b <ORB-template-path> ]
[ -n <include-suffix> ]
[ -I <path> ]
[ -D <macro>[=<definition>] ]
< -S | -C >
< -l (c | c++ | cpp | java | cs | isocpp | isoc++ | c99 | simulink) >
[ -F ]
[ -j [old]:<new>]
[ -o <dds-types> | <custom-psm> | <no-equality> | <deprecated-c++11-mapping>]
[ -d <output-directory> ]
[ -P <dll_macro_name>[,<header_file>] ]
[ -N ]

比如,需要將idl文件生成C++格式的文件,並保存到某個目錄中,那么需要執行:

$ idlpp -l isocpp2 -d gen DataScope.idl

上述命令將idlfiles.idl文件轉換到成isocpp2標准的c++文件,並存儲到當前的gen目錄下。生成的文件有DataScope_DCPS.hpp,DataScope.cpp,DataScope.h,DataScopeSplDcps.cpp,DataScopeSplDcps.h。

建立Topic的代碼應為:

// Create the topic
dds::topic::Topic<DataComm::DataCommType> topic(dp, "DDataComm");

代碼中的兩個參數分別聲明了自己的參與者的GDS與Topic的名稱。

6. 寫數據

Topic中在publisher與subscriber流動, 所以在定義讀寫數據之前應先定義publisher與subscriber,然后定義數據的讀寫。這里先首先定義寫數據,publisher。

// Create the Publisher and DataWriter
dds::pub::Publisher pub(dp);
dds::pub::DataWriter<DataComm::DataCommType> dw(pub, topic);

參與者dp2是數據的publisher,同時定義了寫數據dw。

將數據寫入topic,數據寫入topic的方式主要由兩種:

// Write the data
DataComm::DataCommType sdata(2, 26.5F, 74.0F);
dw.write(sdata);

或者

// Write data using streaming operators (same as calling dw.write(...))
dw << DataComm::DataCommType(2, 26.5F, 74.0F);

這里,兩種方式都是將數據2,26.5以及74.0分別寫入到之前定義的idl的id,data1以及data2中。第二種方式是通過數據流的方式寫入。

7. 讀數據

接下來定義讀數據,subscriber。由於讀數據與寫數據不是同一個參與者,所以,需要重新定義參與者,並且需要重新定義要接收的Topic。

// Creates a domain participant in the domain identified by the number: 19.
dds::domain::DomainParticipant dp(19);

// Create the topic
dds::topic::Topic<DataComm::DataCommType> topic(dp, "DDataComm");

// create a Subscriber and DataReader
dds::sub::Subscriber sub(dp);
dds::sub::DataReader<DataComm::DataCommType> dr(sub, topic);

重新定義了參與者dp,同時定義了要接收的topic,以及讀數據dr。

由於數據一般是周期發出的,這里采用周期讀取的方式將數據讀入idl。

while (true) {
  auto samples = dr.read();
  std::for_each(samples.begin(),
           samples.end(),
           [](const dds::sub::Sample<DataComm::DataCommType>& s) { 
             std::cout << s.data() << std::endl; 
          }); 
  std::this_thread::sleep_for(std::chrono::seconds(1)); 
}

怕循環的速度太快,所以采用sleep的方式讓循環降速。

8. 實例代碼

//OpslPub.cpp

#include <iostream> #include "gen/DataControl_DCPS.hpp" #include <thread> // std::thread, std::this_thread::sleep_for #include <chrono> #include "util.hpp" int main(int argc, char* argv[]) { if (argc < 2) { std::cout << "USAGE:\n\t tspub <sensor-id>" << std::endl; return -1; } int sid = atoi(argv[1]); const int N = 100; dds::domain::DomainParticipant dp(0); dds::topic::Topic<DataComm::DataCommType> topic(dp, "DDataComm"); dds::pub::Publisher pub(dp); dds::pub::DataWriter<DataComm::DataCommType> dw(pub, topic); const float avgT = 25; const float avgH = 0.6; const float deltaT = 5; const float deltaH = 0.15; // Initialize random number generation with a seed  srandom(clock()); // Write some temperature randomly changing around a set point float temp = avgT + ((random() * deltaT) / RAND_MAX); float hum = avgH + ((random() * deltaH) / RAND_MAX); DataComm::DataCommType myData(sid, temp, hum); for (unsigned int i = 0; i < N; ++i) { dw.write(myData); std::cout << "DW << " << myData << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); temp = avgT + ((random() * deltaT) / RAND_MAX); myData.data1(temp); hum = avgH + ((random() * deltaH) / RAND_MAX); myData.data2(hum); } return 0; }
//OpslSub.cpp

#include <iostream> #include <algorithm> #include "gen/DataControl_DCPS.hpp" #include <thread> // std::thread, std::this_thread::sleep_for #include <chrono> #include "util.hpp" int main(int argc, char* argv[]) { dds::domain::DomainParticipant dp(0); dds::topic::Topic<DataComm::DataCommType> topic(dp, "DDataComm"); dds::sub::Subscriber sub(dp); dds::sub::DataReader<DataComm::DataCommType> dr(sub, topic); while (true) { auto samples = dr.read(); std::for_each(samples.begin(), samples.end(), [](const dds::sub::Sample<DataComm::DataCommType>& s) { std::cout << s.data() << std::endl; }); std::this_thread::sleep_for(std::chrono::seconds(1)); } return 0; }

在這里需要重載“<<”操作符,否則cout會有問題,代碼如下:

//重載<<操作符
std::ostream&
operator << (std::ostream& os, const DataComm::DataCommType& dc)
{
  os << "(id = " << dc.id() << ", data1 = " << dc.data1() << ", data2 = " << dc.data2() << ")"; return os; }

程序運行:

-> 在運行OpslPub.cpp時,需要加入id號;

-> 如果運行OpslSub.cpp與OpslPub.cpp,那么OpslPub.cpp會發出id號及兩個隨機浮點數,OpslSub.cpp會接收id號以及這兩個隨機數;

-> 如果同時運行多個OpslSub.cpp與OpslPub.cpp,且使用多個不同的id。那么所有的OpslSub.cpp都會接收,全部OpslPub.cpp發出的信息。

 

注意:

-> 如果在publish和subscribe的代碼中,在定義Topic時,名字("DDataComm")要一致,否則,不能接收。

 [1] OpenSplice采用OMG IDL的一個子集作為子集的標准,如果不熟悉可以參考:http://blog.sciencenet.cn/blog-81613-320261.html

 

原創博文,轉載請標明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM