之前搞過一段時間的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
原創博文,轉載請標明出處。
