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
原创博文,转载请标明出处。