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

 

原创博文,转载请标明出处。

posted on 2020-04-25 11:43  LeonHuo  阅读(2928)  评论(1编辑  收藏  举报

导航