Opensplice的Topic简介

在Overview中,简单介绍了一下Topic,下面详细介绍一下Topic。有不对的地方希望大家可以在留言区告诉我。

Topic是由名称(name)、类型(type)以及服务(QoS)组成。

1. Topic详解

(1)Topic Types

Opensplice支持不同语法的topic,比如:IDL,XML等。同时也支持一些其他vendor的topic,比如:PrismTech、Protobuf等。这里,我们还是只关注IDL。

IDL的Primitive Types主要有:

Table1 IDL Primitive Types
Primitive Type  Size (bits)
boolean  8
octet
char  8
short  16
unsigned short  16
long  32
unsigned long  32
long long  64
unsigned long long   64
 float 32 
 double  64

 在IDL中,没有int,这里的short、long以及long long相当于C99中的int16_t、int32_t以及int64_t。这里对于C++的转化基本遵循与IDL基本相同的类型,octet转换为char类型。

IDL支持的template Types(个人理解主要的功能是为了兼容数据类型)主要由两种:

Table 2 IDL Template Types
Template Type Example
string<length = UNBOUNDED$>

string s1;
string<32> s2;

sequence<T,length = UNBOUNDED>

sequence<octet> oseq;
sequence<octet, 1024> oseq1k;
sequence<MyType> mtseq;
sequence<MyType, $10>$ mtseq10;

IDL的string只能根据其最大长度进行参数化,然而sequence可以根据其最大长度以及其包含的类型进行参数化。sequence有些类似于C++中的vector。

注意:如果没有提供最大长度,那么相应的类型存储空间为无穷,这样就意味着Opensplice会为这个类型尽可能多的分配存储空间。

IDL支持的Constructed Types 类型主要由三种:

Table 3 IDL Constructed Types 
Constructed Types Examples
enum enum Dimension {1D, 2D, 3D, 4D};
struct

struct Coord1D { long x;};
struct Coord2D { long x; long y; };
struct Coord3D { long x; long y; long z; };
struct Coord4D { long x; long y; long z,
unsigned long long t;};

union

union Coord switch (Dimension) {
case 1D: Coord1D c1d;
case 2D: Coord2D c2d;
case 3D: Coord3D c3d;
case 4D: Coord4D c4d;
};

这里应该说明一下,每个topic是一个struct类型,这个struct类型可以包含enum、struct、union类型,同样也可以包含primitive types与template types类型。同样也支持DDS支持的或者用户自定义的多维数组。

(2)Topic Keys, Instances and Samples

每个Topic相当于class(类),可以声明一个或若干个instance。Topic有其特殊的地方,Topic需要用key-set(由pragma keylist声明)来区分不同instances,比如下面代码中的DataCommType,用id作为key-set,每个Key-set可能为空,也可能包含由任意数量组成,为空的称为keyless topic,不为空的称为keyed topic(但key的类型存在一些限制:采用Primitive Types(见Table 1)、enum或者string。Key不能是Constructed Types、array或者sequence类型组成。),这两者之间由本质区别:

-> keyless topic在定义的时候仅有一个instance,可以理解为唯一instance;

-> key topic在定义的时候每个key-set对应一个instance。

这里举一个key topic与没有属性的keyless topic

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

struct KeylessDataCommType {
    short id;
    float data1;
    float data2;
  };
#pragma keylist KeylessDataCommType
};

上述两个topic由#pragma keylist定义的有key-set id的Topic,以及没有属性的key-set的topic。这两个Topic定义的代码可以表示为:

dds::topic::Topic<DataComm::DataCommType> topic(dp2, "DDataComm");
dds::topic::Topic<DataComm::KeylessDataCommType> klTopic(dp, "KLDataComm");

上述例子中,topic对应不同的id产生不同的instance,而klTopic仅仅只有一个instance,不论id是否变化。

 

Topic instances是运行时实体,DDS全程追踪全部实体是否满足:

-> 存在writer;

-> Topic instance是否在系统中是第一次运行;

-> Topic instance是否在系统中移除。

Topic instance影响这系统的reader,同时也影响着整个系统的存储结构。

像我们刚才提起的两种topic的区别,在写一个keyless topic的时候实际上就是写唯一的一个instance(每次写只能修改唯一的topic),在写key topic的时候,可以针对不同的key-set修改不同instance。

针对keyless topic的写操作的代码可以表示为:

dds::pub::DataWriter<DataComm::KeylessDataCommType> kldw(pub, kltsTopic);
DataComm::KeylessDataCommType klts(1, 26.0F, 70.0F);
kldw.write(klts);
kldw << DataComm::KeylessDataCommType(2, 26.0F, 70.0F);

在这个代码的基础上,读操作的相应的顺序为图1所示。

 Opensplice的Topic简介

                                                 图1 keyless topic的写入/读出顺序

针对key topic 写操作的代码可以表示为:

dds::pub::DataWriter<DataComm::DataCommType> dw(pub, topic);
DataComm::DataCommType ts(1, 26.0F, 70.0F);
dw.write(ts);
dw << DataComm::DataCommType(2, 26.0F, 70.0F);

在上述代码的基础上,读写顺序应为如图2所示。

Opensplice的Topic简介

                                                                   图2 key topic的写入/读出顺序

总之,Topic可以理解为面向对象语言中的类,一个key代表一个instance。每个topic instance由Opensplice管理,且分配相应的内存资源。在实际应用中,每个topic instance对应一个硬件设备。在系统运行的过程中,每由一个设备接入,系统会监测到,同时为设个设备分配相应的topic instance。

2. 范围信息(Scoping information)

DDS为范围信息提供两种机制:域(Domain)和分区(Partition)。

(1)域

域建立一个虚拟网络,链接已加入它的所有DDS应用程序。 除非由用户应用程序明确调解,否则跨域不会发生任何通信。

(2)分区

域可以进一步组织成分区,其中每个分区可以表示topic的逻辑分组。

DDS的分区由名字描述,比如:“ensorDataPartition, CommandPartition, LogDataPartition”。为了发布(publish)和订阅(subscribe)topic中的数据,分区必须显式的链接。

Opensplice的Topic简介

                                                              图3 Domains and partitions in DDS

DDS提供的用于加入分区的机制非常灵活,因为发布者或订阅者可以通过提供其全名来加入,比如:“SensorDataPartition”。或者可以加入所有匹配正则表达式的分区,例如Sens *或* Data *。支持的正则表达式符合POSIX标准。

分区还可用于隔离主题实例。这里没有看到具体的例子暂时保留。

3. 内容过滤(Content Filtering)

内容的过滤可以约束创建的topic instance中的值。

当订阅了(subscribe)content-filtered topic,在所有发布(publish)的消息中,只有符合topic滤波器的消息可以被接收。

Table 3 Legal operators for DDS Filters and Query Conditions
Constructed Type Example
= equal
<> not equal
> greater than
< less than
>= greater than or equal
<= less than or equal
BETWEEN between and inclusive range
LINK matches a string pattern

Content-filtered topic是非常有用的:

-> Content-filtered topic限制了使用内存的大小;

-> 通过检查某些数据属性,可以使用过滤来简化应用程序。

举个例子,在上节中的topic中,如果我们想得到data1在20.5~21.5之外,data2在30~50之外的数据,那么滤波器表达式可以为:

((data1 NOT BETWEEN 20.5 AND 21.5)
OR
(data2 NOT BETWEEN 30 AND 50))

Content-filtered topic在代码中的建立如下所示:

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

// Define the filter expression
std::string expression =
"(data1 NOT BETWEEN %20.5 AND %21.5) \
OR \
(data2 NOT BETWEEN %30 and %50)";

// Define the filter parameters
std::vector<std::string> params = {"20.5", "21.5", "30", "50"};

// Create the filter for the content-filtered-topic
dds::topic::Filter filter(expression, params);

// Create the ContentFilteredTopic
dds::topic::ContentFilteredTopic<DataComm::DataCommType> cfTopic(topic, "CFDDataComm", filter);
dds::sub::Subscriber sub(dp);

//This data reader will only receive data that matches the content filter
dds::sub::DataReader<DataScope::DataScopeType> dr(sub, cfTopic);

 通过上述表达式,cfTopic仅接收data1在20.5~21.5之外,data2在30~50之外的数据。

4. 程序代码

keyless topic与content filter部分publish代码。

// OpslPubkeyless.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::KeylessDataCommType> kltopic(dp, "DataComm");
    dds::pub::Publisher pub(dp);
    dds::pub::DataWriter<DataComm::KeylessDataCommType> dw(pub, kltopic);

    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::KeylessDataCommType 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;
}

keyless topic与content filter部分subscribe代码。

// OpslSubkeyless.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[]) {
    if (argc < 2) {
        std::cout << "USAGE:\n\ttssub <filter-expression>"
                << std::endl;
        exit(-1);
    } 

    dds::domain::DomainParticipant dp(0);
    dds::topic::Topic<DataComm::KeylessDataCommType> kltopic(dp, "DataComm");
    dds::sub::Subscriber sub(dp);
    //dds::sub::DataReader<DataComm::KeylessDataCommType> dr(sub, kltopic);

    dds::topic::Filter filter(argv[1]);
    dds::topic::ContentFilteredTopic<DataComm::KeylessDataCommType> cfTopic(kltopic, "CFDataComm", filter);
    
    dds::sub::DataReader<DataComm::KeylessDataCommType> dr(sub, cfTopic);

    while (true) {
        auto samples = dr.read();
        //std::cout << samples->id() << " " << samples->data1() << " " << samples->data2() << std::endl;
        //std::cout << samples.data() << std::endl;
        std::for_each(samples.begin(),
            samples.end(),
            [](const dds::sub::Sample<DataComm::KeylessDataCommType>& s) {
                std::cout << s.data() << std::endl;
            });
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    return 0;
}

将这两部分代码加入到上一章节的代码之中。修改上一章节的OpslSubkey.cpp,如下:

// 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[]) {
    if (argc < 2) {
        std::cout << "USAGE:\n\ttssub <filter-expression>"
                << std::endl;
        exit(-1);
    } 

    dds::domain::DomainParticipant dp(0);
    dds::topic::Topic<DataComm::DataCommType> topic(dp, "DDataComm");
    dds::sub::Subscriber sub(dp);
    

    dds::topic::Filter filter(argv[1]);
    dds::topic::ContentFilteredTopic<DataComm::DataCommType> cfTopic(topic,  "CFDataComm",  filter);
    dds::sub::DataReader<DataComm::DataCommType> dr(sub, cfTopic);

    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;
}

程序运行:

-> keyless topic与key topic混合运行:

---> 如果OpslSub.cpp中的key topic的名称与OpslSubkeyless.cpp中的keyless topic的名字相同,那么在运行这两个程序时,会报运行时错误,如下:

terminate called after throwing an instance of 'dds::core::Error'
  what():  Error: Failed to create Topic
========================================================================================
Context     : dds::topic::detail::Topic<T>::Topic
Date        : Thu Dec 13 07:52:48 CST 2018
Node        : xxxx-GL552JX
Process     : OpslSub <5848>
Thread      : main thread 7efed7c99740
Internals   : TTopicImpl.hpp/203/6.7.180404OSS
----------------------------------------------------------------------------------------
Report      : Error: Failed to create Topic
Internals   : dds::topic::detail::Topic<T>::Topic/TTopicImpl.hpp/203
----------------------------------------------------------------------------------------
Report      : Create kernel entity failed. For Topic: <DDataComm>
Internals   : u_topicNew/u_topic.c/235
----------------------------------------------------------------------------------------
Report      : Precondition not met: Create Topic "DDataComm" failed: typename <DataComm::KeylessDataCommType> differs exiting definition <DataComm::DataCommType>.
Internals   : v_topicNew/v_topicImpl.c/478

Aborted (core dumped)

所以在OpslPub.cpp中的key topic的名称为“DDataComm”,OpslPubkeyless.cpp中的keyless topic名称“DataComm”。

---> 如果将OpslSub.cpp中的key topic的名称为“DataComm”,OpslSubkeyless.cpp中的keyless topic名称“DDataComm”,那么在运行OpslPub.cpp与OpslPubkeyless.cpp之后,运行OpslSub.cpp会报运行时报和上面一样的错误。

这是因为运行OpslSub.cpp之后,要接收的topic的类型与要接收相同名称的topic类型不匹配,所以产生了错误。

-> keyless topic独立运行,且测试content fliter:

---> 如果运行多个OpslPubkeyless.cpp与OpslSubkeyless.cpp,且每个OpslSubkeyless.cpp运行独立content filter,例如: “data1 > 26 AND data2 > 0.65”。那么每个OpslPubkeyless.cpp发出的keyless topic都会被OpslSubkeyless.cpp接收到。从使用的情况看,与key topic没有区别

-> 在运行程序时同样需要重针对keyless topic重载“<<”操作符。

上一篇:幅度、频率和相位可调的双通道DDS 信号发生器


下一篇:Openssl生成RSA-PSS证书