Continues migration with DDS, Common

This commit is contained in:
torsten committed 2026-07-17 12:41:42 +02:00
1 parent 9b15cee841
commit fa48db111e
668 files changed
+111841 -2

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
#
# $Id$
# $HeadURL$
# $Author$
#
ADD_SUBDIRECTORY(DDSConnector)
ADD_SUBDIRECTORY(DTC)
ADD_SUBDIRECTORY(DTS)
ADD_SUBDIRECTORY(FTC)
ADD_SUBDIRECTORY(FTS)
ADD_SUBDIRECTORY(Generic)
ADD_SUBDIRECTORY(Routing)
ADD_SUBDIRECTORY(Core)
ADD_SUBDIRECTORY(QosProfile)
ADD_SUBDIRECTORY(DdsUnitTests)
+97
View File
@@ -0,0 +1,97 @@
#
# $Id$
# $HeadURL$
# $Author$
#
INCLUDE(MacroRTIDDSGen)
INCLUDE(MacroCodeGenIdl)
SET(xml_FILE core.xml)
#
# --- Library svcCoreCommon / ddscore --------------------------------------------------------------------------------
#
SET(SRCS_common
core_export.h
function_reply_object.cpp
function_reply_object.h
logger.cpp
logger.h
)
# Generates core.idl / core_enums.idl
CODEGEN_IDL_SVC(SRCS_common ${xml_FILE})
# Generate core.sql
CODEGEN_SQL(SRCS_common ${xml_FILE})
# Generate *.cpp/*.hpp from core_enums.idl / core.idl
RTIDDSGEN(SRCS_common core_enums.idl)
RTIDDSGEN(SRCS_common core.idl)
# Generate core_dt[s,c]_types.h, core_ft[s,c]_types.h
CODEGEN_TOPIC_TYPEDEFS(SRCS_common ${xml_FILE})
# generate core_[d|f]tc.h
CODEGEN_CLIENT_TOPICS_IFC(SRCS_common ${xml_FILE})
NGL_ADD_LIBRARY(WS AAR HLA SV ddscore SHARED ${SRCS_common})
NGL_TARGET_LINK_LIBRARIES(ddscore
PRIVATE
NGLLogger
PUBLIC
LtsUtilsCoreHeaderOnly
ddsdts
Qt5::Core
RTI::nddsc # DDS_GroupDataQosPolicy_initialize (wo auch immer das herkommt)
RTI::nddscore # RTI_LOG_ANY_FAILURE_s (wo auch immer das herkommt)
RTI::nddscpp2
)
NGL_TARGET_INCLUDE_DIRECTORIES(ddscore
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../../
${CMAKE_CURRENT_BINARY_DIR}/../../
)
NGL_INSTALL_TARGET(ddscore)
#
# --- Library svcCoreClient --------------------------------------------------------------------------------
#
SET(SRCS_client
core_export.h
pacemaker.h
pacemaker.cpp
topic_lock_master.h
topic_lock_master.cpp
)
# generate core_[d|f]tc_impl.[cpp|h]
CODEGEN_CLIENT_TOPICS_IMPL(SRCS_client ${xml_FILE})
NGL_ADD_LIBRARY(AAR SV WS HLA svcCoreClient SHARED ${SRCS_client})
NGL_TARGET_INCLUDE_DIRECTORIES(svcCoreClient
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../../
${CMAKE_CURRENT_BINARY_DIR}/../../
)
NGL_TARGET_LINK_LIBRARIES(svcCoreClient
PRIVATE
RTI::nddscpp2
ddsconnector
PUBLIC
ddscore
ddsftc
)
NGL_INSTALL_TARGET(svcCoreClient)
#
# --- Library svcCoreServer --------------------------------------------------------------------------------
# not used (no core service needed)
#
#
# --- Topic Type Plugin --------------------------------------------------------------
#
CREATE_TOPIC_TYPE_PLUGIN(${xml_FILE})
+204
View File
@@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<service namespace="Core" abbreviation="CORE" procNames="Core" exerciseDependency="Global">
<rcs>
<Id Id="$Id$"/>
<HeadURL HeadURL="$HeadURL$"/>
<Author Author="$Author$"/>
</rcs>
<tables>
<table name="ft_reply_state" comment="static data (not project specific) generate from enum">
<column name="id" datatype="integer" isPrimaryKey="1"/>
<column name="name" datatype="text"/>
</table>
<table name="replay_type" comment="Type of replay (Data for reports/events or situation)">
<column name="id" datatype="integer" isPrimaryKey="1"/>
<column name="name" datatype="text"/>
</table>
<table name="running_state">
<column name="id" datatype="integer" isPrimaryKey="1"/>
<column name="name" datatype="text"/>
</table>
<table name="service_group_type">
<column name="id" datatype="integer" isPrimaryKey="1"/>
<column name="name" datatype="text"/>
</table>
<table name="z_reference">
<column name="id" datatype="integer" isPrimaryKey="1"/>
<column name="name" datatype="text"/>
</table>
</tables>
<views/>
<topicTypes>
<topicType name="BaseEntity">
<column name="id" idlType="long" isKey="1"/>
<column name="name" idlType="string"/>
<column name="isDeleted" idlType="boolean"/>
<column name="isUsed" idlType="boolean" comment="Defines if it is used by others (i.e. foreign key constraint)"/>
</topicType>
<topicType name="BaseEntityTranslatable">
<column name="id" idlType="long" isKey="1"/>
<column name="name" idlType="string" isTranslatable="1"/>
<column name="isDeleted" idlType="boolean"/>
<column name="isUsed" idlType="boolean" comment="Defines if it is used by others (i.e. foreign key constraint)"/>
</topicType>
<topicType name="BaseEntityWithSampleTime" baseValueType="BaseEntity">
<column name="sampleTime" idlType="long long" comment="Timestamp"/>
</topicType>
<topicType name="CurrentRunningState" isDataObject="1" routeToClient="0" routeToServer="0" routeInReplay="0">
<column name="pid" idlType="long long" isKey="1"/>
<column name="runningState" idlType="RunningState"/>
<column name="ports" idlType="sequence&lt;PortInformation&gt;" isOptional="1"/>
<column name="errorString" idlType="string" isOptional="1"/>
</topicType>
<topicType name="FunctionReply">
<column name="id" idlType="long"/>
<column name="errorString" idlType="string"/>
<column name="errorParams" idlType="sequence&lt;string&gt;"/>
<column name="additionalInfo" idlType="string"/>
</topicType>
<topicType name="FunctionReplyWithState" baseValueType="FunctionReply">
<column name="replyState" idlType="FtReplyState"/>
</topicType>
<topicType name="GeoPositionAbsoluteTime" baseValueType="GeoPosition">
<column name="absoluteTime" idlType="long long" comment="Timestamp"/>
</topicType>
<topicType name="GeoPositionRelativeTime" baseValueType="GeoPosition">
<column name="relativeTime" idlType="long long" comment="Timestamp"/>
</topicType>
<topicType name="GeoPosition" baseValueType="GeoPosition2D">
<column name="z" idlType="double" comment="height above ground"/>
</topicType>
<topicType name="GeoPosition2D">
<column name="lon" idlType="double" comment="[m]"/>
<column name="lat" idlType="double" comment="[m]"/>
</topicType>
<topicType name="LockEntityError">
<column name="workstationName" idlType="string"/>
<column name="operatorName" idlType="string"/>
</topicType>
<topicType name="TryLockEntityReply" baseValueType="FunctionReply">
<column name="alreadyLocked" idlType="LockEntityError" isOptional="1"/>
</topicType>
<topicType name="LockedEntity" isDataObject="1" routeToClient="0" routeToServer="0" routeInReplay="0" baseValueType="LockedEntityBase">
<column name="id" idlType="long long" isKey="1"/>
</topicType>
<topicType name="LockedEntityBase">
<column name="operatorId" idlType="long"/>
<column name="workstationId" idlType="long"/>
<column name="exerciseId" idlType="long"/>
<column name="entityId" idlType="long long"/>
<column name="entityTypeName" idlType="string"/>
</topicType>
<topicType name="TryLockEntityRequest" baseValueType="LockedEntityBase">
<column name="id" idlType="long"/>
</topicType>
<topicType name="TryUnlockEntityRequest">
<column name="id" idlType="long"/>
</topicType>
<topicType name="Orientation">
<column name="heave" idlType="double"/>
<column name="pitch" idlType="double"/>
<column name="roll" idlType="double"/>
</topicType>
<topicType name="PartitionTime" isDataObject="1" routeToClient="1" routeToServer="0" routeInReplay="0">
<column name="timeStamp" idlType="long long"/>
</topicType>
<topicType name="PortInformation">
<column name="number" idlType="long"/>
<column name="type" idlType="PortType"/>
<column name="properties" idlType="string"/>
</topicType>
<topicType name="RelativePosition">
<column name="offsetX" idlType="double" comment="in meter"/>
<column name="offsetY" idlType="double" comment="in meter"/>
<column name="offsetZ" idlType="double" comment="in meter"/>
</topicType>
<topicType name="RelativePositionAbsoluteTime" isDataObject="1" autosubscribe="1" routeToClient="1" routeToServer="0" routeInReplay="0" baseValueType="RelativePosition">
<column name="absoluteTime" idlType="long long" comment="TimeStamp"/>
</topicType>
<topicType name="RelativePositionRelativeTime" isDataObject="1" autosubscribe="1" routeToClient="1" routeToServer="0" routeInReplay="0" baseValueType="RelativePosition">
<column name="relativeTime" idlType="long long" comment="TimeStamp"/>
</topicType>
<topicType name="RemoveElementRequest">
<column name="id" idlType="long"/>
</topicType>
<topicType name="ReplayPartition" isDataObject="1" routeToClient="1" routeToServer="0" routeInReplay="0">
<column name="id" idlType="long" isKey="1"/>
<column name="name" idlType="string"/>
<column name="exerciseId" idlType="long"/>
<column name="workstationId" idlType="long"/>
<column name="operatorId" idlType="long"/>
<column name="replayType" idlType="ReplayType"/>
<column name="domainId" idlType="long"/>
<column name="tcpConnectionUrl" idlType="string"/>
</topicType>
<topicType name="Velocity">
<column name="x" idlType="double"/>
<column name="y" idlType="double"/>
<column name="z" idlType="double"/>
</topicType>
</topicTypes>
<functionTopics>
<functionTopic name="TryLockEntity" requestTopicType="TryLockEntityRequest" replyTopicType="TryLockEntityReply" requestTopicName="TryLockEntityRequest" replyTopicName="TryLockEntityReply" routeInReplay="1"/>
<!-- <functionTopic name="TryLockEntity" requestTopicType="LockedEntityBase" replyTopicType="TryLockEntityReply" requestTopicName="TryLockEntityRequest" replyTopicName="TryLockEntityReply" routeInReplay="1"/>-->-->
<functionTopic name="TryUnlockEntity" requestTopicType="TryUnlockEntityRequest" replyTopicType="FunctionReply" requestTopicName="TryUnlockEntityRequest" replyTopicName="TryUnlockEntityReply" routeInReplay="1"/>
</functionTopics>
<enumerations>
<enumeration name="FtReplyState" table="ft_reply_state">
<attribute key="NotStarted" value="0"/>
<attribute key="InProgress" value="1"/>
<attribute key="Completed" value="2"/>
<attribute key="Error" value="4"/>
</enumeration>
<enumeration name="PortType">
<attribute key="Unknown" value="0"/>
<attribute key="WebSocketServer" value="1"/>
<attribute key="WebSocketPositionServer" value="2"/>
<attribute key="WebSocketAdlerServer" value="3"/>
</enumeration>
<enumeration name="PositionReportingSource">
<attribute key="Gps" value="0"/>
<attribute key="Wls" value="1"/>
<attribute key="Ops" value="2"/>
<attribute key="External" value="3"/>
<attribute key="SetByOperator" value="4"/>
<attribute key="MovementSimulation" value="5"/>
<attribute key="OpsTunnel" value="6"/>
<attribute key="OpsBuilding" value="7"/>
</enumeration>
<enumeration name="ReplayType" table="replay_type">
<attribute key="UnknownData" value="0"/>
<attribute key="SituationData" value="1"/>
<attribute key="ReportData" value="2"/>
<attribute key="EventData" value="3"/>
<attribute key="OtherData" value="4"/>
<attribute key="SnapshotData" value="5"/>
</enumeration>
<enumeration name="RunningState" table="running_state">
<attribute key="NotYetStarted" value="0"/>
<attribute key="Starting" value="1"/>
<attribute key="StartingError" value="2"/>
<attribute key="Running" value="3"/>
<attribute key="Stopping" value="4"/>
<attribute key="StoppedNormally" value="5"/>
<attribute key="StoppedError" value="6"/>
</enumeration>
<enumeration name="ServiceGroupType" table="service_group_type">
<attribute key="Master" value="0"/>
<attribute key="Core" value="1"/>
<attribute key="ExercisePreparation" value="2"/>
<attribute key="ExerciseExecution" value="3"/>
<attribute key="ExercisePaused" value="4"/>
<attribute key="ExerciseAfterActionReview" value="5"/>
<attribute key="Playback" value="6"/>
</enumeration>
<enumeration name="ZReference" table="z_reference">
<attribute key="ZrUndefined" value="0"/>
<attribute key="Zr_Unused" value="1"/>
<attribute key="ZrRelativeAGL" value="100"/>
<attribute key="ZrAbsolute" value="200"/>
<attribute key="ZrDrapedOnGround" value="300"/>
<attribute key="ZrRelativeToDefinedValue" value="400"/>
</enumeration>
</enumerations>
</service>
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <QtCore/qglobal.h>
#ifndef CORE_COMMON_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define CORE_COMMON_EXPORT
# else
# if defined(MAKE_DDSCORE_LIB)
# define CORE_COMMON_EXPORT Q_DECL_EXPORT
# ifndef WIN32
# ifdef NDDSUSERDllExport
# undef NDDSUSERDllExport
# endif
# define NDDSUSERDllExport Q_DECL_EXPORT
# endif
# else
# define CORE_COMMON_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
#ifndef CORE_CLIENT_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define CORE_CLIENT_EXPORT
# else
# if defined(MAKE_SVCCORECLIENT_LIB)
# define CORE_CLIENT_EXPORT Q_DECL_EXPORT
# else
# define CORE_CLIENT_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
#ifndef CORE_SERVER_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define CORE_SERVER_EXPORT
# else
# if defined(MAKE_SVCCORESERVER_LIB)
# define CORE_SERVER_EXPORT Q_DECL_EXPORT
# else
# define CORE_SERVER_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
+48
View File
@@ -0,0 +1,48 @@
#include "function_reply_object.h"
#include <QtCore/QString>
#include <QtCore/QCoreApplication>
namespace Core
{
FunctionReplyObject &FunctionReplyObject::arg(qint64 val)
{
m_args.push_back(std::to_string(val));
return *this;
}
FunctionReplyObject &FunctionReplyObject::arg(const std::string &str)
{
m_args.push_back(str);
return *this;
}
FunctionReplyObject &FunctionReplyObject::arg(const QString &str)
{
m_args.push_back(str.toStdString());
return *this;
}
std::string FunctionReplyObject::operator()() const
{
if (m_args.empty())
return m_str;
QString ret(QString::fromStdString(m_str));
for (const std::string &s : m_args)
ret = ret.arg(QString::fromStdString(s));
return ret.toStdString();
}
QString FunctionReplyObject::translated() const
{
QString ret(QCoreApplication::translate("dds", m_str.c_str()));
for (const std::string &s : m_args)
ret = ret.arg(QString::fromStdString(s));
return ret;
}
}
+95
View File
@@ -0,0 +1,95 @@
#pragma once
#include <string>
#include <vector>
#include "core_export.h"
#ifndef tr_lts
#define tr_lts(ctx, t) (t)
#endif
namespace Core
{
class CORE_COMMON_EXPORT FunctionReplyObject
{
public:
explicit FunctionReplyObject(int id = -1, const char *str = nullptr)
: m_str(str ? str : std::string())
, m_id(id)
{}
explicit FunctionReplyObject(const char *str)
: m_str(str ? str : std::string())
{}
explicit FunctionReplyObject(int id, const std::string &errStr, const std::vector<std::string> &errArgs)
: m_str(errStr)
, m_args(errArgs)
, m_id(id)
{}
explicit FunctionReplyObject(int id, std::string &&errStr, std::vector<std::string> &&errArgs)
: m_str(std::move(errStr))
, m_args(std::move(errArgs))
, m_id(id)
{}
explicit FunctionReplyObject(int id, const std::string &errStr, const std::vector<std::string> &errArgs, const std::string &additionalInfo)
: m_str(errStr)
, m_additionalInfo(additionalInfo)
, m_args(errArgs)
, m_id(id)
{}
explicit FunctionReplyObject(int id, std::string &&errStr, std::vector<std::string> &&errArgs, std::string &&additionalInfo)
: m_str(std::move(errStr))
, m_additionalInfo(std::move(additionalInfo))
, m_args(std::move(errArgs))
, m_id(id)
{}
~FunctionReplyObject() = default;
FunctionReplyObject &arg(qint64 val);
FunctionReplyObject &arg(const std::string &str);
FunctionReplyObject &arg(const QString &str);
bool error() const { return !m_str.empty(); }
void setId(int id) { m_id = id; }
FunctionReplyObject &setStr(const char *str) { m_str = str; return *this; }
FunctionReplyObject &setAdditionalInfo(const char *additionalInfo) { m_additionalInfo = additionalInfo; return *this; }
int id() const { return m_id; }
std::string str() const { return m_str; }
std::string additionalInfo() const { return m_additionalInfo; }
std::vector<std::string> args() const { return m_args; }
std::string operator()() const;
QString translated() const;
int64_t uniqueId() const { return m_uniqueId; }
void setUniqueId(int64_t uniqueId) { m_uniqueId = uniqueId; }
private:
std::string m_str;
std::string m_additionalInfo;
std::vector<std::string> m_args;
int64_t m_uniqueId = 0;
int m_id = -1;
};
}
+31
View File
@@ -0,0 +1,31 @@
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace Core
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "CORE.";
std::string loggerName;
const size_t len = strlen(name);
loggerName.reserve(sizeof(ns) + len);
loggerName.append(ns, sizeof(ns) - 1);
loggerName.append(name, len);
try
{
return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger( loggerName );
}
catch ( const std::exception &ex )
{
fprintf(stderr, "Error retrieving logger: %s", ex.what());
abort();
}
}
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <NGLLogger/INGLLogger.h>
#include "core_export.h"
namespace Core
{
using LOGGER_T = NGL::INGLLogger&;
CORE_COMMON_EXPORT LOGGER_T getLogger(const char *name);
}
+117
View File
@@ -0,0 +1,117 @@
#include "pacemaker.h"
#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <DDS/DTS/generic_dts.h>
#include <DDS/DTS/generic_dts_impl.h>
#include <DDS/DDSConnector/dds_connector.h>
namespace Core
{
Pacemaker::Pacemaker(DDSConnector::DdsConnector *ddsConnector)
: QObject(nullptr)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Core.Pacemaker"))
{
init(ddsConnector);
}
Pacemaker::~Pacemaker()
{
m_paceThread->requestInterruption();
m_paceThread->quit();
m_paceThread->wait();
delete m_paceThread;
m_publishTimer->deleteLater();
delete m_dtsCurrentRunningState;
}
void Pacemaker::setNewState(Core::RunningState newState, const QString &errorString)
{
if (QThread::currentThread() == this->thread())
setNewStateInternal(newState, errorString);
else
QTimer::singleShot(0, this, [this, newState, errorString]() { setNewStateInternal(newState, errorString); });
}
void Pacemaker::setNewStateInternal(Core::RunningState newState, const QString &errorString)
{
m_state.runningState(newState);
if (errorString.isEmpty())
m_state.errorString().reset();
else
m_state.errorString(errorString.toStdString());
publishState();
}
void Pacemaker::addPort(unsigned short number, Core::PortType portType, const QString &properties)
{
if (QThread::currentThread() == this->thread())
addPortInternal(number, portType, properties);
else
QTimer::singleShot(0, this, [this, number, portType, properties]() { addPortInternal(number, portType, properties); });
}
void Pacemaker::addPortInternal(unsigned short number, Core::PortType portType,const QString &properties)
{
if (!m_state.ports().is_set())
m_state.ports() = std::vector<Core::PortInformation>();
m_state.ports().get().push_back(Core::PortInformation(number, portType, properties.toStdString()));
publishState();
}
void Pacemaker::publishState()
{
if (!m_dtsCurrentRunningState)
return;
m_dtsCurrentRunningState->write(m_state);
}
void Pacemaker::init(DDSConnector::DdsConnector *ddsConnector)
{
m_dtsCurrentRunningState = ddsConnector->createDTS<DTS::CurrentRunningStateImpl>();
if (!m_dtsCurrentRunningState)
{
WARNING_LOG(m_logger) << "Can not initialize DTS::CurrentRunningState!";
return;
}
m_state.pid(QCoreApplication::applicationPid());
m_state.runningState(Core::RunningState::Starting);
m_paceThread = new QThread;
this->moveToThread(m_paceThread);
startTimer();
publishState();
}
void Pacemaker::startTimer()
{
if (m_publishTimer)
return;
m_publishTimer = new QTimer();
m_publishTimer->moveToThread(m_paceThread);
connect(m_publishTimer, &QTimer::timeout,
this, &Pacemaker::publishState);
m_publishTimer->setSingleShot(false);
m_publishTimer->setInterval(m_publishTimerDelay);
connect(m_paceThread, &QThread::started, this, [this](){
m_publishTimer->start();
});
m_paceThread->start();
}
}
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <DDS/Core/core_dts_types.h>
#include "core_export.h"
namespace DDSConnector
{
class DdsConnector;
}
class QTimer;
namespace NGL
{
class INGLLogger;
}
namespace Core
{
class CORE_CLIENT_EXPORT Pacemaker final : public QObject
{
Q_OBJECT
public:
explicit Pacemaker(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
: Pacemaker(ddsConnector.get())
{}
explicit Pacemaker(DDSConnector::DdsConnector *ddsConnector);
~Pacemaker();
void startTimer();
public Q_SLOTS:
void setNewState(Core::RunningState newState, const QString &errorString = QString());
void addPort(unsigned short number, Core::PortType portType, const QString &properties = QStringLiteral(""));
void publishState();
private:
void init(DDSConnector::DdsConnector *ddsConnector);
void setNewStateInternal(Core::RunningState newState, const QString &errorString = QString());
void addPortInternal(unsigned short number, Core::PortType portType, const QString &properties = QStringLiteral(""));
private:
static constexpr const int m_publishTimerDelay = 3000;
CurrentRunningState m_state;
QTimer *m_publishTimer = nullptr;
QThread *m_paceThread = nullptr;
DTS::CurrentRunningStateImpl *m_dtsCurrentRunningState;
NGL::INGLLogger &m_logger;
};
}
+47
View File
@@ -0,0 +1,47 @@
#include "topic_lock_master.h"
#include <DDS/DDSConnector/dds_connector.h>
namespace Core
{
TopicLockMaster::TopicLockMaster(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
: QObject(nullptr)
, m_coreDtc(nullptr)
{
init(ddsConnector);
}
TopicLockMaster::~TopicLockMaster()
{
m_coreDtc.reset();
}
bool TopicLockMaster::init(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
{
if (!ddsConnector)
return false;
m_coreDtc = Core::Interface::Dtc::create(ddsConnector);
if (!m_coreDtc)
return false;
m_coreDtc->enableAll();
m_coreDtc->lockedEntitys([this] (const LockedEntity &data) {
m_lockedEntitys << key(data.entityId(), data.exerciseId(), QString::fromStdString(data.entityTypeName()));
});
connect(m_coreDtc.get(), &Interface::Dtc::lockedEntityChanged, this,
[this] (const LockedEntity &data, DataModification modType)
{
key k(data.entityId(), data.exerciseId(), QString::fromStdString(data.entityTypeName()));
if (modType == DataModification::eAdded)
m_lockedEntitys << k;
else if (modType == DataModification::eRemoved)
m_lockedEntitys.remove(k);
} );
return true;
}
}
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include <Core/core_dtc.h>
#include <QSet>
#include <QString>
namespace DDSConnector
{
class DdsConnector;
}
namespace Core
{
struct key
{
inline key() = default;
inline key(int64_t id, int exId, const QString &name)
: entityTypeName(name)
, entityId(id)
, exerciseId(exId)
{}
inline bool operator==(const key &o) const
{
return entityId == o.entityId &&
exerciseId == o.exerciseId &&
entityTypeName == o.entityTypeName;
}
QString entityTypeName;
int64_t entityId = 0;
int exerciseId = 0;
};
inline uint qHash(const Core::key &k, uint seed = 0)
{
return ::qHash(k.entityTypeName, seed) ^ ::qHash(k.entityId, seed) ^ ::qHash(k.exerciseId, seed);
}
class CORE_CLIENT_EXPORT TopicLockMaster : public QObject
{
Q_OBJECT
public:
explicit TopicLockMaster(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
~TopicLockMaster();
bool init(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
template<typename E>
bool isLocked(const E &data, int exerciseId)
{
auto it = m_lockedEntitys.constFind(key(data.id(), exerciseId, QString::fromStdString(typeid(E).name())));
return !(it == m_lockedEntitys.cend());
}
private:
QSet<key> m_lockedEntitys;
std::shared_ptr<Core::Interface::Dtc> m_coreDtc;
};
}
+36
View File
@@ -0,0 +1,36 @@
#
# $Id$
# $HeadURL$
# $Author$
#
SET(SRCS
dds_connector.cpp
dds_connector.h
publisher.cpp
publisher.h
pub_sub_base.cpp
pub_sub_base.h
pub_sub_base_p.h
subscriber.cpp
subscriber.h
)
NGL_ADD_LIBRARY2(AAR SV WS HLA ddsconnector SHARED ${SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../../
)
NGL_TARGET_LINK_LIBRARIES(ddsconnector
PRIVATE
NGLLogger
PUBLIC
RTI::nddsc
RTI::nddscore
RTI::nddscpp2
ddsQoS
ddsdtc
ddsdts
ProjectConfig
Qt5::Core
)
+283
View File
@@ -0,0 +1,283 @@
#include "dds_connector.h"
#include "publisher.h"
#include "subscriber.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <DDS/QosProfile/DDS_DefaultQos.h>
#include <DDS/Generic/wait_set_handler.h>
#include <QCoreApplication>
namespace DDSConnector
{
DdsConnector::DdsConnector(int domainId,
const dds::domain::DomainParticipant &domainParticipant,
IDL::PartitionType defaultPartitionType,
int partitionId,
int exerciseId,
DDS::QosProfile::ConnectionType connectionType,
const QUrl &tcpConnectionUrl)
: m_domainId(domainId)
, m_defaultPartitionType(defaultPartitionType)
, m_partitionId(partitionId)
, m_exerciseId(exerciseId)
, m_tcpConnectionUrl(tcpConnectionUrl)
, m_connectionType(tcpConnectionUrl.isEmpty() ? connectionType : DDS::QosProfile::ConnectionType::Tcp)
, m_domainParticipant(domainParticipant)
, m_publisher(nullptr)
, m_subscriber(nullptr)
, m_wsHandler(nullptr)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Common.DDSConnector"))
{
initInternal();
DEBUG_LOG(m_logger) << "DdsConnector ctor: " << m_domainId << "," << m_partitionId << ","<< m_exerciseId << "," << static_cast<int>(m_defaultPartitionType);
}
DdsConnector::DdsConnector(int domainId,
IDL::PartitionType defaultPartitionType,
int partitionId, int exerciseId,
const QUrl &tcpConnectionUrl)
: DdsConnector(domainId,
nullptr,
defaultPartitionType,
partitionId,
exerciseId,
DDS::QosProfile::ConnectionType::Server,
tcpConnectionUrl)
{}
DdsConnector::DdsConnector(const dds::domain::DomainParticipant &domainParticipant,
IDL::PartitionType defaultPartitionType,
int partitionId, int exerciseId,
const QUrl &tcpConnectionUrl)
: DdsConnector(domainParticipant.domain_id(),
domainParticipant,
defaultPartitionType,
partitionId,
exerciseId,
DDS::QosProfile::ConnectionType::Server,
tcpConnectionUrl)
{}
DdsConnector::DdsConnector(const dds::domain::DomainParticipant &domainParticipant,
IDL::PartitionType defaultPartitionType,
DDS::QosProfile::ConnectionType connectionType,
int partitionId,
int exerciseId
)
: DdsConnector(domainParticipant.domain_id(),
domainParticipant,
defaultPartitionType,
partitionId,
exerciseId,
connectionType,
QUrl())
{}
DdsConnector::DdsConnector(int domainId,
IDL::PartitionType defaultPartitionType,
int partitionId, int exerciseId,
DDS::QosProfile::ConnectionType connectionType)
: DdsConnector(domainId,
nullptr,
defaultPartitionType,
partitionId,
exerciseId,
connectionType,
QUrl())
{
Q_ASSERT(connectionType != DDS::QosProfile::ConnectionType::Tcp);
}
DdsConnector::~DdsConnector()
{
delete m_subscriber;
delete m_publisher;
delete m_wsHandler;
}
bool DdsConnector::init(const QStringList &qosFileList)
{
return DDS::QosProfile::setDefault(qosFileList);
}
QUrl DdsConnector::getTcpConnectionUrl() const
{
DEBUG_LOG(m_logger) << this << " gettcpConnectionUrl: " << m_tcpConnectionUrl.toString();
return m_tcpConnectionUrl;
}
dds::pub::Publisher DdsConnector::getDdsPublisher() const
{
return m_publisher ? m_publisher->getDdsPublisher() : nullptr;
}
dds::sub::Subscriber DdsConnector::getDdsSubscriber() const
{
return m_subscriber ? m_subscriber->getDdsSubscriber() : nullptr;
}
DDSGeneric::SubscriberListener *DdsConnector::getSubscriberListener()
{
return m_subscriber ? m_subscriber->getSubscriberListener() : nullptr;
}
void DdsConnector::initPublisherSubscriber()
{
initSubscriber();
initPublisher();
}
void DdsConnector::initPublisher()
{
if (m_publisher)
return;
initDomainParticipant();
setPublisher(new DDSConnector::Publisher(m_domainParticipant, getDefaultPartition()));
}
void DdsConnector::initSubscriber()
{
if (m_subscriber)
return;
initDomainParticipant();
setSubscriber(new DDSConnector::Subscriber(m_domainParticipant, getDefaultPartition()));
}
void DdsConnector::initDomainParticipant()
{
if (m_domainParticipant == nullptr)
setDomainParticipant(dds::domain::DomainParticipant(m_domainId,
DDS::QosProfile::domainParticipantDefault(m_connectionType, m_tcpConnectionUrl)));
}
std::string DdsConnector::getDefaultPartition() const
{
return getPartitionString(m_defaultPartitionType, m_partitionId);
}
std::string DdsConnector::getPartitionString(IDL::PartitionType pt, int partitionId)
{
switch (pt)
{
case IDL::PartitionType::Any:
return "*";
case IDL::PartitionType::Global:
return IDL::exercisePartition(0);
case IDL::PartitionType::Exercise:
case IDL::PartitionType::AllExercises:
return IDL::exercisePartition(partitionId);
case IDL::PartitionType::Replay:
return IDL::replayPartition(partitionId);
}
NGL::INGLLogger &m_logger = NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Common.DDSConnector");
ERROR_LOG(m_logger) << "Invalid partition type: " << static_cast<int>(pt);
return std::string();
}
void DdsConnector::setDomainParticipant(const dds::domain::DomainParticipant &participant)
{
m_domainParticipant = participant;
if (m_domainParticipant == nullptr)
return;
try
{
dds::domain::qos::DomainParticipantQos dpQos(m_domainParticipant.qos());
DDS::QosProfile::print(dpQos, m_logger);
rti::core::policy::EntityName entityName;
entityName.name(qAppName().toStdString() + (" - DP ") + std::to_string(m_domainId));
dpQos << entityName;
m_domainParticipant.qos(dpQos);
m_domainParticipant->default_datareader_qos(DDS::QosProfile::dataReaderDefault(m_connectionType));
m_domainParticipant.enable();
}
catch (const dds::core::Exception &ex)
{
WARNING_LOG(m_logger) << "Could not enable DomainParticipant: " << ex.what();
exit(1);
}
}
void DdsConnector::setPublisher(Publisher *publisher)
{
if (m_publisher && publisher != m_publisher)
{
WARNING_LOG(m_logger) << "Trying to set publisher but there is already a publisher set - overwriting!";
delete m_publisher;
}
m_publisher = publisher;
if (m_publisher)
DDS::QosProfile::print(m_publisher->getDdsPublisher().default_datawriter_qos(), m_logger);
}
void DdsConnector::enableBatchedWriter(bool bEnable)
{
if (m_publisher)
m_publisher->enableBatchedWriter(bEnable);
}
void DdsConnector::setSubscriber(Subscriber *subscriber)
{
if (m_subscriber && subscriber != m_subscriber)
{
WARNING_LOG(m_logger) << "Trying to set subscriber but there is already a subscriber set - overwriting!";
delete m_subscriber;
}
m_subscriber = subscriber;
if (m_subscriber)
DDS::QosProfile::print(m_subscriber->getDdsSubscriber().default_datareader_qos(), m_logger);
}
void DdsConnector::initInternal()
{
if (m_tcpConnectionUrl.isValid())
{
m_connectionType = DDS::QosProfile::ConnectionType::Tcp;
}
else
{
m_tcpConnectionUrl.clear();
if (m_connectionType == DDS::QosProfile::ConnectionType::Tcp)
m_connectionType = DDS::QosProfile::ConnectionType::ShmUdp;
}
if (m_defaultPartitionType == IDL::PartitionType::Any ||
m_defaultPartitionType == IDL::PartitionType::AllExercises)
m_partitionId = -1;
if (m_defaultPartitionType == IDL::PartitionType::Exercise && m_partitionId < 0)
WARNING_LOG(m_logger) << "Want to subscribe to an exercise, but no exercise Id is given!";
if (m_defaultPartitionType == IDL::PartitionType::Global)
{
m_partitionId = 0;
m_exerciseId = 0;
}
INFO_LOG(m_logger) << "DDSConnector(" << m_domainId << ") for partition: " << m_partitionId
<< " with partition type " << static_cast<int>(m_defaultPartitionType);
dds::domain::qos::DomainParticipantFactoryQos domainParticipantFactoryQos = dds::domain::DomainParticipant::participant_factory_qos();
domainParticipantFactoryQos << dds::core::policy::EntityFactory::ManuallyEnable();
dds::domain::DomainParticipant::participant_factory_qos(domainParticipantFactoryQos);
}
void DdsConnector::createWaitSetHandler()
{
if (!m_wsHandler)
m_wsHandler = new DDSGeneric::WaitSetHandler;
}
}
+214
View File
@@ -0,0 +1,214 @@
#pragma once
#include <dds/pub/CoherentSet.hpp>
#include <dds/pub/Publisher.hpp>
#include <dds/sub/Subscriber.hpp>
#include <QtCore/QStringList>
#include <Common/ProjectConfig/project_defines.h>
#include <DDS/DDSConnector/ddsconnector_export.h>
#include <DDS/Generic/generic_base.h>
#include <QUrl>
class QObject;
namespace DDS
{
namespace QosProfile
{
enum class ConnectionType;
}
}
namespace NGL
{
class INGLLogger;
}
namespace DDSGeneric
{
class WaitSetHandler;
class SubscriberListener;
}
namespace DDSConnector
{
class Publisher;
class Subscriber;
class DDSCONNECTOR_EXPORT DdsConnector
{
DdsConnector(int domainId,
const dds::domain::DomainParticipant &domainParticipant,
IDL::PartitionType defaultPartitionType,
int partitionId,
int exerciseId,
DDS::QosProfile::ConnectionType connectionType,
const QUrl &tcpConnectionUrl);
public:
DdsConnector(const DdsConnector &) = delete;
DdsConnector &operator=(const DdsConnector &) = delete;
DdsConnector(int domainId,
IDL::PartitionType defaultPartitionType,
int partitionId = 0,
int exerciseId = 0,
const QUrl &tcpConnectionUrl = QUrl());
DdsConnector(const dds::domain::DomainParticipant &domainParticipant,
IDL::PartitionType defaultPartitionType,
int partitionId = 0,
int exerciseId = 0,
const QUrl &tcpConnectionUrl = QUrl());
DdsConnector(const dds::domain::DomainParticipant &domainParticipant,
IDL::PartitionType defaultPartitionType,
DDS::QosProfile::ConnectionType connectionType,
int partitionId = 0,
int exerciseId = 0);
DdsConnector(int domainId,
IDL::PartitionType defaultPartitionType,
int partitionId,
int exerciseId,
DDS::QosProfile::ConnectionType connectionType);
virtual ~DdsConnector();
virtual bool init(const QStringList &qosFileList = QStringList());
dds::domain::DomainParticipant getDomainParticipant() const { return m_domainParticipant; }
void setDomainParticipant(const dds::domain::DomainParticipant &participant);
Publisher *getPublisher() { return m_publisher; }
dds::pub::Publisher getDdsPublisher() const;
void setPublisher(Publisher *publisher);
void enableBatchedWriter(bool bEnable);
Subscriber *getSubscriber() { return m_subscriber; }
dds::sub::Subscriber getDdsSubscriber() const;
void setSubscriber(Subscriber *subscriber);
DDSGeneric::SubscriberListener *getSubscriberListener();
const DDSGeneric::WaitSetHandler *getWaitSetHandler() const { return m_wsHandler; }
int getDomainId() const { return m_domainId; }
IDL::PartitionType getPartitionType() const { return m_defaultPartitionType; }
int getPartitionId() const { return m_partitionId; }
int getExerciseId() const { return m_exerciseId; }
std::string getDefaultPartition() const;
QUrl getTcpConnectionUrl() const;
static std::string getPartitionString(IDL::PartitionType pt, int partitionId = -1);
void initPublisherSubscriber();
void initPublisher();
void initSubscriber();
void initDomainParticipant();
template<typename T>
T *createDTS(const std::string &topicName = DDS::Helper::getShortTopicName(DDS::topic_type_name<typename T::TopicType>::value()))
{
if (getDomainParticipant() == nullptr ||
getDdsPublisher() == nullptr)
return nullptr;
T *dts = new T(getDomainParticipant(),
getDdsPublisher(),
topicName);
return dts;
}
template<typename T>
T *createDTC(const std::string &topicName = DDS::Helper::getShortTopicName(DDS::topic_type_name<typename T::TopicType>::value()))
{
if (getDomainParticipant() == nullptr ||
getDdsSubscriber() == nullptr)
return nullptr;
T *dtc = new T(getDomainParticipant(),
getDdsSubscriber(),
getSubscriberListener(),
topicName);
return dtc;
}
template<typename T>
T *createDTC(const dds::topic::Filter &filter,
const std::string &topicName = DDS::Helper::getShortTopicName(DDS::topic_type_name<typename T::TopicType>::value()))
{
if (getDomainParticipant() == nullptr ||
getDdsSubscriber() == nullptr)
return nullptr;
T *dtc = new T(getDomainParticipant(),
getDdsSubscriber(),
getSubscriberListener(),
filter,
topicName);
return dtc;
}
template<typename T>
T *createFTS()
{
if (getDomainParticipant() == nullptr ||
getDdsPublisher() == nullptr ||
getDdsSubscriber() == nullptr)
return nullptr;
createWaitSetHandler();
T *fts = new T(getDomainParticipant(),
getDdsPublisher(),
getDdsSubscriber(),
m_wsHandler);
return fts;
}
template<typename T>
T *createFTC(QObject *objToInform)
{
if (getDomainParticipant() == nullptr ||
getDdsPublisher() == nullptr ||
getDdsSubscriber() == nullptr)
return nullptr;
T *ftc = new T(getDomainParticipant(),
getDdsPublisher(),
getDdsSubscriber(),
objToInform,
IDL::toStdString(m_defaultPartitionType),
m_partitionId);
return ftc;
}
private:
void initInternal();
void createWaitSetHandler();
private:
int m_domainId;
IDL::PartitionType m_defaultPartitionType;
int m_partitionId;
int m_exerciseId;
QUrl m_tcpConnectionUrl;
DDS::QosProfile::ConnectionType m_connectionType;
dds::domain::DomainParticipant m_domainParticipant;
DDSConnector::Publisher *m_publisher;
DDSConnector::Subscriber *m_subscriber;
DDSGeneric::WaitSetHandler *m_wsHandler;
NGL::INGLLogger &m_logger;
};
class CoherentSet
{
public:
explicit CoherentSet(const std::shared_ptr<DdsConnector> &connector)
: m_coherentSet(connector->getDdsPublisher())
{}
private:
dds::pub::CoherentSet m_coherentSet;
};
}
+15
View File
@@ -0,0 +1,15 @@
#include "pub_sub_base.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace DDSConnector
{
PubSubBase::PubSubBase(const dds::domain::DomainParticipant &domainParticipant, const std::string &partition, const char *loggerName)
: m_domainParticipant(domainParticipant)
, m_partitions({partition})
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(loggerName))
{}
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <DDS/DDSConnector/ddsconnector_export.h>
#include <string>
#include <vector>
#include <dds/domain/DomainParticipant.hpp>
namespace NGL
{
class INGLLogger;
}
namespace DDSConnector
{
class DDSCONNECTOR_EXPORT PubSubBase
{
protected:
PubSubBase(const dds::domain::DomainParticipant &domainParticipant,
const std::string &partition,
const char *loggerName);
public:
virtual ~PubSubBase() = default;
void setDomainParticipant(const dds::domain::DomainParticipant &domainParticipant) { m_domainParticipant = domainParticipant; }
const dds::domain::DomainParticipant &getDomainParticipant() const { return m_domainParticipant; }
protected:
template<typename P>
void updatePartitionQoS(P &pubSub);
protected:
dds::domain::DomainParticipant m_domainParticipant;
std::vector<std::string> m_partitions;
NGL::INGLLogger &m_logger;
};
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
namespace DDSConnector
{
template<typename P>
void PubSubBase::updatePartitionQoS(P &pubSub)
{
if (pubSub == nullptr)
return;
auto publisherQos = pubSub.qos();
dds::core::policy::Partition partitionQos;
partitionQos.name( m_partitions );
publisherQos << partitionQos;
pubSub.qos(publisherQos);
}
}
+106
View File
@@ -0,0 +1,106 @@
#include "publisher.h"
#include "pub_sub_base_p.h"
#include <dds/pub/AnyDataWriter.hpp>
#include <DDS/QosProfile/DDS_DefaultQos.h>
#include <NGLLogger/INGLLogger.h>
namespace DDSConnector
{
Publisher::Publisher(const dds::domain::DomainParticipant &domainParticipant, const std::string &partition)
: PubSubBase(domainParticipant, partition, "DDSConnector.Publisher")
, m_ddsPublisher(nullptr)
{
Q_ASSERT(domainParticipant != nullptr);
initDDS();
}
Publisher::~Publisher()
{
try
{
deleteDDS();
} catch (...)
{}
}
void Publisher::initDDS()
{
if (m_partitions.empty())
return;
if (m_logger.isDebugEnabled()) {
DEBUG_LOG(m_logger) << "Publisher (DataWriter) Qos: ";
dds::core::QosProvider qosProvider = dds::core::QosProvider::Default();
DDS::QosProfile::print(qosProvider.datawriter_qos(), m_logger);
}
dds::pub::qos::PublisherQos publisherQos = m_domainParticipant.default_publisher_qos();
rti::core::policy::EntityName entityName;
entityName.name(std::string("Pub ") + m_partitions.front());
publisherQos << entityName;
DEBUG_LOG(m_logger) << "Publish data on partition " << m_partitions.front();
m_ddsPublisher = dds::pub::Publisher(m_domainParticipant, publisherQos);
enableBatchedWriter(false);
updatePartitionQoS(m_ddsPublisher);
}
void Publisher::enableBatchedWriter(bool bEnable)
{
dds::pub::qos::DataWriterQos dwQos = m_ddsPublisher.default_datawriter_qos();
auto batch = (bEnable ? rti::core::policy::Batch::Enabled() : rti::core::policy::Batch::Disabled());
if (bEnable)
{
batch.max_data_bytes(64 * 1024);
batch.max_flush_delay(dds::core::Duration::from_millisecs(10));
}
dwQos << batch;
m_ddsPublisher.default_datawriter_qos(dwQos);
}
void Publisher::deleteDDS()
{
qDeleteAll(m_dtsMap);
qDeleteAll(m_dynamicTypeMap);
m_ddsPublisher = nullptr;
}
bool Publisher::write(const dds::core::xtypes::DynamicData &data, const std::string &topicName)
{
DEBUG_LOG(m_logger) << "Publish dynamic data for topic (" << topicName << ")";
try
{
dds::pub::AnyDataWriter* anyWriter = m_dynamicTypeMap.value(topicName);
if (anyWriter == nullptr)
{
dds::topic::Topic<dds::core::xtypes::DynamicData> topic(m_domainParticipant, topicName, data.type());
auto writer = dds::pub::DataWriter<dds::core::xtypes::DynamicData>(m_ddsPublisher, topic);
anyWriter = new dds::pub::AnyDataWriter(writer);
m_dynamicTypeMap.insert(topicName, anyWriter);
}
anyWriter->get<dds::core::xtypes::DynamicData>().write(data);
}
catch ( ... )
{
ERROR_LOG(m_logger) << "Failed to publish data for topic (" << topicName << ")";
return false;
}
return true;
}
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include <QtCore/QMap>
#include <NGLLogger/INGLLogger.h>
#include <DDS/DTS/generic_dts_impl.h>
#include <DDS/Generic/generic_data_writer.h>
#include "pub_sub_base.h"
namespace DDSConnector
{
class DDSCONNECTOR_EXPORT Publisher : public PubSubBase
{
public:
Publisher(const dds::domain::DomainParticipant &domainParticipant, const std::string &partition);
~Publisher();
void enableBatchedWriter(bool bEnable);
dds::pub::Publisher getDdsPublisher() const { return m_ddsPublisher; }
template <typename T>
bool write(const T &data)
{
using myDWO = DDSGeneric::GenericDataWriter<T>;
using myDTS = DTS::GenericDtsImpl< myDWO >;
std::string dwoType = DDS::topic_type_name<T>::value();
DEBUG_LOG(m_logger) << "dwoType = " << dwoType;
myDTS *dts = static_cast<myDTS *>(m_dtsMap.value(dwoType));
if (dts == nullptr)
{
dts = new myDTS(m_domainParticipant, m_ddsPublisher);
m_dtsMap.insert(dwoType, dts);
}
return dts->write(data);
}
bool write(const dds::core::xtypes::DynamicData &data)
{
return write(data, data.type().name());
}
bool write(const dds::core::xtypes::DynamicData &data, const std::string &topicName);
protected:
void initDDS();
void deleteDDS();
protected:
dds::pub::Publisher m_ddsPublisher;
QMap<std::string, DTS::Base*> m_dtsMap;
QMap<std::string, dds::pub::AnyDataWriter*> m_dynamicTypeMap;
};
}
+74
View File
@@ -0,0 +1,74 @@
#include "subscriber.h"
#include "pub_sub_base_p.h"
#include <DDS/QosProfile/DDS_DefaultQos.h>
#include <NGLLogger/INGLLogger.h>
namespace DDSConnector
{
Subscriber::Subscriber(const dds::domain::DomainParticipant &domainParticipant, const std::string &partition, bool bUseSubscriberListener)
: PubSubBase(domainParticipant, partition, "DDSConnector.Subscriber")
, m_ddsSubscriber(nullptr)
, m_subscriberListener(bUseSubscriberListener ? new DDSGeneric::SubscriberListener : nullptr)
, m_subscriberListenerBinder(nullptr)
{
Q_ASSERT(domainParticipant != nullptr);
initDDS();
}
Subscriber::~Subscriber()
{
try
{
deleteDDS();
} catch (...)
{}
}
void Subscriber::initDDS()
{
if (m_partitions.empty())
return;
if (m_logger.isDebugEnabled()) {
DEBUG_LOG(m_logger) << "Subscriber (DataReader) Qos: ";
dds::core::QosProvider qosProvider = dds::core::QosProvider::Default();
DDS::QosProfile::print(qosProvider.datareader_qos(), m_logger);
}
dds::sub::qos::SubscriberQos subscriberQos = m_domainParticipant.default_subscriber_qos();
rti::core::policy::EntityName entityName;
entityName.name(std::string("Sub ") + m_partitions.front());
subscriberQos << entityName;
DEBUG_LOG(m_logger) << "Subscribe on partition " << m_partitions.front();
m_ddsSubscriber = dds::sub::Subscriber(m_domainParticipant,
subscriberQos);
if (m_subscriberListener)
m_subscriberListenerBinder = rti::core::bind_and_manage_listener(m_ddsSubscriber,
m_subscriberListener,
DDSGeneric::SubscriberListener::mask());
updatePartitionQoS(m_ddsSubscriber);
}
void Subscriber::deleteDDS()
{
if (m_subscriberListener)
{
m_subscriberListener->prepareShutdown();
m_subscriberListener = nullptr;
}
m_subscriberListenerBinder = nullptr;
m_ddsSubscriber = nullptr;
}
}
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include "pub_sub_base.h"
#include <DDS/Generic/dynamic_data_reader.h>
#include <DDS/DTC/generic_dtc_impl.h>
#include <DDS/Generic/topic_description.h>
#include <rti/core/ListenerBinder.hpp>
namespace NGL
{
class INGLLogger;
}
namespace DDSConnector
{
class DDSCONNECTOR_EXPORT Subscriber : public PubSubBase
{
public:
Subscriber(const dds::domain::DomainParticipant &domainParticipant, const std::string &partition, bool bUseSubscriberListener = true);
virtual ~Subscriber();
dds::sub::Subscriber getDdsSubscriber() const { return m_ddsSubscriber; }
DDSGeneric::SubscriberListener *getSubscriberListener() { return m_subscriberListener; }
template <typename DTC_TYPE>
DTC_TYPE *create(std::function<void(const typename DTC_TYPE::TopicTypeRead &, DataModification modType)> cb)
{
DTC_TYPE* dtc = new DTC_TYPE;
if (!dtc->create(m_domainParticipant, m_ddsSubscriber, m_subscriberListener))
{
delete dtc;
return nullptr;
}
dtc->setSubscriberCallback(cb);
return dtc;
}
DTC::Ddtc<DDSGeneric::DynamicDataReader> *create(const std::function<void(const DDSGeneric::DynamicData &, const std::string &, DataModification modType)> &cb,
const dds::core::xtypes::StructType &structType,
DDSGeneric::SubscriberListener *listener = nullptr,
const std::string &specialTopicName = std::string())
{
if (!listener)
listener = getSubscriberListener();
DTC::DdtcImpl<DDSGeneric::DynamicDataReader> *ddtc =
new DTC::DdtcImpl<DDSGeneric::DynamicDataReader>(getDomainParticipant(),
getDdsSubscriber(),
listener,
structType,
specialTopicName);
ddtc->setSubscriberCallback(cb);
return ddtc;
}
protected:
void initDDS();
void deleteDDS();
protected:
dds::sub::Subscriber m_ddsSubscriber;
DDSGeneric::SubscriberListener *m_subscriberListener;
rti::core::ListenerBinder< dds::sub::Subscriber, dds::sub::SubscriberListener> m_subscriberListenerBinder;
};
}
+23
View File
@@ -0,0 +1,23 @@
#
# $Id$
# $HeadURL$
# $Author$
#
ADD_LIBRARY(ddsdtc INTERFACE)
TARGET_INCLUDE_DIRECTORIES(ddsdtc
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../../
)
TARGET_LINK_LIBRARIES(ddsdtc
INTERFACE
Qt5::Core
ddsgen
NGLLogger
LtsUtilsCoreHeaderOnly
)
SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/generic_dtc.h
${CMAKE_CURRENT_SOURCE_DIR}/generic_dtc_impl.h
)
TARGET_SOURCES(ddsdtc INTERFACE ${SRCS})
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <LtsUtilsCore/data_modification_enum.h>
#include <dds/sub/LoanedSamples.hpp>
namespace NGLLogger
{
class INGLLogger;
}
namespace DTC
{
class SubscriberListener;
template <typename DRO_OBJECT, typename SC>
class GenericDtcSC
{
public:
using DataReaderObject = DRO_OBJECT;
using SubscriberCallback = SC;
using TopicType = typename DRO_OBJECT::TopicType;
GenericDtcSC() = default;
virtual ~GenericDtcSC() = default;
virtual bool setSubscriberCallback(const SubscriberCallback &subscriberCallback) = 0;
virtual dds::sub::LoanedSamples<TopicType> readAlive() const = 0;
virtual dds::sub::LoanedSamples<TopicType> takeAlive() const = 0;
virtual dds::sub::LoanedSamples<TopicType> takeDisposed() const = 0;
virtual bool hasSamples() const = 0;
virtual DataReaderObject *dataReader() const = 0;
virtual bool waitForHistoricalData(int timeoutMSec) = 0;
};
template<typename DRO_OBJECT>
class GenericDtc
: public GenericDtcSC<DRO_OBJECT,
std::function<void(const typename DRO_OBJECT::TopicType &, DataModification)> >
{};
template<typename DRO_OBJECT>
class GenericDtcSampleInfo
: public GenericDtcSC<DRO_OBJECT,
std::function<void(const typename DRO_OBJECT::TopicType &, const dds::sub::SampleInfo &)> >
{};
template<typename DRO_OBJECT>
class Ddtc
: public GenericDtcSC<DRO_OBJECT,
std::function<void(const typename DRO_OBJECT::TopicType &, const std::string &, DataModification)> >
{};
template<typename DRO_OBJECT>
class DdtcSampleInfo
: public GenericDtcSC<DRO_OBJECT,
std::function<void(const typename DRO_OBJECT::TopicType &, const std::string &, const dds::sub::SampleInfo &)> >
{};
}
+313
View File
@@ -0,0 +1,313 @@
#pragma once
#include <DDS/Generic/generic_data_reader.h>
#include <DDS/Generic/subscriber_listener.h>
#include <QTimer>
#include <dds/core/cond/StatusCondition.hpp>
#include <dds/core/cond/WaitSet.hpp>
#include "generic_dtc.h"
namespace DDSGeneric
{
class DynamicDataReader;
}
namespace DTC
{
template<typename SuperClass>
class GenericDtcImplSubscriberTemplate : public DDSGeneric::SubscriberListenerCallback, public SuperClass
{
public:
using DataReaderObject = typename SuperClass::DataReaderObject;
using SubscriberCallback = typename SuperClass::SubscriberCallback;
using TopicTypeRead = typename DataReaderObject::TopicType;
using TopicTypeSubscribe = typename DataReaderObject::TopicType;
using DataReader = typename DataReaderObject::DataReader;
GenericDtcImplSubscriberTemplate(const dds::domain::DomainParticipant &domainParticipant,
const dds::sub::Subscriber &ddsSubscriber,
DDSGeneric::SubscriberListener *subscriberListener,
const std::string &topicName = DDS::Helper::getShortTopicName(DDS::topic_type_name<TopicTypeRead>::value()))
: m_subscriberListener(subscriberListener)
, m_dataReaderObject(new DataReaderObject)
, m_subscriberCallback(nullptr)
, m_writerState(0)
, m_logger(DDS::getLogger(DDS::topic_type_name<TopicTypeRead>::value(), DDS::DataType::DATA_TOPIC_CLIENT))
{
createInternal(m_dataReaderObject->create(domainParticipant, ddsSubscriber, topicName));
}
GenericDtcImplSubscriberTemplate(const dds::domain::DomainParticipant &domainParticipant,
const dds::sub::Subscriber &ddsSubscriber,
DDSGeneric::SubscriberListener *subscriberListener,
const dds::topic::Filter &filter,
const std::string &topicName)
: m_subscriberListener(subscriberListener)
, m_dataReaderObject(new DataReaderObject)
, m_subscriberCallback(nullptr)
, m_writerState(0)
, m_logger(DDS::getLogger(DDS::topic_type_name<TopicTypeRead>::value(), DDS::DataType::DATA_TOPIC_CLIENT))
{
createInternal(m_dataReaderObject->create(domainParticipant, ddsSubscriber, topicName, filter));
}
GenericDtcImplSubscriberTemplate(const dds::domain::DomainParticipant &domainParticipant,
const dds::sub::Subscriber &ddsSubscriber,
DDSGeneric::SubscriberListener *subscriberListener,
const dds::core::xtypes::StructType &structType,
const std::string &topicName)
: m_subscriberListener(subscriberListener)
, m_dataReaderObject(new DataReaderObject)
, m_subscriberCallback(nullptr)
, m_logger(DDS::getLogger(DDS::topic_type_name<TopicTypeRead>::value(), DDS::DataType::DATA_TOPIC_CLIENT))
{
createInternal(m_dataReaderObject->create(domainParticipant, ddsSubscriber, topicName, structType));
}
~GenericDtcImplSubscriberTemplate()
{
if (m_dataReaderObject && m_subscriberCallback)
m_subscriberListener->removeCallback(m_dataReaderObject->getDataReader(), this);
delete m_dataReaderObject;
}
bool setSubscriberCallback(const SubscriberCallback &subscriberCallback) override final
{
if (m_subscriberCallback)
{
WARNING_LOG(m_logger) << "Can not set subscriber callback - already set ...";
return false;
}
if (!m_subscriberListener)
{
WARNING_LOG(m_logger) << "Can not set subscriber callback - no subscriber listener ...";
return false;
}
m_subscriberCallback = subscriberCallback;
if (!m_dataReaderObject)
{
WARNING_LOG(m_logger) << "Can not set subscriber callback - no data reader object ...";
return false;
}
m_dataReaderObject->setHaveSubscriberCallback(m_subscriberCallback != nullptr);
return true;
}
dds::sub::LoanedSamples<TopicTypeRead> readAlive() const override final
{
if (!m_dataReaderObject)
return dds::sub::LoanedSamples<TopicTypeRead>();
bool bSeenUnreadSamples = false;
auto ret = m_dataReaderObject->readAlive(bSeenUnreadSamples);
if (bSeenUnreadSamples)
QTimer::singleShot(0, this, &GenericDtcImplSubscriberTemplate::unreadSamplesDuringRead);
return ret;
}
dds::sub::LoanedSamples<TopicTypeRead> takeAlive() const override final
{
if (!m_dataReaderObject)
return dds::sub::LoanedSamples<TopicTypeRead>();
bool bSeenUnreadSamples = false;
auto ret = m_dataReaderObject->takeAlive(bSeenUnreadSamples);
if (bSeenUnreadSamples)
QTimer::singleShot(0, this, &GenericDtcImplSubscriberTemplate::unreadSamplesDuringRead);
return ret;
}
dds::sub::LoanedSamples<TopicTypeRead> takeDisposed() const override final
{
if (!m_dataReaderObject)
return dds::sub::LoanedSamples<TopicTypeRead>();
return m_dataReaderObject->takeDisposed();
}
bool hasSamples() const override final
{
if (!m_dataReaderObject)
return false;
return m_dataReaderObject->hasSamples();
}
DataReaderObject *dataReader() const override final
{
return m_dataReaderObject;
}
bool waitForHistoricalData(int timeoutMSec) override
{
if (m_dataReaderObject == nullptr ||
m_dataReaderObject->getDataReader() == nullptr)
return false;
const bool bWaitForever = (timeoutMSec < 0);
auto dr = m_dataReaderObject->getDataReader();
try
{
if (dr.subscription_matched_status().current_count() == 0)
{
dds::core::cond::WaitSet ws;
dds::core::cond::StatusCondition cond(dr);
cond.enabled_statuses(dds::core::status::StatusMask::subscription_matched());
ws.attach_condition(cond);
const dds::core::Duration waitTimeout(bWaitForever ? dds::core::Duration::infinite() : dds::core::Duration::from_millisecs(timeoutMSec));
const dds::core::cond::WaitSet::ConditionSeq seq(ws.wait(waitTimeout));
if (seq.empty())
return false;
}
dr.wait_for_historical_data(dds::core::Duration(1));
}
catch (const dds::core::Exception &)
{
return false;
}
return true;
}
protected:
void unreadSamplesDuringRead()
{
droDataAvailable(DDS::topic_type_name<TopicTypeRead>::value(), m_dataReaderObject->topicName());
}
void droDataAvailable(const std::string &topicType, const std::string &topicName) override final
{
Q_UNUSED(topicType);
if (m_writerState.testAndSetRelaxed(1, 0))
{
DEBUG_LOG(m_logger) << "Recreate Reader for topic " << topicName
<< ", type name " << DDS::topic_type_name<TopicTypeRead>::value();
if (m_subscriberListener)
m_subscriberListener->removeCallback(m_dataReaderObject->getDataReader(), this);
m_dataReaderObject->recreateReader();
if (m_subscriberListener)
m_subscriberListener->addCallback(m_dataReaderObject->getDataReader(), this);
}
DEBUG_LOG(m_logger) << "IN droDataAvailable(), topic name: " << topicName
<< ", type name " << DDS::topic_type_name<TopicTypeRead>::value()
<< ", this: " << this
<< ", m_dataReaderObject: " << m_dataReaderObject
<< ", hasSamples: " << (m_dataReaderObject ? m_dataReaderObject->hasSamples() : -1);
if (m_subscriberListener)
m_subscriberListener->enableCallback(m_dataReaderObject->getDataReader(), this);
if (!m_dataReaderObject || !m_subscriberCallback)
return;
m_dataReaderObject->read(m_subscriberCallback);
}
void noWriterAnymore(const std::string &topicType, const std::string &topicName) override
{
Q_UNUSED(topicType);
Q_UNUSED(topicName);
m_writerState = -1;
}
void writerAvailable(const std::string &topicType, const std::string &topicName) override
{
Q_UNUSED(topicType);
Q_UNUSED(topicName);
m_writerState.testAndSetRelaxed(-1, 1);
}
bool createInternal(bool droInitOk)
{
if (!droInitOk)
{
ERROR_LOG(m_logger) << "Error initializing DRO for type " << DDS::topic_type_name<TopicTypeRead>::value();
delete m_dataReaderObject;
m_dataReaderObject = nullptr;
return false;
}
if (m_subscriberListener)
{
DEBUG_LOG(m_logger) << "subscriberListener exists, adding type "
<< DDS::topic_type_name<TopicTypeRead>::value();
m_subscriberListener->addCallback(m_dataReaderObject->getDataReader(), this);
}
else
{
DEBUG_LOG(m_logger) << "Cannot add DTC to subscriberListener callback - type: "
<< DDS::topic_type_name<TopicTypeRead>::value();
}
m_dataReaderObject->setHaveSubscriberCallback(m_subscriberListener != nullptr);
return true;
}
private:
DDSGeneric::SubscriberListener *m_subscriberListener;
DataReaderObject *m_dataReaderObject;
SubscriberCallback m_subscriberCallback;
QAtomicInt m_writerState;
NGL::INGLLogger &m_logger;
};
template<typename DRO_OBJECT>
class GenericDtcImpl final
: public GenericDtcImplSubscriberTemplate<GenericDtc<DRO_OBJECT>>
{
using BaseClass = GenericDtcImplSubscriberTemplate<GenericDtc<DRO_OBJECT>>;
public:
using BaseClass::BaseClass;
};
template<typename DRO_OBJECT>
class GenericDtcImplSampleInfo final
: public GenericDtcImplSubscriberTemplate<GenericDtcSampleInfo<DRO_OBJECT>>
{
using BaseClass = GenericDtcImplSubscriberTemplate<GenericDtcSampleInfo<DRO_OBJECT>>;
public:
using BaseClass::BaseClass;
};
template<typename DRO_OBJECT>
class DdtcImpl final
: public GenericDtcImplSubscriberTemplate<Ddtc<DRO_OBJECT>>
{
using BaseClass = GenericDtcImplSubscriberTemplate<Ddtc<DRO_OBJECT>>;
public:
using BaseClass::BaseClass;
};
template<typename DRO_OBJECT>
class DdtcImplSampleInfo final
: public GenericDtcImplSubscriberTemplate<DdtcSampleInfo<DRO_OBJECT>>
{
using BaseClass = GenericDtcImplSubscriberTemplate<DdtcSampleInfo<DRO_OBJECT>>;
public:
using BaseClass::BaseClass;
};
}
+22
View File
@@ -0,0 +1,22 @@
#
# $Id$
# $HeadURL$
# $Author$
#
ADD_LIBRARY(ddsdts INTERFACE)
TARGET_INCLUDE_DIRECTORIES(ddsdts
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../../
)
TARGET_LINK_LIBRARIES(ddsdts
INTERFACE
Qt5::Core
ddsgen
NGLLogger
)
SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/generic_dts.h
${CMAKE_CURRENT_SOURCE_DIR}/generic_dts_impl.h
)
TARGET_SOURCES(ddsdts INTERFACE ${SRCS})
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <dds/domain/DomainParticipant.hpp>
#include <dds/pub/Publisher.hpp>
#include <dds/sub/SampleInfo.hpp>
namespace DTS
{
class Base
{
public:
Base() = default;
virtual ~Base() = default;
};
template <typename TopicType>
class GenericDts : public Base
{
public:
GenericDts() = default;
virtual ~GenericDts() = default;
virtual bool write(const TopicType &data,
const dds::core::optional<dds::sub::SampleInfo> &si = dds::core::optional<dds::sub::SampleInfo>()) = 0;
virtual bool remove(const TopicType &data, bool errorMsgIfNotExists = true) = 0;
};
}
+88
View File
@@ -0,0 +1,88 @@
#pragma once
#include "generic_dts.h"
#include <DDS/Generic/generic_data_writer.h>
#include <DDS/Generic/generic_logger.h>
namespace DTS
{
template<typename DWO_OBJECT>
class GenericDtsImpl final : public GenericDts<typename DWO_OBJECT::TopicType>
{
public:
using DataWriterObject = DWO_OBJECT;
using TopicType = typename DWO_OBJECT::TopicType;
using DataWriter = typename dds::pub::DataWriter<TopicType>;
GenericDtsImpl(const dds::domain::DomainParticipant &domainParticipant,
const dds::pub::Publisher &ddsPublisher,
const std::string &topicName = DDS::Helper::getShortTopicName(DDS::topic_type_name<TopicType>::value()))
: m_dataWriterObject(new DataWriterObject())
, m_logger(DDS::getLogger(DDS::topic_type_name<TopicType>::value(), DDS::DataType::DATA_TOPIC_SERVER))
{
Q_ASSERT(domainParticipant != nullptr);
Q_ASSERT(ddsPublisher != nullptr);
m_dataWriterObject->create(domainParticipant, ddsPublisher, topicName);
}
~GenericDtsImpl()
{
delete m_dataWriterObject;
}
bool write(const TopicType &data,
const dds::core::optional<dds::sub::SampleInfo> &si = dds::core::optional<dds::sub::SampleInfo>()) override final
{
DEBUG_LOG(m_logger) << "IN write ("
<< DDS::topic_type_name<TopicType>::value()
<< ")";
if (!m_dataWriterObject)
return false;
if (!m_dataWriterObject->write(data, si))
{
ERROR_LOG(m_logger) << "Error writing for "
<< DDS::topic_type_name<TopicType>::value();
return false;
}
DEBUG_LOG(m_logger) << "TopicData ("
<< DDS::topic_type_name<TopicType>::value()
<< ") written";
return true;
}
bool remove(const TopicType &data, bool errorMsgIfNotExists = true) override final
{
DEBUG_LOG(m_logger) << "IN remove ("
<< DDS::topic_type_name<TopicType>::value()
<< ")";
if (!m_dataWriterObject)
return false;
if (!m_dataWriterObject->remove(data, errorMsgIfNotExists))
{
if (errorMsgIfNotExists)
ERROR_LOG(m_logger) << "Error removing for "
<< DDS::topic_type_name<TopicType>::value();
return false;
}
DEBUG_LOG(m_logger) << "TopicData ("
<< DDS::topic_type_name<TopicType>::value()
<< ") removed";
return true;
}
private:
DataWriterObject *m_dataWriterObject;
NGL::INGLLogger &m_logger;
};
}
+129
View File
@@ -0,0 +1,129 @@
#
# $Id$
# $HeadURL$
# $Author$
#
IF(BUILD_TESTING AND TARGET ddsgen AND TARGET ddsdtc AND TARGET ddsfts AND TARGET ddsQoS)
SET(xml_FILE ddsut.xml)
SET(SRCS_common
ddsut_export.h
)
# Generates ddsut.idl / ddsut_enums.idl
CODEGEN_IDL_SVC(SRCS_common ${xml_FILE})
# Generate *.cpp/*.hpp from ddsut_enums.idl / ddsut.idl
RTIDDSGEN(SRCS_common ddsut_enums.idl)
RTIDDSGEN(SRCS_common ddsut.idl)
# Generate ddsut_dt[s,c]_types.h, ddsut_ft[s,c]_types.h
CODEGEN_TOPIC_TYPEDEFS(SRCS_common ${xml_FILE})
# generate ddsut_[d|f]tc.h
CODEGEN_CLIENT_TOPICS_IFC(SRCS_common ${xml_FILE})
NGL_ADD_LIBRARY(ALL ddsut_common SHARED ${SRCS_common})
NGL_TARGET_LINK_LIBRARIES(ddsut_common
PRIVATE
NGLLogger
PUBLIC
LtsUtilsCoreHeaderOnly
Qt5::Core
RTI::nddsc
RTI::nddscore
RTI::nddscpp2
ddscore
)
NGL_TARGET_INCLUDE_DIRECTORIES(ddsut_common
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/../
)
# -----------------------------------------------------------------------------------
# Testing DataWriter/DataReader without DTS/DTC
# -----------------------------------------------------------------------------------
SET(SRCS_TEST1
generic_data_tests.cpp
generic_data_tests.h
initialisation.cpp
initialisation.h
)
# -----------------------------------------------------------------------------------
# Testing DataTopic Functionality (DTS/DTC)
# -----------------------------------------------------------------------------------
SET(SRCS_TEST2
data_topic_tests_main.cpp
data_topic_tests_main.h
data_topic_tests.cpp
data_topic_tests.h
)
# -----------------------------------------------------------------------------------
# Testing FunctionTopic Functionality (FTS/FTC)
# -----------------------------------------------------------------------------------
SET(SRCS_TEST3
function_topic_tests.cpp
function_topic_tests.h
)
# Testing DataWriter/DataReader without DTS/DTC
ADD_EXECUTABLE(ddsGenericDataTests ${SRCS_TEST1})
TARGET_LINK_LIBRARIES(ddsGenericDataTests
ddsgen
ddsdtc
ddsQoS
nglcoreapplication
NGLLogger
ProjectConfig
Qt5::Core
Qt5::Test
ddsut_common
)
ADD_TEST(NAME ddsGenericDataTests COMMAND $<TARGET_FILE:ddsGenericDataTests>)
# Testing DataTopic Functionality (DTS/DTC)
ADD_EXECUTABLE(ddsDataTopicTests ${SRCS_TEST2})
TARGET_LINK_LIBRARIES(ddsDataTopicTests
nglcoreapplication
NGLLogger
RTI::nddscpp2
ddsgen
ddsdts
ddsdtc
ddsQoS
ProjectConfig
Qt5::Core
Qt5::Test
ddsut_common
)
ADD_TEST(NAME ddsDataTopicTests COMMAND $<TARGET_FILE:ddsDataTopicTests>)
# Testing FunctionTopic Functionality (FTS/FTC)
ADD_EXECUTABLE(ddsFunctionTopicTests ${SRCS_TEST3})
TARGET_LINK_LIBRARIES(ddsFunctionTopicTests
ddsgen
ddsfts
ddsftc
ddsQoS
ddsconnector
nglcoreapplication
NGLLogger
ngltools
Qt5::Core
Qt5::Test
ddsut_common
)
ADD_TEST(NAME ddsFunctionTopicTests COMMAND $<TARGET_FILE:ddsFunctionTopicTests>)
IF(NOT BUILD_RPM)
NGL_INSTALL(AAR SV TARGETS ddsut_common DESTINATION bin/test)
NGL_INSTALL(AAR SV TARGETS ddsGenericDataTests DESTINATION bin/test)
NGL_INSTALL(AAR SV TARGETS ddsDataTopicTests DESTINATION bin/test)
NGL_INSTALL(AAR SV TARGETS ddsFunctionTopicTests DESTINATION bin/test)
NGL_INSTALL(AAR SV FILES log4cxx_ddsUnitTests.props DESTINATION bin/test)
ENDIF()
ENDIF()
+155
View File
@@ -0,0 +1,155 @@
#include "data_topic_tests.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <DDS/QosProfile/DDS_DefaultQos.h>
#include <DDS/DTS/generic_dts_impl.h>
#include <DDS/DTC/generic_dtc_impl.h>
#include <Common/ProjectConfig/project_defines.h>
#include <QtTest/QTest>
#include <QVariant>
#include <QThread>
DataTopicTests::DataTopicTests()
: m_domainParticipantPub(nullptr)
, m_domainParticipantSub(nullptr)
, m_domainId(0)
, m_partitionId(1)
, m_ddsPublisher(nullptr)
, m_testPart(nullptr)
, m_ddsSubscriber(nullptr)
, m_subscriberListener(nullptr)
, m_dtcTestPart(nullptr)
, m_mainThreadId(QThread::currentThreadId())
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTests.DataTopicTests"))
{
if (!DDS::QosProfile::setDefault())
ERROR_LOG(m_logger) << "Error setting default QoS from xml-file ";
DEBUG_LOG(m_logger) << "Main thread ID is " << m_mainThreadId;
}
DataTopicTests::~DataTopicTests()
{
delete m_testPart;
delete m_dtcTestPart;
delete m_subscriberListener;
}
bool DataTopicTests::initPublisher()
{
m_domainParticipantPub =
dds::domain::DomainParticipant(m_domainId,
DDS::QosProfile::domainParticipantShm());
dds::pub::qos::PublisherQos publisherQos = m_domainParticipantPub.default_publisher_qos();
QString partition = QString::fromStdString(IDL::exercisePartition(m_partitionId));
dds::core::policy::Partition partitionQos;
partitionQos.name( { partition.toStdString() } );
publisherQos << partitionQos;
DEBUG_LOG(m_logger) << "Publish on partition " << partition;
m_ddsPublisher = dds::pub::Publisher(m_domainParticipantPub, publisherQos);
m_testPart = new DTS::TestParticipantImpl(m_domainParticipantPub, m_ddsPublisher);
return true;
}
bool DataTopicTests::write(const DdsUnitTests::TestParticipant &data)
{
INFO_LOG(m_logger) << "Publish topic TestParticipant ";
DEBUG_LOG(m_logger) << "ParticipantId: " << data.location().participantId()
<< ", equipNo = " << data.location().equipNo();
return m_testPart->write(data);
}
bool DataTopicTests::initSubscriber()
{
DEBUG_LOG(m_logger) << "Initialisation DDS (Subscriber/Reader)";
m_domainParticipantSub =
dds::domain::DomainParticipant(m_domainId,
DDS::QosProfile::domainParticipantShm());
dds::sub::qos::SubscriberQos subscriberQos = m_domainParticipantSub.default_subscriber_qos();
dds::core::StringSeq partitions;
partitions.reserve(1);
for(int partitionId : { 1 } )
{
partitions.push_back(IDL::exercisePartition(partitionId));
}
dds::core::policy::Partition partitionQos;
partitionQos.name( partitions );
subscriberQos << partitionQos;
DEBUG_LOG(m_logger) << "Initialise DTC::SubscriberListener";
m_subscriberListener = new DDSGeneric::SubscriberListener();
m_ddsSubscriber = dds::sub::Subscriber(m_domainParticipantSub,
subscriberQos,
m_subscriberListener,
DDSGeneric::SubscriberListener::mask());
m_dtcTestPart = new DTC::SampleInfo::TestParticipantImpl(m_domainParticipantSub,
m_ddsSubscriber,
m_subscriberListener);
m_dtcTestPart->setSubscriberCallback([this](const DdsUnitTests::TestParticipant &data, const dds::sub::SampleInfo &si) { processData(data, si); } );
return true;
}
void DataTopicTests::deleteDTC()
{
delete m_dtcTestPart;
m_dtcTestPart = nullptr;
}
void DataTopicTests::deleteSubscriber()
{
m_ddsSubscriber = nullptr;
delete m_subscriberListener;
m_subscriberListener = nullptr;
}
void DataTopicTests::processData(const DdsUnitTests::TestParticipant &instance, const dds::sub::SampleInfo &si)
{
INFO_LOG(m_logger) << "process data in topic TestParticipant, Sampleinfo: " << si.source_timestamp().sec();
DEBUG_LOG(m_logger) << "participantId: " << instance.location().participantId();
DEBUG_LOG(m_logger) << "equipNo: " << instance.location().equipNo();
m_receivedParticipantData = instance;
QCOMPARE(m_mainThreadId, QThread::currentThreadId());
emit testParticipantDataArrived();
}
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <QObject>
#include <DDS/Generic/subscriber_listener.h>
#include <DdsUnitTests/ddsut_dtc_types.h>
#include <DdsUnitTests/ddsut_dts_types.h>
namespace NGL {
class INGLLogger;
}
class DataTopicTests : public QObject
{
Q_OBJECT
public:
DataTopicTests();
virtual ~DataTopicTests();
bool initPublisher();
bool initSubscriber();
bool write(const DdsUnitTests::TestParticipant &data);
DdsUnitTests::TestParticipant getReceivedParticipantData() const { return m_receivedParticipantData; }
void deleteDTC();
void deleteSubscriber();
protected:
void processData(const DdsUnitTests::TestParticipant &instance, const dds::sub::SampleInfo &si);
Q_SIGNALS:
void testParticipantDataArrived();
private:
dds::domain::DomainParticipant m_domainParticipantPub;
dds::domain::DomainParticipant m_domainParticipantSub;
int m_domainId;
int m_partitionId;
dds::pub::Publisher m_ddsPublisher;
DTS::TestParticipant *m_testPart;
dds::sub::Subscriber m_ddsSubscriber;
DDSGeneric::SubscriberListener *m_subscriberListener;
DTC::SampleInfo::TestParticipant *m_dtcTestPart;
Qt::HANDLE m_mainThreadId;
DdsUnitTests::TestParticipant m_receivedParticipantData;
NGL::INGLLogger& m_logger;
};
+102
View File
@@ -0,0 +1,102 @@
#include "data_topic_tests_main.h"
#include "data_topic_tests.h"
#include <Common/ProjectConfig/project_defines.h>
#include <QtTest/QTest>
#include <nglapplication/nglcoreapplication.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
int main(int argc, char *argv[])
{
NGLCoreApplication app(argc, argv);
app.createLogger("UnitTests", "log4cxx_ddsUnitTests.props");
DataTopicTestsMain *tc = new DataTopicTestsMain;
int retCode = QTest::qExec(tc, argc, argv);
delete tc;
return retCode;
}
DataTopicTestsMain::DataTopicTestsMain()
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTests.DataTopicTestsMain"))
{
}
void DataTopicTestsMain::initTestCase()
{
DEBUG_LOG(m_logger) <<"DataTopicTestsMain: initTestCase";
m_dtTest = new DataTopicTests();
QVERIFY2(m_dtTest, "Initialisation of DataTopicTest failed");
QVERIFY2(m_dtTest->initPublisher(), "Initialisation Publisher failed");
QVERIFY2(m_dtTest->initSubscriber(), "Initialisation Subscriber failed");
m_participantDataArrivedSpy = new QSignalSpy(m_dtTest, &DataTopicTests::testParticipantDataArrived);
}
void DataTopicTestsMain::publishTestCase()
{
DEBUG_LOG(m_logger) <<"DataTopicTestsMain: publish TestParticipant";
m_sentParticipantData.location().participantId(100);
m_sentParticipantData.location().equipNo(1010);
m_sentParticipantData.testId(100);
QVERIFY2(m_dtTest->write(m_sentParticipantData), "Publishing Topic TestParticipant failed");
DEBUG_LOG(m_logger) <<"DataTopicTestsMain: publish ready";
}
void DataTopicTestsMain::subscribeTestCase()
{
DEBUG_LOG(m_logger) << "IN subscribeTestCase";
QTRY_COMPARE(m_participantDataArrivedSpy->count(), 1);
DdsUnitTests::TestParticipant recvPartiticpant = m_dtTest->getReceivedParticipantData();
QCOMPARE(recvPartiticpant.location().participantId(), m_sentParticipantData.location().participantId());
QCOMPARE(recvPartiticpant.location().equipNo(), m_sentParticipantData.location().equipNo());
}
void DataTopicTestsMain::deleteDataReaderTestCase()
{
DdsUnitTests::TestParticipant data;
for (int i = 0; i < 1000; ++i)
{
m_sentParticipantData.id(i);
m_dtTest->write(m_sentParticipantData);
}
m_dtTest->deleteDTC();
for (int i = 0; i < 1000; ++i)
{
m_sentParticipantData.id(i);
m_dtTest->write(m_sentParticipantData);
}
m_dtTest->deleteSubscriber();
for (int i = 0; i < 1000; ++i)
{
m_sentParticipantData.id(i);
m_dtTest->write(m_sentParticipantData);
}
}
void DataTopicTestsMain::cleanupTestCase()
{
DEBUG_LOG(m_logger) <<"DataTopicTestsMain: cleanupTestCase";
delete m_dtTest; m_dtTest = nullptr;
delete m_participantDataArrivedSpy; m_participantDataArrivedSpy = nullptr;
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <QObject>
#include <QSignalSpy>
#include <DdsUnitTests/ddsut_dtc_types.h>
namespace NGL
{
class INGLLogger;
}
class DataTopicTests;
class DataTopicTestsMain : public QObject
{
Q_OBJECT
public:
DataTopicTestsMain();
private Q_SLOTS:
void initTestCase();
void publishTestCase();
void subscribeTestCase();
void deleteDataReaderTestCase();
void cleanupTestCase();
protected:
private:
DdsUnitTests::TestParticipant m_sentParticipantData;
QSignalSpy *m_participantDataArrivedSpy = nullptr;
DataTopicTests *m_dtTest = nullptr;
NGL::INGLLogger &m_logger;
};
+123
View File
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<service namespace="DdsUnitTests" abbreviation="DDSUT" procNames="">
<rcs>
<Id Id="$Id: core.xml 38132 2018-01-10 07:52:50Z ehrlicher $"/>
<HeadURL HeadURL="$HeadURL: svn://slssvn/TDS/branches/product/devel/trunk/Lts/DDS/Core/core.xml $"/>
<Author Author="$Author: ehrlicher $"/>
</rcs>
<tables/>
<views/>
<topicTypes>
<topicType name="BaseEntity">
<column name="id" idlType="long" isKey="1"/>
<column name="name" idlType="string"/>
<column name="isDeleted" idlType="boolean"/>
<column name="isUsed" idlType="boolean" comment="Defines if it is used by others (i.e. foreign key constraint)"/>
<column name="isBlockedForEdit" idlType="boolean"/>
</topicType>
<topicType name="BaseEntityTranslatable">
<column name="id" idlType="long" isKey="1"/>
<column name="name" idlType="string" isTranslatable="1"/>
<column name="isDeleted" idlType="boolean"/>
<column name="isUsed" idlType="boolean" comment="Defines if it is used by others (i.e. foreign key constraint)"/>
<column name="isBlockedForEdit" idlType="boolean"/>
</topicType>
<topicType name="BaseEntityWithSampleTime" extensibility="EXTENSIBLE" baseValueType="BaseEntity">
<column name="sampleTime" idlType="long long" comment="Timestamp"/>
</topicType>
<topicType name="CurrentRunningState" isDataObject="1" routeToClient="0" routeInReplay="0">
<column name="pid" idlType="long long" isKey="1"/>
<column name="runningState" idlType="Core::RunningState"/>
<column name="ports" idlType="sequence&lt;PortInformation&gt;" isOptional="1"/>
<column name="errorString" idlType="string" isOptional="1"/>
</topicType>
<topicType name="Entity" extensibility="EXTENSIBLE" baseValueType="BaseEntityWithSampleTime">
<column name="location" idlType="SpatialData"/>
<column name="symbolId" idlType="long"/>
</topicType>
<topicType name="FunctionReply">
<column name="id" idlType="long"/>
<column name="errorString" idlType="string"/>
<column name="errorParams" idlType="sequence&lt;string&gt;"/>
</topicType>
<topicType name="FunctionReplyWithState" baseValueType="FunctionReply">
<column name="replyState" idlType="Core::FtReplyState"/>
</topicType>
<topicType name="FunctionRequest">
<column name="id" idlType="long"/>
<column name="value" idlType="double"/>
<column name="info" idlType="string" isOptional="1"/>
</topicType>
<topicType name="FunctionRequestWithState" baseValueType="FunctionRequest">
<column name="status" idlType="long"/>
</topicType>
<topicType name="GeoPosition" extensibility="FINAL">
<column name="lon" idlType="double"/>
<column name="lat" idlType="double"/>
<column name="z" idlType="double" comment="height above ground"/>
</topicType>
<topicType name="GeoPosition2D" extensibility="FINAL">
<column name="lon" idlType="double" comment="[m]"/>
<column name="lat" idlType="double" comment="[m]"/>
</topicType>
<topicType name="Orientation" extensibility="FINAL">
<column name="heave" idlType="double"/>
<column name="pitch" idlType="double"/>
<column name="roll" idlType="double"/>
</topicType>
<topicType name="PartitionTime" isDataObject="1" routeToClient="1" routeInReplay="0">
<column name="timeStamp" idlType="long long"/>
</topicType>
<topicType name="PortInformation">
<column name="number" idlType="long"/>
<column name="type" idlType="PortType"/>
</topicType>
<topicType name="RelativePosition">
<column name="offsetX" idlType="double" comment="in meter"/>
<column name="offsetY" idlType="double" comment="in meter"/>
<column name="offsetZ" idlType="double" comment="in meter"/>
</topicType>
<topicType name="RemoveElementRequest">
<column name="id" idlType="long"/>
</topicType>
<topicType name="ReplayPartition" isDataObject="1" routeToClient="1" routeInReplay="0">
<column name="id" idlType="long" isKey="1"/>
<column name="name" idlType="string"/>
<column name="exerciseId" idlType="long"/>
<column name="workstationId" idlType="long"/>
<column name="operatorId" idlType="long"/>
<column name="replayType" idlType="Core::ReplayType"/>
<column name="domainId" idlType="long"/>
<column name="tcpConnectionUrl" idlType="string"/>
</topicType>
<topicType name="SpatialData" isDataObject="1" routeToClient="0" routeInReplay="0">
<column name="changedAt" idlType="long"/>
<column name="equipNo" idlType="long"/>
<column name="gpsPosition" idlType="GeoPosition"/>
<column name="orientation" idlType="Orientation"/>
<column name="participantId" idlType="long"/>
<column name="velocity" idlType="Velocity"/>
</topicType>
<topicType name="TestParticipant" isDataObject="1" routeToClient="0" routeInReplay="0" recordTopic="1" baseValueType="Entity">
<column name="designator" idlType="string&lt;20&gt;"/>
<column name="testId" idlType="long"/>
<column name="gpsQuality" idlType="short"/>
<column name="equipmentNumber" idlType="long"/>
</topicType>
<topicType name="Velocity">
<column name="x" idlType="double"/>
<column name="y" idlType="double"/>
<column name="z" idlType="double"/>
</topicType>
</topicTypes>
<functionTopics>
<functionTopic name="FunctionTopicTest" requestTopicType="FunctionRequest" replyTopicType="FunctionReply" requestTopicName="FunctionTopicTestRequest" replyTopicName="FunctionTopicTestReply" routeInReplay="0"/>
<functionTopic name="FunctionTopicTestWithState" requestTopicType="FunctionRequestWithState" replyTopicType="FunctionReplyWithState" requestTopicName="FunctionTopicTestWithStateRequest" replyTopicName="FunctionTopicTestWithStateReply" routeInReplay="0"/>
</functionTopics>
<enumerations>
<enumeration name="PortType">
<attribute key="Unknown" value="0"/>
<attribute key="WebSocketServer" value="1"/>
</enumeration>
</enumerations>
</service>
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <QtCore/qglobal.h>
#ifndef DDSUT_COMMON_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define DDSUT_COMMON_EXPORT
# else
# if defined(MAKE_DDSUT_COMMON_LIB)
# define DDSUT_COMMON_EXPORT Q_DECL_EXPORT
# ifndef WIN32
# ifdef NDDSUSERDllExport
# undef NDDSUSERDllExport
# endif
# define NDDSUSERDllExport Q_DECL_EXPORT
# endif
# else
# define DDSUT_COMMON_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
+257
View File
@@ -0,0 +1,257 @@
#include "function_topic_tests.h"
#include <Common/ProjectConfig/project_defines.h>
#include <DDS/DDSConnector/subscriber.h>
#include <DDS/DDSConnector/publisher.h>
#include <QtTest/QTest>
#include <nglapplication/nglcoreapplication.h>
#include <NGLLogger/INGLLogger.h>
int main(int argc, char *argv[])
{
NGLCoreApplication app(argc, argv);
app.createLogger("UnitTests", "log4cxx_ddsUnitTests.props");
FunctionTopicTests *tc = new FunctionTopicTests;
int retCode = QTest::qExec(tc, argc, argv);
delete tc;
return retCode;
}
FunctionTopicTests::FunctionTopicTests()
: m_mainThreadId(QThread::currentThreadId())
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTests.FunctionTopicTests"))
{
qRegisterMetaType<DdsUnitTests::FunctionReply>("DdsUnitTests::FunctionReply");
qRegisterMetaType<DdsUnitTests::FunctionReplyWithState>("DdsUnitTests::FunctionReplyWithState");
}
void FunctionTopicTests::initTestCase()
{
DEBUG_LOG(m_logger) <<"FunctionTopicTests: initTestCase";
const int domainId = 0;
m_ddsConnectorClient = std::make_shared<DDSConnector::DdsConnector>(domainId, IDL::PartitionType::Global);
QVERIFY2(m_ddsConnectorClient->init(), "Initialisation of ddsconnector (Client) failed");
m_ddsConnectorClient->initSubscriber();
QVERIFY2(m_ddsConnectorClient->getSubscriber(), "Initialisation of Subscriber (DdsConnectorClient) failed");
m_ddsConnectorClient->initPublisher();
QVERIFY2(m_ddsConnectorClient->getPublisher(), "Initialisation of Publisher (DdsConnectorClient) failed");
m_ddsConnectorServer = std::make_shared<DDSConnector::DdsConnector>(domainId, IDL::PartitionType::Global);
QVERIFY2(m_ddsConnectorServer->init(), "Initialisation of ddsconnector (Server) failed");
m_ddsConnectorServer->initSubscriber();
QVERIFY2(m_ddsConnectorServer->getSubscriber(), "Initialisation of Subscriber (DdsConnectorServer) failed");
m_ddsConnectorServer->initPublisher();
QVERIFY2(m_ddsConnectorServer->getPublisher(), "Initialisation of Publisher (DdsConnectorServer) failed");
}
void FunctionTopicTests::initFTSTestCase()
{
m_fts.reset(m_ddsConnectorServer->createFTS<FTS::FunctionTopicTest>());
QVERIFY2(m_fts.get(), "Initialisation of FTS Replier failed");
m_fts->setSubscriberCallback([this](const rti::core::SampleIdentity &identity, const DdsUnitTests::FunctionRequest &request) { this->onFunctionTopicRequest(identity, request);});
m_ftsWS.reset(m_ddsConnectorServer->createFTS<FTS::FunctionTopicTestWithState>());
QVERIFY2(m_ftsWS.get(), "Initialisation of FTS Replier failed");
m_ftsWS->setSubscriberCallback([this](const rti::core::SampleIdentity &identity, const DdsUnitTests::FunctionRequestWithState &request) { this->onFunctionTopicRequest(identity, request);});
}
void FunctionTopicTests::initFTCTestCase()
{
m_ftc.reset(m_ddsConnectorClient->createFTC<FTC::FunctionTopicTest>(this));
QVERIFY2(m_ftc.get(), "Initialisation of FTC Requester failed");
m_ftcWS.reset(m_ddsConnectorClient->createFTC<FTC::FunctionTopicTestWithState>(this));
QVERIFY2(m_ftc.get(), "Initialisation of FTC Requester failed");
}
void FunctionTopicTests::requestTestCase1()
{
requestWithState();
QCOMPARE(m_ddsConnectorServer->getWaitSetHandler()->attachedConditionCount(), 3);
requestWithState();
QCOMPARE(m_ddsConnectorServer->getWaitSetHandler()->attachedConditionCount(), 3);
}
void FunctionTopicTests::requestWithState()
{
QSignalSpy spyRequest(this, &FunctionTopicTests::functionTopicRequestFinished);
QSignalSpy spyReply(this, &FunctionTopicTests::functionTopicReply);
DEBUG_LOG(m_logger) <<"FunctionTopicTests: request";
DdsUnitTests::FunctionRequestWithState data;
data.id(1);
data.info(std::string("Test request"));
data.value(1000.0);
int uniqueId = 0;
bool bRet = m_ftcWS->requestDataClassic(data,
SLOT(slotRequestedData(DdsUnitTests::FunctionTopicTestWithStateReply,int,bool)),
SLOT(slotRequestedDataError(int,int,int)),
uniqueId);
QVERIFY2(bRet, "Could not request data!");
QTRY_COMPARE(spyRequest.count(), 1);
QTRY_COMPARE(spyReply.count(), 2);
}
void FunctionTopicTests::requestTestCase2()
{
QSignalSpy spyRequest(this, &FunctionTopicTests::functionTopicRequestFinished);
QSignalSpy spyReply(this, &FunctionTopicTests::functionTopicReply);
DEBUG_LOG(m_logger) <<"FunctionTopicTests: request";
DdsUnitTests::FunctionRequest data;
data.id(1);
data.info(std::string("Test request"));
data.value(1000.0);
int uniqueId = 0;
bool bRet = m_ftc->requestDataClassic(data,
SLOT(slotRequestedData(DdsUnitTests::FunctionTopicTestReply,int,bool)),
SLOT(slotRequestedDataError(int,int,int)),
uniqueId);
QVERIFY2(bRet, "Could not request data!");
QTRY_COMPARE(spyRequest.count(), 1);
QTRY_COMPARE(spyReply.count(), 1);
}
void FunctionTopicTests::cleanupTestCase()
{
DEBUG_LOG(m_logger) <<"FunctionTopicTests: cleanupTestCase";
m_fts = nullptr;
m_ftc = nullptr;
m_ftsWS = nullptr;
m_ftcWS = nullptr;
m_ddsConnectorServer = nullptr;
m_ddsConnectorClient = nullptr;
}
void FunctionTopicTests::onFunctionTopicRequest(const rti::core::SampleIdentity &sampleId,
const DdsUnitTests::FunctionRequestWithState &request)
{
QCOMPARE(m_mainThreadId, QThread::currentThreadId());
INFO_LOG(m_logger) << "Got function topic request (with status member)";
m_requestTopicWithState = request;
QCOMPARE(m_requestTopicWithState.id(), 1);
QCOMPARE(m_requestTopicWithState.info().get(), std::string("Test request"));
QCOMPARE(m_requestTopicWithState.value(), 1000.0);
DdsUnitTests::FunctionReplyWithState reply;
DEBUG_LOG(m_logger) << "Send sample with status REPLY_COMPLETED";
reply.id(1);
reply.errorString("Hello");
reply.replyState(Core::FtReplyState::Completed);
bool bRet = m_ftsWS->sendReply(sampleId, reply);
QVERIFY2(bRet, "Sending Reply failed");
emit functionTopicRequestFinished();
}
void FunctionTopicTests::onFunctionTopicRequest(const rti::core::SampleIdentity &sampleId,
const DdsUnitTests::FunctionRequest &request)
{
QCOMPARE(m_mainThreadId, QThread::currentThreadId());
INFO_LOG(m_logger) << "Got function topic request (without status member)";
m_requestTopic = request;
DdsUnitTests::FunctionReply reply;
reply.id(2);
reply.errorString("What?");
bool bRet = m_fts->sendReply(sampleId, reply);
QVERIFY2(bRet, "Sending Reply failed");
emit functionTopicRequestFinished();
}
void FunctionTopicTests::slotRequestedData(const DdsUnitTests::FunctionTopicTestWithStateReply &reply,
int seqNo, bool bFinished)
{
Q_UNUSED(seqNo);
QCOMPARE(m_mainThreadId, QThread::currentThreadId());
INFO_LOG(m_logger) << "Got reply from FTS: " << reply.replyState();
m_replyTopicWithState = reply;
if (m_replyTopicWithState.replyState() == Core::FtReplyState::InProgress)
{
QVERIFY(!bFinished);
QCOMPARE(m_replyTopicWithState.id(), 0);
}
else
{
QVERIFY(bFinished);
QCOMPARE(m_replyTopicWithState.id(), 1);
QCOMPARE(m_replyTopicWithState.errorString(), std::string("Hello"));
QCOMPARE(m_replyTopicWithState.replyState().underlying(), Core::FtReplyState::Completed);
}
emit functionTopicReply();
}
void FunctionTopicTests::slotRequestedData(const DdsUnitTests::FunctionTopicTestReply &reply,
int seqNo, bool bFinished)
{
Q_UNUSED(seqNo);
QVERIFY(bFinished);
QCOMPARE(m_mainThreadId, QThread::currentThreadId());
m_replyTopic = reply;
QCOMPARE(m_replyTopic.id(), 2);
QCOMPARE(m_replyTopic.errorString(), std::string("What?"));
emit functionTopicReply();
}
void FunctionTopicTests::slotRequestedDataError(int reason, int seqNo, int requestId)
{
Q_UNUSED(seqNo);
Q_UNUSED(requestId);
QFAIL(qPrintable(QStringLiteral("Error when trying to request data: %1").arg(reason)));
}
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include <QObject>
#include <QSignalSpy>
#include <QThread>
#include <DdsUnitTests/ddsut.hpp>
#include <DdsUnitTests/ddsut_ftc_types.h>
#include <DdsUnitTests/ddsut_fts_types.h>
#include <DDS/DDSConnector/dds_connector.h>
#include <DDS/FTC/generic_ftc_impl.h>
#include <DDS/FTS/generic_fts_impl.h>
namespace NGL
{
class INGLLogger;
}
class FunctionTopicTests : public QObject
{
Q_OBJECT
public:
FunctionTopicTests();
private Q_SLOTS:
void initTestCase();
void initFTSTestCase();
void initFTCTestCase();
void requestTestCase1();
void requestTestCase2();
void cleanupTestCase();
Q_SIGNALS:
void functionTopicRequestFinished();
void functionTopicReply();
protected Q_SLOTS:
void slotRequestedData(const DdsUnitTests::FunctionTopicTestReply &reply, int seqNo, bool bFinished);
void slotRequestedData(const DdsUnitTests::FunctionTopicTestWithStateReply &reply, int seqNo, bool bFinished);
void slotRequestedDataError(int reason, int seqNo, int requestId);
void onFunctionTopicRequest(const rti::core::SampleIdentity &sampleId,
const DdsUnitTests::FunctionRequest &request);
void onFunctionTopicRequest(const rti::core::SampleIdentity &sampleId,
const DdsUnitTests::FunctionRequestWithState &request);
private:
void requestWithState();
private:
std::unique_ptr<FTS::FunctionTopicTest> m_fts;
std::unique_ptr<FTS::FunctionTopicTestWithState> m_ftsWS;
std::unique_ptr<FTC::FunctionTopicTest> m_ftc;
std::unique_ptr<FTC::FunctionTopicTestWithState> m_ftcWS;
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnectorClient;
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnectorServer;
DdsUnitTests::FunctionRequest m_requestTopic;
DdsUnitTests::FunctionReply m_replyTopic;
DdsUnitTests::FunctionRequestWithState m_requestTopicWithState;
DdsUnitTests::FunctionReplyWithState m_replyTopicWithState;
Qt::HANDLE m_mainThreadId;
NGL::INGLLogger& m_logger;
};
+226
View File
@@ -0,0 +1,226 @@
#include "generic_data_tests.h"
#include <Common/ProjectConfig/project_defines.h>
#include <DDS/Generic/generic_data_reader.h>
#include <DDS/Generic/generic_data_writer.h>
#include <QtTest/QTest>
#include <nglapplication/nglcoreapplication.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
int main(int argc, char *argv[])
{
NGLCoreApplication app(argc, argv);
app.createLogger("UnitTests", "log4cxx_ddsUnitTests.props");
GenericDataTests *tc = new GenericDataTests;
int retCode = QTest::qExec(tc, argc, argv);
delete tc;
return retCode;
}
GenericDataTests::GenericDataTests()
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTests.GenericDataTests"))
{
m_spatialData.equipNo(0);
m_testPart.location().equipNo(0);
m_testPart.location().participantId(0);
}
void GenericDataTests::initTestCase()
{
DEBUG_LOG(m_logger) <<"GenericDataTests: initTestCase";
m_init = new Initialisation();
int domainId = 0;
int exerciseId = 2;
QVERIFY2(m_init, "Initialisation failed");
QVERIFY2(m_init->initDDS(domainId, exerciseId, Initialisation::USE_PUBLISHER), "Initialisation (Publisher) failed");
QVERIFY2(m_init->initDDS(domainId, exerciseId, Initialisation::USE_SUBSCRIBER), "Initialisation (Subscriber) failed");
QVERIFY2(m_init->getSubscriberListener(), "Initialisation SubscriberListener failed");
}
void GenericDataTests::initDRTestCase()
{
m_dataReader = new DTC::DroTestParticipant;
QVERIFY2(m_dataReader, "Initialisation of GenericDataReader(TestParticipant) failed");
QVERIFY2(m_dataReader->create(m_init->getDomainParticipantSub(), m_init->getDDSSubscriber()), "Creating DataReader (TestParticipant) failed");
m_init->getSubscriberListener()->addCallback(m_dataReader->getDataReader(), this);
m_dataReaderSd = new DTC::DroSpatialData;
QVERIFY2(m_dataReaderSd, "Initialisation of GenericDataReader(SpatialData) failed");
QVERIFY2(m_dataReaderSd->create(m_init->getDomainParticipantSub(), m_init->getDDSSubscriber()), "Creating DataReader (SpatialData) failed");
m_init->getSubscriberListener()->addCallback(m_dataReaderSd->getDataReader(), this);
}
void GenericDataTests::initDWTestCase()
{
m_dataWriter = new DTS::DwoTestParticipant();
QVERIFY2(m_dataWriter, "Initialisation of GenericDataWriter(TestParticipant) failed");
QVERIFY2(m_dataWriter->create(m_init->getDomainParticipant(), m_init->getDDSPublisher()), "Creating DataWriter (TestParticipant) failed");
m_dataWriterSd = new DTS::DwoSpatialData();
QVERIFY2(m_dataWriterSd, "Initialisation of GenericDataWriter(SpatialData) failed");
QVERIFY2(m_dataWriterSd->create(m_init->getDomainParticipant(), m_init->getDDSPublisher()), "Creating DataWriter (SpatialData) failed");
}
void GenericDataTests::dataWriterTestCase()
{
DEBUG_LOG(m_logger) <<"GenericDataTests: dataWriter";
DdsUnitTests::SpatialData data;
data.participantId(100);
data.equipNo(999);
data.gpsPosition().lon(70000);
data.gpsPosition().lat(90000);
data.gpsPosition().z(10);
data.orientation().heave(10);
data.orientation().pitch(10);
data.orientation().roll(10);
data.velocity().x(10);
data.velocity().y(2);
data.velocity().z(1);
data.changedAt(11111);
QVERIFY2(m_dataWriterSd->write(data), "Publishing Topic (SpatialData) failed");
DdsUnitTests::TestParticipant allData;
allData.name("************************ Name of TestParticpant ***********************");
allData.location() = data;
allData.id(10000);
QVERIFY2(m_dataWriter->write(allData), "Publishing Topic (TestParticipant) failed");
allData.id(11000);
QVERIFY2(m_dataWriter->write(allData), "Publishing Topic (TestParticipant) failed");
allData.id(11100);
QVERIFY2(m_dataWriter->write(allData), "Publishing Topic (TestParticipant) failed");
allData.id(11110);
QVERIFY2(m_dataWriter->write(allData), "Publishing Topic (TestParticipant) failed");
allData.id(11111);
QVERIFY2(m_dataWriter->write(allData), "Publishing Topic (TestParticipant) failed");
}
void GenericDataTests::dataReaderTestCase()
{
DEBUG_LOG(m_logger) << "GenericDataTests: dataReader";
QTRY_COMPARE(m_spatialData.equipNo(), 999);
QCOMPARE(m_spatialData.changedAt(), 11111);
QTRY_COMPARE(m_testPart.location().equipNo(), 999);
QCOMPARE(m_testPart.location().participantId(), 100);
bool bSeenUnreadSamples = false;
dds::sub::LoanedSamples<DdsUnitTests::TestParticipant> vecTp = m_dataReader->readAlive(bSeenUnreadSamples);
INFO_LOG(m_logger) <<"vecTp size: " << vecTp.length();
QCOMPARE(vecTp.length(), static_cast<decltype(vecTp.length())>(5));
QCOMPARE(vecTp[0].data().id(), 10000);
QCOMPARE(vecTp[1].data().id(), 11000);
QCOMPARE(vecTp[2].data().id(), 11100);
QCOMPARE(vecTp[3].data().id(), 11110);
QCOMPARE(vecTp[4].data().id(), 11111);
dds::sub::LoanedSamples<DdsUnitTests::TestParticipant> vecTpLoaned = m_dataReader->takeAlive(bSeenUnreadSamples);
INFO_LOG(m_logger) <<"vecTpLoaned size: " << vecTp.length();
QCOMPARE(vecTpLoaned.length(), static_cast<decltype(vecTpLoaned.length())>(5));
QCOMPARE(vecTpLoaned[0].data().id(), 10000);
QCOMPARE(vecTpLoaned[1].data().id(), 11000);
QCOMPARE(vecTpLoaned[2].data().id(), 11100);
QCOMPARE(vecTpLoaned[3].data().id(), 11110);
QCOMPARE(vecTpLoaned[4].data().id(), 11111);
}
void GenericDataTests::deleteDataReaderTestCase()
{
DdsUnitTests::TestParticipant data;
for (int i = 0; i < 1000; ++i)
{
data.id(i);
m_dataWriter->write(data);
}
m_init->getSubscriberListener()->removeCallback(m_dataReader->getDataReader(), this);
delete m_dataReader;
m_dataReader = nullptr;
for(int i = 0; i < 1000; ++i)
{
data.id(i);
m_dataWriter->write(data);
}
}
void GenericDataTests::cleanupTestCase()
{
DEBUG_LOG(m_logger) <<"GenericDataTests: cleanupTestCase";
if (m_dataReader)
{
m_init->getSubscriberListener()->removeCallback(m_dataReader->getDataReader(), this);
delete m_dataReader;
}
if (m_dataReaderSd)
{
m_init->getSubscriberListener()->removeCallback(m_dataReaderSd->getDataReader(), this);
delete m_dataReaderSd;
}
m_init->cleanupSubscriberListener();
delete m_dataWriter;
delete m_dataWriterSd;
delete m_init;
deleteLater();
}
void GenericDataTests::processData(const DdsUnitTests::SpatialData &data, DataModification modType)
{
DEBUG_LOG(m_logger) << "SpatialData: equipNo = " << data.equipNo() << " modType: " << modType;
m_spatialData = data;
}
void GenericDataTests::processData(const DdsUnitTests::TestParticipant &data, const dds::sub::SampleInfo &si)
{
Q_UNUSED(si);
DEBUG_LOG(m_logger) << "TestParticipant: equipNo = " << data.location().equipNo();
m_testPart = data;
}
void GenericDataTests::droDataAvailable(const std::string &topicType, const std::string &topicName)
{
Q_UNUSED(topicName)
DEBUG_LOG(m_logger) << "IN droDataAvailable: type = " << topicType;
if (topicType == "DdsUnitTests::TestParticipant")
{
m_init->getSubscriberListener()->enableCallback(m_dataReader->getDataReader(), this);
m_dataReader->read([this](const DdsUnitTests::TestParticipant &data, const dds::sub::SampleInfo &si) { processData(data, si); });
}
else if(topicType == "DdsUnitTests::SpatialData")
{
m_init->getSubscriberListener()->enableCallback(m_dataReaderSd->getDataReader(), this);
m_dataReaderSd->read([this](const DdsUnitTests::SpatialData &data, DataModification modType) { processData(data, modType); } );
}
}
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <QObject>
#include "initialisation.h"
#include <DdsUnitTests/ddsut_dtc_types.h>
#include <DdsUnitTests/ddsut_dts_types.h>
#include <DDS/Generic/subscriber_listener.h>
#include <LtsUtilsCore/data_modification_enum.h>
namespace NGL
{
class INGLLogger;
}
class GenericDataTests : public DDSGeneric::SubscriberListenerCallback
{
Q_OBJECT
public:
GenericDataTests();
private Q_SLOTS:
void initTestCase();
void initDRTestCase();
void initDWTestCase();
void dataWriterTestCase();
void dataReaderTestCase();
void deleteDataReaderTestCase();
void cleanupTestCase();
protected:
void droDataAvailable(const std::string &topicType, const std::string &topicName) override;
void noWriterAnymore(const std::string &topicType, const std::string &topicName) override
{
Q_UNUSED(topicType);
Q_UNUSED(topicName);
}
void writerAvailable(const std::string &topicType, const std::string &topicName) override
{
Q_UNUSED(topicType);
Q_UNUSED(topicName);
}
void processData(const DdsUnitTests::TestParticipant &data, const dds::sub::SampleInfo &si);
void processData(const DdsUnitTests::SpatialData &data, DataModification modType);
private:
Initialisation *m_init = nullptr;
DTS::DwoTestParticipant *m_dataWriter = nullptr;
DTC::DroTestParticipant *m_dataReader = nullptr;
DTS::DwoSpatialData *m_dataWriterSd = nullptr;
DTC::DroSpatialData *m_dataReaderSd = nullptr;
DdsUnitTests::SpatialData m_spatialData;
DdsUnitTests::TestParticipant m_testPart;
NGL::INGLLogger &m_logger;
};
+97
View File
@@ -0,0 +1,97 @@
#include "initialisation.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <DDS/QosProfile/DDS_DefaultQos.h>
#include <Common/ProjectConfig/project_defines.h>
Initialisation::Initialisation()
: m_domainParticipant(nullptr)
, m_domainParticipantSub(nullptr)
, m_domainId(0)
, m_exerciseId(-1)
, m_ddsPublisher(nullptr)
, m_ddsSubscriber(nullptr)
, m_subListener(new DDSGeneric::SubscriberListener)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTests.Initialisation"))
{
if (!DDS::QosProfile::setDefault())
ERROR_LOG(m_logger) << "Error setting default QoS from xml-file ";
}
Initialisation::~Initialisation()
{
cleanupSubscriberListener();
m_ddsSubscriber = nullptr;
m_ddsPublisher = nullptr;
}
void Initialisation::cleanupSubscriberListener()
{
if (m_ddsSubscriber != nullptr && m_subListener != nullptr)
{
m_ddsSubscriber.listener(nullptr, dds::core::status::StatusMask::none());
delete m_subListener;
m_subListener = nullptr;
}
}
bool Initialisation::initDDS(int domainId, int exerciseId, PubSub pubsub)
{
if (m_domainParticipant == nullptr)
m_domainParticipant = dds::domain::DomainParticipant(domainId, DDS::QosProfile::domainParticipantShm());
if (pubsub == USE_PUBLISHER || pubsub == USE_PUB_SUB)
{
dds::pub::qos::PublisherQos publisherQos = m_domainParticipant.default_publisher_qos();
DEBUG_LOG(m_logger) << "Publish on partition " << IDL::exercisePartition(exerciseId);
dds::core::policy::Partition partitionQos;
partitionQos.name( { IDL::exercisePartition(exerciseId) } );
publisherQos << partitionQos;
m_ddsPublisher = dds::pub::Publisher(m_domainParticipant,
publisherQos);
}
if (pubsub == USE_SUBSCRIBER || pubsub == USE_PUB_SUB)
{
dds::domain::DomainParticipant domainPart = m_domainParticipant;
if ( domainPart == nullptr || m_domainParticipantSub == nullptr)
{
m_domainParticipantSub = dds::domain::DomainParticipant(domainId, DDS::QosProfile::domainParticipantShm());
domainPart = m_domainParticipantSub;
}
dds::sub::qos::SubscriberQos subscriberQos = domainPart.default_subscriber_qos();
DEBUG_LOG(m_logger) << "Subsribing on partition " << IDL::exercisePartition(exerciseId);
dds::core::policy::Partition partitionQos;
partitionQos.name( { IDL::exercisePartition(exerciseId) } );
subscriberQos << partitionQos;
m_ddsSubscriber = dds::sub::Subscriber(domainPart,
subscriberQos,
m_subListener,
m_subListener->mask());
}
if (m_domainId == 0)
m_domainId = domainId;
if (m_exerciseId < 0)
m_exerciseId = exerciseId;
return true;
}
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include <DDS/Generic/generic_base.h>
#include <DDS/Generic/subscriber_listener.h>
namespace NGL {
class INGLLogger;
}
class Initialisation
{
public:
Initialisation();
virtual ~Initialisation();
enum PubSub
{
USE_PUBLISHER = 1,
USE_SUBSCRIBER = 2,
USE_PUB_SUB = 4
};
bool initDDS(int domainId, int exerciseId, PubSub pubsub = USE_PUBLISHER);
void cleanupSubscriberListener();
dds::domain::DomainParticipant getDomainParticipant() { return m_domainParticipant; }
dds::domain::DomainParticipant getDomainParticipantSub() { return m_domainParticipantSub != nullptr ? m_domainParticipantSub : m_domainParticipant; }
dds::pub::Publisher getDDSPublisher() { return m_ddsPublisher; }
dds::sub::Subscriber getDDSSubscriber() { return m_ddsSubscriber; }
DDSGeneric::SubscriberListener *getSubscriberListener() { return m_subListener; }
private:
dds::domain::DomainParticipant m_domainParticipant;
dds::domain::DomainParticipant m_domainParticipantSub;
int m_domainId;
int m_exerciseId;
dds::pub::Publisher m_ddsPublisher;
dds::sub::Subscriber m_ddsSubscriber;
DDSGeneric::SubscriberListener *m_subListener;
NGL::INGLLogger &m_logger;
};
+120
View File
@@ -0,0 +1,120 @@
log4j.reset=true
log4j.rootLogger=WARN, file
# Service: PlayerManagement
###########################################################
log4j.logger.UnitTests.main=WARN
# GenericDataTests
log4j.logger.UnitTests.Initialisation=WARN
log4j.logger.UnitTests.GenericDataTests=WARN
# DataTopicTests
log4j.logger.UnitTests.DataTopicTestsMain=WARN
log4j.logger.UnitTests.DataTopicTests=WARN
# FunctionTopicTests
log4j.logger.UnitTests.FunctionTopicTests=WARN
# GenericTopicSqlTests
log4j.logger.UnitTests.GenericTopicSqlTests=WARN
# Qos Infos
log4j.logger.DDS.DefaultQos=WARN
# Generic
#log4j.logger.GenericBase=DEBUG
#log4j.logger.Generic.DataWriter=DEBUG
#log4j.logger.Generic.DataReader=DEBUG
#log4j.logger.Generic.DroTypeInfo=DEBUG
log4j.logger.GenericBase=WARN
log4j.logger.Generic.Subscriber=WARN
log4j.logger.Generic.Publisher=WARN
log4j.logger.Generic.DataWriter=WARN
log4j.logger.Generic.DataReader=WARN
log4j.logger.Generic.DroTypeInfo=WARN
# DTCs
log4j.logger.Generic.DTC=WARN
log4j.logger.DTC.SubscriberListener=WARN
log4j.logger.DTC.Topic=WARN
log4j.logger.DTC.Exercise=WARN
log4j.logger.DTC.StatusDRWatcher=WARN
# FTSs / FTCs
log4j.logger.FTS.Replier=WARN
log4j.logger.FTC.Requester=WARN
log4j.logger.Generic.FTS=WARN
log4j.logger.Generic.FTC=WARN
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.MaxFileSize=1MB
log4j.appender.file.MaxBackupIndex=9
log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_dds_unittests.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n
# Appnd_Console
###########################################################
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= %d %p %c %c{1} - %m (%-.30F:%L) %n
# Appnd_Syslog
###########################################################
log4j.appender.syslog=org.apache.log4j.SyslogAppender
log4j.appender.syslog.Threshold=ERROR
log4j.appender.syslog.SysLogHost=localhost
log4j.appender.syslog.Facility=LOCAL7
log4j.appender.syslog.layout=org.apache.log4j.PatternLayout
log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m
log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true
log4j.appender.syslog.LevelRangeFilter.LevelMin=INFO
log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL
###########################################################
###########################################################
###########################################################
# 1) Available Levels in LOG4CXX Version 0.10.0:
# TRACE,
# DEBUG,
# INFO,
# WARN,
# ERROR
# FATAL
#
# d = date/time
# p = priority (Level)
# c = category/channel
# m = message
# F = file
# L = line
# n = platform dependent line separator character or characters.
# ....
#
# Examples for console:
# a)
# %d %p %c #%m# (%.50F:%L)%n
# date/time
# priority
# category/channel
# #message#
# (Maximal 50Character des Filename von rechts:Line)
# line separator
#
# b)
# %d (%.15F:%L) %-5p %c |#| %m%n
# date/time
# (Maximal 15Character des Filename von rechts:Line)
# priority, Right pad with spaces if the priority name is less than 5 characters long.
# category/channel
# message
# line separator
#
# Examples for files:
+48
View File
@@ -0,0 +1,48 @@
#
# $Id$
# $HeadURL$
# $Author$
#
# ddsftc
ADD_LIBRARY(ddsftc INTERFACE)
TARGET_INCLUDE_DIRECTORIES(ddsftc
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../../
)
TARGET_LINK_LIBRARIES(ddsftc
INTERFACE
Qt5::Core
ddsfts_connext
ddscore # due to the usage of Core::FtReplyState inside generic_ftc_impl.h, only on core.hpp - fixme :/
ddsgen
ddsQoS
RTI::nddsc
RTI::nddscpp2
ddsftccore
LtsUtilsCoreHeaderOnly
)
SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/generic_ftc.h
${CMAKE_CURRENT_SOURCE_DIR}/generic_ftc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/generic_ftc_impl.h
${CMAKE_CURRENT_SOURCE_DIR}/generic_ftc_threading.h
${CMAKE_CURRENT_SOURCE_DIR}/generic_ftc_threading.cpp
)
TARGET_SOURCES(ddsftc INTERFACE ${SRCS})
# ddsftccore
NGL_ADD_LIBRARY2(WS SV HLA ddsftccore SHARED abstract_requester.h abstract_requester.cpp)
NGL_TARGET_INCLUDE_DIRECTORIES(ddsftccore
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../../ # 'DDS/FTC/...' reads better than only FTC
)
NGL_TARGET_LINK_LIBRARIES(ddsftccore
PUBLIC
Qt5::Core
ddscore
)
NGL_INSTALL_TARGET(ddsftccore)
+6
View File
@@ -0,0 +1,6 @@
#include "abstract_requester.h"
Request2::Request2():QObject(){}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <DDS/FTC/ddsftccore_export.h>
#include <QObject>
class DDSFTCCORE_EXPORT Request2 : public QObject
{
Q_OBJECT
public:
explicit Request2();
signals:
void finished();
};
+5
View File
@@ -0,0 +1,5 @@
#include "generic_ftc.h"
namespace FTC
{
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <DDS/Core/core.hpp>
#include <future>
class Request2;
namespace FTC
{
template <typename RequestTopicType, typename ReplyTopicType>
class GenericFtc
{
public:
GenericFtc() = default;
virtual ~GenericFtc() = default;
virtual bool requestDataClassic(const RequestTopicType &data,
const char *replySlot,
const char *errorSlot,
int &uniqueId) = 0;
virtual std::future<ReplyTopicType> requestDataModern(const RequestTopicType &requestTopic, Request2 *requestWatcher) = 0;
};
}
+567
View File
@@ -0,0 +1,567 @@
#pragma once
#include "generic_ftc.h"
#include "abstract_requester.h"
#include "generic_ftc_threading.h"
#include <DDS/Core/core.hpp>
#include <DDS/FTS/connext.h>
#include <DDS/Generic/generic_logger.h>
#include <DDS/Generic/generic_base.h>
#include <DDS/QosProfile/DDS_DefaultQos.h>
#include <QObject>
#include <QMetaMethod>
#include <QTimer>
#include <queue>
#include <future>
namespace FTC
{
template <class C>
class HasStatusFunction
{
template <class T>
static std::true_type testSignature(void (T::*)(const Core::FtReplyState &));
template <class T>
static decltype(testSignature(&T::replyState)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
template <class C>
class HasRequestId
{
template <class T>
static std::true_type testSignature(const int32_t &(T::*)() const);
template <class T>
static std::true_type testSignature(int32_t (T::*)() const);
template <class T>
static decltype(testSignature(&T::requestId)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
template<typename GENERIC_FTC>
struct TopicNames
{};
template<typename TOPICTYPE_REQUEST, typename TOPICTYPE_REPLY, typename DUMMY>
class GenericFtcImpl final : public GenericFtc<TOPICTYPE_REQUEST, TOPICTYPE_REPLY>, public Threading
{
public:
using RequestTopic = TOPICTYPE_REQUEST;
using ReplyTopic = TOPICTYPE_REPLY;
using TopicNameHelper = TopicNames<GenericFtcImpl<RequestTopic, ReplyTopic, DUMMY>>;
GenericFtcImpl(const dds::domain::DomainParticipant &domainParticipant,
const dds::pub::Publisher &ddsPublisher,
const dds::sub::Subscriber &ddsSubscriber,
QObject *objToInform,
const std::string &partitionType,
int partitionId)
: GenericFtc<RequestTopic, ReplyTopic>()
, m_objToInform(objToInform)
, m_requesterParams(domainParticipant)
, m_partitionType(partitionType)
, m_partitionId(partitionId)
, m_logger(DDS::getLogger(TopicNameHelper::functionTopicName(), DDS::DataType::FUNCTION_TOPIC_CLIENT))
{
Q_ASSERT(domainParticipant != nullptr);
Q_ASSERT(ddsPublisher != nullptr);
Q_ASSERT(ddsSubscriber != nullptr);
Q_ASSERT(objToInform != nullptr);
m_requesterParams.request_topic_name(TopicNameHelper::requestTopicName());
m_requesterParams.reply_topic_name(TopicNameHelper::replyTopicName());
m_requesterParams.publisher(ddsPublisher);
m_requesterParams.subscriber(ddsSubscriber);
m_requesterParams.datareader_qos(DDS::QosProfile::dataReaderDefault(DDS::QosProfile::TopicType::FunctionTopic));
m_requesterParams.datawriter_qos(DDS::QosProfile::dataWriterDefault(DDS::QosProfile::TopicType::FunctionTopic));
DDS::QosProfile::print(m_requesterParams.datareader_qos(), m_logger);
DDS::QosProfile::print(m_requesterParams.datawriter_qos(), m_logger);
DEBUG_LOG(m_logger) << "=> Static request using request/reply topic names: "
<< TopicNameHelper::requestTopicName()
<< ", reply: "
<< TopicNameHelper::replyTopicName();
qRegisterMetaType<ReplyTopic>(TopicNameHelper::replyTopicTypeName());
preInit();
}
~GenericFtcImpl()
{
Q_ASSERT(!m_dying);
m_dying = true;
decltype(m_modernRequests) modernRequests;
decltype(m_classicRequests) classicRequests;
{
{
std::lock_guard<std::mutex> l(m_conditionMutex);
setWorkAvailable(false);
}
std::lock_guard<std::mutex> l(m_requestMutex);
Q_ASSERT(m_classicRequests.empty());
Q_ASSERT(m_modernRequests.empty());
classicRequests.swap(m_classicRequests);
modernRequests.swap(m_modernRequests);
}
for (; !modernRequests.empty(); modernRequests.pop())
{
ModernRequest<ReplyTopic> &request = modernRequests.front();
fprintf(stderr, "[!] GenericFtcImpl::dtor: there was a modern request in the queue while the Ftc is being destroyed. Throwing!\n");
Q_ASSERT(false);
request.promise.set_exception(std::make_exception_ptr(std::runtime_error("Session aborted.")));
emit request.requestWatcher->finished();
}
for (; !classicRequests.empty(); classicRequests.pop())
{
ClassicRequest &request = classicRequests.front();
fprintf(stderr, "[!] GenericFtcImpl::dtor: there was a classic request in the queue while the Ftc is being destroyed. Throwing!\n");
Q_ASSERT(false);
sendError(request, 0, true );
}
}
struct ClassicRequest
{
connext::WriteSample<RequestTopic> sample;
QMetaMethod replySlot;
QMetaMethod errorSlot;
int uniqueId = 0;
};
bool requestDataClassic(const RequestTopic &requestTopic,
const char *replySlot,
const char *errorSlot,
int &uniqueId) override
{
Q_ASSERT(!m_dying);
ClassicRequest request;
if (!checkSlots(request, replySlot, errorSlot))
return false;
request.sample.set_data(requestTopic);
request.uniqueId = ++m_uniqueId;
uniqueId = request.uniqueId;
createThread();
{
std::lock_guard<std::mutex> l(m_requestMutex);
m_classicRequests.push(request);
}
runThread();
return true;
}
template<typename ReplyTopic>
struct ModernRequest
{
std::promise<ReplyTopic> promise;
connext::WriteSample<RequestTopic> sample;
Request2 *requestWatcher;
};
std::future<ReplyTopic> requestDataModern(const RequestTopic &requestTopic, Request2 *requestWatcher) override
{
Q_ASSERT(!m_dying);
std::promise<ReplyTopic> promise;
std::future<ReplyTopic> future = promise.get_future();
connext::WriteSample<RequestTopic> sample;
sample.set_data(requestTopic);
createThread();
{
std::lock_guard<std::mutex> l(m_requestMutex);
m_modernRequests.push({ std::move(promise), sample, requestWatcher });
}
runThread();
return future;
}
private:
QString functionTopicName() const override
{
return QString::fromUtf8(TopicNameHelper::functionTopicName());
}
int partitionId() const override
{
return m_partitionId;
}
QString partitionType() const override
{
return QString::fromStdString(m_partitionType);
}
std::unique_ptr<connext::Requester<RequestTopic, ReplyTopic> > requester;
void cleanupThread() override
{
requester.reset();
}
void threadDone() override
{
for (; !m_modernRequests.empty(); m_modernRequests.pop())
{
ModernRequest<ReplyTopic> &request = m_modernRequests.front();
fprintf(stderr, "[!] GenericFtcImpl::threadDone: there was a modern request in the queue while the thread already ended. Throwing!\n");
Q_ASSERT(false);
request.promise.set_exception(std::make_exception_ptr(std::runtime_error("Worker thread timed out.")));
}
for (; !m_classicRequests.empty(); m_classicRequests.pop())
{
ClassicRequest &request = m_classicRequests.front();
fprintf(stderr, "[!] GenericFtcImpl::dtor: there was a classic request in the queue while the thread already ended. Throwing!\n");
Q_ASSERT(false);
sendError(request, 0, true );
}
}
void work() override
{
try
{
decltype(m_classicRequests) classicRequests;
decltype(m_modernRequests) modernRequests;
Q_ASSERT(isWorkAvailable());
if (!isWorkAvailable())
{
fprintf(stderr, "[!] GenericFtc::work: called while marked as no work available! run? %d (%s)\n", isRun(), threadName().toStdString().c_str());
}
setWorkAvailable(false);
{
std::lock_guard<std::mutex> l(m_requestMutex);
if (!m_classicRequests.empty())
{
m_classicRequests.swap(classicRequests);
}
if (!m_modernRequests.empty())
{
m_modernRequests.swap(modernRequests);
}
}
cancellationPoint();
if (classicRequests.empty() && modernRequests.empty())
{
fprintf(stderr, "[!] GenericFtc::work: called without having any requests! (%s)\n", threadName().toStdString().c_str());
Q_ASSERT(false);
return;
}
if (!requester)
{
requester.reset(new connext::Requester<RequestTopic, ReplyTopic>(m_requesterParams));
}
for (; !classicRequests.empty(); classicRequests.pop())
{
cancellationPoint();
ClassicRequest &request = classicRequests.front();
requester->send_request(request.sample);
cancellationPoint();
getRequestedDataClassic(*requester.get(), request);
cancellationPoint();
}
for (; !modernRequests.empty(); modernRequests.pop())
{
cancellationPoint();
ModernRequest<ReplyTopic> &request = modernRequests.front();
requester->send_request(request.sample);
cancellationPoint();
try
{
getModernRequestedData(*requester.get(), request);
}
catch (...)
{
request.promise.set_exception(std::current_exception());
QTimer::singleShot(0, request.requestWatcher, &Request2::finished);
}
cancellationPoint();
}
}
catch (const dds::core::Exception &ex)
{
fprintf(stderr, "[!] GenericFtc::work: dds::core::exception: %s (%s)\n", ex.what(), threadName().toStdString().c_str());
}
catch (const std::exception &ex)
{
fprintf(stderr, "[!] GenericFtc::work: exception: %s (%s)\n", ex.what(), threadName().toStdString().c_str());
}
catch(...)
{
fprintf(stderr, "[!] GenericFtc::work: unknown exception occured !!! (%s)\n", threadName().toStdString().c_str());
}
}
template <typename T, typename std::enable_if<HasStatusFunction<T>::value, int>::type = 0>
static inline bool isInProgress(const connext::Sample<T> &reply)
{
return (reply.data().replyState() == Core::FtReplyState::InProgress);
}
template <typename T, typename std::enable_if<!HasStatusFunction<T>::value, int>::type = 0>
static inline bool isInProgress(const connext::Sample<T> &)
{
return false;
}
void getRequestedDataClassic(connext::Requester<RequestTopic, ReplyTopic> &requester, ClassicRequest &request)
{
#ifdef WIN32
const dds::core::Duration MAX_WAIT(200);
#else
const dds::core::Duration MAX_WAIT(20);
#endif
bool inProgress = true;
while (inProgress && isRun())
{
DEBUG_LOG(m_logger) << "Waiting for reply ";
connext::Sample<ReplyTopic> reply;
if (!requester.receive_reply(reply, MAX_WAIT))
{
WARNING_LOG(m_logger) << "Timed out waiting for reply";
sendError(request, 3);
return;
}
if (reply.info().valid())
{
if (request.sample.info().identity() != reply.info()->related_original_publication_virtual_sample_identity())
{
INFO_LOG(m_logger) << "#receiveReply - outdated. sample id: " << connext::DebugInfo::toReadable(request.sample.info().identity())
<< ", virutal sample id: " << connext::DebugInfo::toReadable(reply.info()->related_original_publication_virtual_sample_identity());
continue;
}
inProgress = isInProgress(reply);
if (request.replySlot.isValid())
{
INFO_LOG(m_logger) << "#receiveReply for " << TopicNameHelper::replyTopicTypeName();
request.replySlot.invoke(m_objToInform, Qt::QueuedConnection,
QArgument<ReplyTopic>(TopicNameHelper::replyTopicTypeName(), reply.data()),
Q_ARG(int, request.uniqueId),
Q_ARG(bool, !inProgress));
}
else
{
WARNING_LOG(m_logger) << "reply slot doesn't exist for " << dds::topic::topic_type_name<ReplyTopic>::value();
}
if (!inProgress)
return;
}
else
WARNING_LOG(m_logger) << "No valid data available ";
}
}
void getModernRequestedData(connext::Requester<RequestTopic, ReplyTopic> &requester, ModernRequest<ReplyTopic> &request) noexcept(false)
{
#ifdef WIN32
const dds::core::Duration MAX_WAIT(200);
#else
const dds::core::Duration MAX_WAIT(20);
#endif
while (isRun())
{
connext::Sample<ReplyTopic> reply;
if (!requester.receive_reply(reply, MAX_WAIT))
{
WARNING_LOG(m_logger) << "Timeout while waiting for reply!";
throw std::runtime_error("Timeout while waiting for reply!");
}
if (!reply.info().valid())
{
WARNING_LOG(m_logger) << "No valid data available!";
throw std::runtime_error("No valid data available!");
}
if (request.sample.info().identity() != reply.info()->related_original_publication_virtual_sample_identity())
{
INFO_LOG(m_logger) << "#receiveReply - outdated. sample id: " << connext::DebugInfo::toReadable(request.sample.info().identity())
<< ", virutal sample id: " << connext::DebugInfo::toReadable(reply.info()->related_original_publication_virtual_sample_identity());
continue;
}
Q_ASSERT(!isInProgress(reply));
if (isInProgress(reply))
{
ERROR_LOG(m_logger) << "Used ModernRequest for a request with FtReplyState! Your data will most likely be imcomplete!";
}
INFO_LOG(m_logger) << "#receiveReply for " << TopicNameHelper::replyTopicTypeName();
request.promise.set_value(reply.data());
QTimer::singleShot(0, request.requestWatcher, &Request2::finished);
return;
}
}
void sendErrorInternal(const QMetaMethod &errorSlot, int reason, int uniqueId, int requestId, bool directConnection = false)
{
if (errorSlot.isValid())
{
errorSlot.invoke(m_objToInform, directConnection ? Qt::DirectConnection : Qt::QueuedConnection,
Q_ARG(int, reason),
Q_ARG(int, uniqueId),
Q_ARG(int, requestId));
}
}
template < typename T = TOPICTYPE_REQUEST, typename std::enable_if < HasRequestId<T>::value, int >::type = 0 >
void sendError(const ClassicRequest &request, int reason, bool directConnection = false)
{
sendErrorInternal(request.errorSlot, reason, request.uniqueId, request.sample.data().requestId(), directConnection);
}
template < typename T = TOPICTYPE_REQUEST, typename std::enable_if < !HasRequestId<T>::value, int >::type = 0 >
void sendError(const ClassicRequest &request, int reason, bool directConnection = false)
{
sendErrorInternal(request.errorSlot, reason, request.uniqueId, -1, directConnection);
}
bool checkSlots(ClassicRequest &request, const char *replySlot, const char *errorSlot)
{
const QByteArray baReply = QByteArray::number(QSIGNAL_CODE) +
"signalSignature(" +
TopicNameHelper::replyTopicTypeName() +
",int,bool)";
request.replySlot = DDS::Helper::getMethod(m_logger, m_objToInform, replySlot, baReply.data());
if (!request.replySlot.isValid())
return false;
if (errorSlot)
{
const QByteArray baError = QByteArray::number(QSIGNAL_CODE) + "signalSignature(int,int,int)";
request.errorSlot = DDS::Helper::getMethod(m_logger, m_objToInform, errorSlot, baError.data());
if (!request.errorSlot.isValid())
return false;
}
else
request.errorSlot = QMetaMethod();
return true;
}
QObject *m_objToInform;
int m_uniqueId { 0 };
std::queue<ModernRequest<ReplyTopic> > m_modernRequests;
std::queue<ClassicRequest> m_classicRequests;
connext::RequesterParams m_requesterParams;
bool m_dying { false };
std::string m_partitionType;
int m_partitionId;
std::mutex m_requestMutex;
NGL::INGLLogger &m_logger;
};
}
+330
View File
@@ -0,0 +1,330 @@
#include "generic_ftc_threading.h"
#include <QAtomicInt>
#include <QTimer>
#include <QRegularExpression>
#include <chrono>
#include <future>
#ifdef Q_OS_LINUX
#include <pthread.h>
#endif
Threading::~Threading()
{
if (m_threadLifetime != nullptr)
{
m_threadLifetime->stop();
m_threadLifetime.reset();
}
setCleanupNeeded(false);
{
std::lock_guard<std::mutex> l(m_conditionMutex);
setRun(false);
}
if (!m_used)
{
}
if (m_thread.joinable())
{
m_condition.notify_all();
m_thread.join();
}
}
void Threading::preInit() const
{
m_partitionId = partitionId();
m_partitionType = partitionType();
const QString base(QStringLiteral("F%1%2").arg(m_partitionType.left(1)).arg(m_partitionId));
const int baseLen = base.length();
const int maxLen = 16 - 1 - baseLen;
if (maxLen < 1)
{
Q_ASSERT(false);
m_threadName = functionTopicName();
return;
}
const QString orgName(functionTopicName());
QString name;
if (orgName.length() > maxLen)
{
QRegularExpression regexp(QStringLiteral("[A-Z][a-z]*"));
auto matches = regexp.globalMatch(functionTopicName());
QStringList words;
while (matches.hasNext())
{
const auto word = matches.next().capturedTexts().join(QString());
words += word;
}
if (words.isEmpty())
{
Q_ASSERT(false);
m_threadName = functionTopicName();
return;
}
int wordsLeft = words.count();
if (wordsLeft < 1)
{
Q_ASSERT(false);
m_threadName = functionTopicName();
return;
}
int bremse = 20;
while (wordsLeft > 0)
{
if (bremse < 1)
{
Q_ASSERT(false);
break;
}
bremse--;
const int lenRest = maxLen - name.count();
if (lenRest < 1)
{
Q_ASSERT(false);
m_threadName = functionTopicName();
return;
}
const int maxWordLen = lenRest / wordsLeft;
if (words.isEmpty())
{
Q_ASSERT(false);
break;
}
name += words.takeFirst().leftRef(maxWordLen);
wordsLeft--;
}
const int finalLeft = maxLen - name.count();
if (finalLeft < 0 || (finalLeft > 0 && functionTopicName().length() > maxLen))
{
}
}
else
{
name = orgName;
}
m_threadName = QStringLiteral("%1%2[%3]").arg(base, name).arg(qrand(), 8, 16, QLatin1Char('0'));
}
const QString &Threading::threadName() const
{
if (m_threadName.isEmpty())
{
Q_ASSERT(false);
}
return m_threadName;
}
const char *Threading::posixThreadName() const
{
if (m_posixThreadName.empty())
{
m_posixThreadName = threadName().left(15).toStdString();
}
return m_posixThreadName.c_str();
}
void Threading::createThread()
{
if (!isRun())
{
if (m_thread.joinable())
{
m_condition.notify_all();
m_thread.join();
threadDone();
}
m_alreadyThreaded = 0;
setRun(true);
m_thread = std::thread(&Threading::run, this, threadName());
#ifdef Q_OS_LINUX
pthread_setname_np(m_thread.native_handle(), posixThreadName());
#endif
}
m_createThreadCalledBefore = true;
}
void Threading::runThread()
{
m_used = true;
Q_ASSERT(m_createThreadCalledBefore);
m_createThreadCalledBefore = false;
{
std::lock_guard<std::mutex> l(m_conditionMutex);
setWorkAvailable(true);
}
m_condition.notify_all();
if (!m_threadLifetime)
{
m_threadLifetime.reset(new QTimer);
m_threadLifetime->setSingleShot(true);
m_threadLifetime->setInterval(11*1000);
QObject::connect(m_threadLifetime.get(), &QTimer::timeout, m_threadLifetime.get(),
[this]
{
bool workAvailable = false;
bool run = false;
bool working = false;
{
std::lock_guard<std::mutex> l(m_conditionMutex);
workAvailable = isWorkAvailable();
run = isRun();
working = isWorking();
}
if (workAvailable || working)
{
m_threadLifetime->start();
return;
}
m_threadLifetime->deleteLater();
m_threadLifetime = nullptr;
if (!run)
{
Q_ASSERT(false);
return;
}
{
std::lock_guard<std::mutex> l(m_conditionMutex);
setRun(false);
}
m_condition.notify_all();
});
}
m_threadLifetime->start();
}
void Threading::run(const QString &threadName)
{
Q_ASSERT(m_alreadyThreaded == 0);
m_alreadyThreaded = 1;
const std::string _tn = threadName.toStdString();
const char *tn = _tn.c_str();
while (isRun())
{
try
{
{
std::unique_lock<std::mutex> conditionLock(m_conditionMutex);
m_condition.wait(conditionLock, [this]{ return (isWorkAvailable() || !isRun()); });
}
cancellationPoint();
{
std::lock_guard<std::mutex> l(m_conditionMutex);
setWorking(true);
}
work();
{
std::lock_guard<std::mutex> l(m_conditionMutex);
setWorking(false);
}
cancellationPoint();
}
catch (const CancelationPointReached &ex)
{
break;
}
catch (const std::exception &ex)
{
fprintf(stderr, "[!] Threading::run: exception: %s (%s)\n", ex.what(), tn);
}
catch(...)
{
fprintf(stderr, "[!] Threading::run: unknown exception occured (%s) !!!\n", tn);
}
}
if (isCleanupNeeded())
{
cleanupThread();
}
else
{
}
}
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include <QObject>
#include <QAtomicInt>
#include <QTimer>
#include <thread>
#include <exception>
#include <condition_variable>
class CancelationPointReached : public std::runtime_error
{
public:
explicit CancelationPointReached()
: std::runtime_error("CancelationPointReached")
{}
};
class Threading
{
public:
Threading() = default;
~Threading();
protected:
void preInit() const;
void createThread();
void runThread();
inline bool isRun() const { return (m_run != 0); }
inline void setRun(bool run) { m_run = (run ? 1 : 0); }
inline void setWorkAvailable(bool workAvailable) { m_workAvailable = (workAvailable ? 1 : 0); }
inline bool isWorkAvailable() const { return (m_workAvailable != 0); }
inline void setWorking(bool working) { m_isWorking = (working ? 1 : 0); }
inline bool isWorking() const { return (m_isWorking != 0); }
const QString &threadName() const;
inline const char *posixThreadName() const;
virtual void work() = 0;
virtual QString functionTopicName() const = 0;
virtual int partitionId() const = 0;
virtual QString partitionType() const = 0;
virtual void cleanupThread() = 0;
virtual void threadDone() = 0;
inline void cancellationPoint() const noexcept(false)
{
if (!isRun())
{
throw CancelationPointReached();
}
}
std::mutex m_conditionMutex;
private:
inline bool isCleanupNeeded() const { return (m_cleanupNeeded != 0); }
inline void setCleanupNeeded(bool cleanupNeeded) { m_cleanupNeeded = (cleanupNeeded ? 1 : 0); }
void run(const QString &threadName);
std::unique_ptr<QTimer> m_threadLifetime;
QAtomicInt m_run { 0 };
QAtomicInt m_cleanupNeeded { 1 };
std::thread m_thread;
std::condition_variable m_condition;
QAtomicInt m_workAvailable { 0 };
QAtomicInt m_isWorking { 0 };
mutable QString m_threadName;
mutable std::string m_posixThreadName;
QAtomicInt m_alreadyThreaded;
mutable int m_partitionId { -1 };
mutable QString m_partitionType;
bool m_used { false };
bool m_createThreadCalledBefore { false };
};
+45
View File
@@ -0,0 +1,45 @@
#
# $Id$
# $HeadURL$
# $Author$
#
ADD_LIBRARY(ddsfts_connext INTERFACE)
TARGET_INCLUDE_DIRECTORIES(ddsfts_connext
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../../
)
TARGET_LINK_LIBRARIES(ddsfts_connext
INTERFACE
RTI::nddscpp2
)
SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/connext.h
)
TARGET_SOURCES(ddsfts_connext INTERFACE ${SRCS})
SET(SRCS
generic_fts.h ;# Interface
generic_fts_impl.h ;# Implementation, using internal class GenericFTCInternal
generic_fts_internal.h
generic_fts_internal.cpp
)
NGL_ADD_LIBRARY2(WS AAR SV HLA ddsfts SHARED ${SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../../
)
NGL_TARGET_LINK_LIBRARIES(ddsfts
PRIVATE
RTI::nddscore
ProjectConfig
ddsQoS
PUBLIC
Qt5::Core
RTI::nddsc
RTI::nddscpp2
ddsgen
ddsfts_connext
)
+502
View File
@@ -0,0 +1,502 @@
#pragma once
#include <dds/core/cond/GuardCondition.hpp>
#include <dds/core/cond/StatusCondition.hpp>
#include <dds/core/cond/WaitSet.hpp>
#include <dds/domain/DomainParticipant.hpp>
#include <dds/pub/DataWriter.hpp>
#include <dds/pub/Publisher.hpp>
#include <dds/sub/DataReader.hpp>
#include <dds/sub/SampleInfo.hpp>
#include <dds/sub/Subscriber.hpp>
#include <dds/topic/ContentFilteredTopic.hpp>
#include <dds/topic/find.hpp>
#include <rti/pub/WriteParams.hpp>
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace connext
{
namespace DebugInfo
{
inline QByteArray toReadable(const rti::core::Guid &guid)
{
const QByteArray str(reinterpret_cast<const char *>(&guid[0]), 16);
return str.toHex();
}
inline QByteArray toReadable(const rti::core::SequenceNumber &seqNo,
const rti::core::Guid &guid)
{
return "SeqNo: " + QByteArray::number(seqNo.value()) +
", Guid-DW: " + toReadable(guid);
}
inline QByteArray toReadable(const rti::core::SampleIdentity &identiy)
{
return toReadable(identiy.sequence_number(), identiy.writer_guid());
}
}
template<typename T>
dds::topic::Topic<T> getTopic(const dds::domain::DomainParticipant &participant,
const std::string &topicName)
{
using DataTopic = dds::topic::Topic<T>;
auto ret = dds::topic::find<DataTopic>(participant, topicName);
if (ret != nullptr)
return ret;
return DataTopic(participant, topicName);
}
template<typename T>
class SampleBase
{
protected:
SampleBase() = default;
virtual ~SampleBase() = default;
public:
void set_data(const T &data)
{
m_data = data;
}
T &data()
{
return m_data;
}
const T &data() const
{
return m_data;
}
protected:
T m_data;
};
template<typename T>
class WriteSample : public SampleBase<T>
{
public:
WriteSample() = default;
void set_info(const rti::pub::WriteParams &info)
{
m_info = info;
}
rti::pub::WriteParams &info()
{
return m_info;
}
const rti::pub::WriteParams &info() const
{
return m_info;
}
private:
rti::pub::WriteParams m_info;
};
template<typename T>
class WriteSampleRef
{
public:
WriteSampleRef()
: m_data_ptr(nullptr)
, m_info_ptr(nullptr)
{}
void set_data(T *data_ptr)
{
m_data_ptr = data_ptr;
}
T &data()
{
return *m_data_ptr;
}
void set_info(rti::pub::WriteParams *info_ptr)
{
m_info_ptr = info_ptr;
}
rti::pub::WriteParams &info()
{
return *m_info_ptr;
}
private:
T *m_data_ptr;
rti::pub::WriteParams *m_info_ptr;
};
template<typename T>
class Sample : public SampleBase<T>
{
public:
Sample() = default;
void set_info(const dds::sub::SampleInfo &info)
{
m_info = info;
}
dds::sub::SampleInfo &info()
{
return m_info;
}
const dds::sub::SampleInfo &info() const
{
return m_info;
}
void set_identity(const rti::core::SampleIdentity &identity)
{
m_identity = identity;
}
const rti::core::SampleIdentity &identity() const
{
return m_identity;
}
private:
dds::sub::SampleInfo m_info;
rti::core::SampleIdentity m_identity;
};
class RequesterParams
{
public:
explicit RequesterParams(const dds::domain::DomainParticipant &participant)
: m_participant(participant)
, m_publisher(nullptr)
, m_subscriber(nullptr)
{}
virtual ~RequesterParams() = default;
const std::string &request_topic_name() const
{
return m_request_topic_name;
}
void request_topic_name(const std::string &request_topic_name)
{
m_request_topic_name = request_topic_name;
}
const std::string &reply_topic_name() const
{
return m_reply_topic_name;
}
void reply_topic_name(const std::string &reply_topic_name)
{
m_reply_topic_name = reply_topic_name;
}
const dds::domain::DomainParticipant &domainParticipant() const
{
return m_participant;
}
const dds::pub::Publisher &publisher() const
{
return m_publisher;
}
void publisher(const dds::pub::Publisher &publisher)
{
m_publisher = publisher;
}
const dds::sub::Subscriber &subscriber() const
{
return m_subscriber;
}
void subscriber(const dds::sub::Subscriber &subscriber)
{
m_subscriber = subscriber;
}
const dds::pub::qos::DataWriterQos &datawriter_qos() const
{
return m_datawriter_qos;
}
void datawriter_qos(const dds::pub::qos::DataWriterQos &datawriter_qos)
{
m_datawriter_qos = datawriter_qos;
}
const dds::sub::qos::DataReaderQos &datareader_qos() const
{
return m_datareader_qos;
}
void datareader_qos(const dds::sub::qos::DataReaderQos &datareader_qos)
{
m_datareader_qos = datareader_qos;
}
protected:
dds::domain::DomainParticipant m_participant;
dds::pub::Publisher m_publisher;
dds::sub::Subscriber m_subscriber;
dds::pub::qos::DataWriterQos m_datawriter_qos;
dds::sub::qos::DataReaderQos m_datareader_qos;
std::string m_request_topic_name;
std::string m_reply_topic_name;
};
template <typename REQUEST, typename REPLY>
class ReplierParams : public RequesterParams
{
public:
using RequesterParams::RequesterParams;
};
template<typename REQUEST, typename REPLY>
class ReplyRequestBase
{
protected:
explicit ReplyRequestBase(const connext::RequesterParams &requesterParams)
: m_requestTopic(getTopic<REQUEST>(requesterParams.domainParticipant(),
requesterParams.request_topic_name()))
, m_replyTopic(getTopic<REPLY>(requesterParams.domainParticipant(),
requesterParams.reply_topic_name()))
{}
virtual ~ReplyRequestBase() = default;
protected:
dds::topic::Topic<REQUEST> m_requestTopic;
dds::topic::Topic<REPLY> m_replyTopic;
};
template<typename REQUEST, typename REPLY>
class Requester final : public ReplyRequestBase<REQUEST, REPLY>
{
public:
explicit Requester(const connext::RequesterParams &requesterParams)
: ReplyRequestBase<REQUEST, REPLY>(requesterParams)
, m_requesterParams(requesterParams)
, m_dataWriter(nullptr)
, m_dataReader(nullptr)
, m_contentFilteredTopic(nullptr)
, m_curInstanceHandle(dds::core::InstanceHandle::nil())
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("connext.requester"))
{}
void create_contentfilter(bool bNewDataWriter)
{
if (!bNewDataWriter && m_contentFilteredTopic != nullptr)
return;
rti::core::policy::DataWriterProtocol dwp;
m_dataWriter.qos() >> dwp;
const rti::core::Guid dwGuid = dwp.virtual_guid();
const QByteArray writerGuid = DebugInfo::toReadable(dwGuid);
const QByteArray filterContent = "@related_sample_identity.writer_guid.value = &hex(" + writerGuid + ")";
if (m_contentFilteredTopic == nullptr)
{
const std::string rtName = ReplyRequestBase<REQUEST, REPLY>::m_replyTopic.name();
const QByteArray filterName(QByteArray::fromRawData(rtName.c_str(), static_cast<int>(rtName.size())) +
QByteArray(" ") + writerGuid);
m_contentFilteredTopic = dds::topic::ContentFilteredTopic<REPLY>(ReplyRequestBase<REQUEST, REPLY>::m_replyTopic,
filterName.data(),
dds::topic::Filter(filterContent.data()));
}
else
{
m_contentFilteredTopic->filter(dds::topic::Filter(filterContent.data()));
}
DEBUG_LOG(m_logger) << "content filter for topic "
<< ReplyRequestBase<REQUEST, REPLY>::m_requestTopic.name()
<< " - related_sample_identity should be: "
<< writerGuid;
}
void send_request(connext::WriteSample<REQUEST> &request)
{
bool bNewDataWriter = false;
if (m_dataWriter == nullptr)
{
m_dataWriter = dds::pub::DataWriter<REQUEST>(m_requesterParams.publisher(),
ReplyRequestBase<REQUEST, REPLY>::m_requestTopic,
m_requesterParams.datawriter_qos());
bNewDataWriter = true;
}
else
{
}
create_contentfilter(bNewDataWriter);
if (m_dataReader == nullptr)
{
m_dataReader = dds::sub::DataReader<REPLY>(m_requesterParams.subscriber(),
m_contentFilteredTopic,
m_requesterParams.datareader_qos());
}
m_curInstanceHandle = m_dataWriter.register_instance(request.data());
request.info().replace_automatic_values(true);
request.info().identity(rti::core::SampleIdentity::automatic());
m_dataWriter->write(request.data(), request.info());
const rti::core::SampleIdentity currentSampleIdentity = request.info().identity();
DEBUG_LOG(m_logger) << "send_request for topic "
<< ReplyRequestBase<REQUEST, REPLY>::m_requestTopic.name()
<< ", m_currentSampleIdentity: "
<< DebugInfo::toReadable(currentSampleIdentity);
}
bool receive_reply(connext::Sample<REPLY> &reply,
const dds::core::Duration &duration)
{
auto condition(dds::sub::cond::ReadCondition(m_dataReader, dds::sub::status::SampleState::not_read()));
dds::core::cond::WaitSet waitset;
waitset.attach_condition(condition);
bool bRet = false;
while (true)
{
if (m_dataReader->take(reply.data(), reply.info()) && reply.info().valid())
{
DEBUG_LOG(m_logger) << "receive_reply for topic "
<< ReplyRequestBase<REQUEST, REPLY>::m_requestTopic.name()
<< ", SampleIdentity - original publication virtual: "
<< DebugInfo::toReadable(reply.info()->original_publication_virtual_sample_identity());
DEBUG_LOG(m_logger) << "receive_reply for topic "
<< ReplyRequestBase<REQUEST, REPLY>::m_requestTopic.name()
<< ", SampleIdentity - related original: "
<< DebugInfo::toReadable(reply.info()->related_original_publication_virtual_sample_identity());
bRet = true;
break;
}
dds::core::cond::WaitSet::ConditionSeq activeConditionsSeq = waitset.wait(duration);
if (activeConditionsSeq.empty())
{
ERROR_LOG(m_logger) << "Bailing out!";
break;
}
}
static const rti::core::SampleFlag irs(rti::core::SampleFlag::intermediate_reply_sequence());
if ((reply.info()->flag() & irs) == 0)
{
if (!m_curInstanceHandle.is_nil())
{
DEBUG_LOG(m_logger) << "Keyed topic - disposing instance: "
<< ReplyRequestBase<REQUEST, REPLY>::m_requestTopic.name();
m_dataWriter.dispose_instance(m_curInstanceHandle);
}
}
return bRet;
}
dds::pub::DataWriter<REQUEST> get_request_datawriter() const { return m_dataWriter; }
dds::sub::DataReader<REPLY> get_request_datareader() const { return m_dataReader; }
private:
connext::RequesterParams m_requesterParams;
dds::pub::DataWriter<REQUEST> m_dataWriter;
dds::sub::DataReader<REPLY> m_dataReader;
dds::topic::ContentFilteredTopic<REPLY> m_contentFilteredTopic;
dds::core::InstanceHandle m_curInstanceHandle;
NGL::INGLLogger &m_logger;
};
template<typename REQUEST, typename REPLY>
class Replier final : public ReplyRequestBase<REQUEST, REPLY>
{
public:
explicit Replier(const connext::ReplierParams<REQUEST, REPLY> &replierParams)
: ReplyRequestBase<REQUEST, REPLY>(replierParams)
, m_replierParams(replierParams)
, m_dataWriter(dds::pub::DataWriter<REPLY>(replierParams.publisher(),
ReplyRequestBase<REQUEST, REPLY>::m_replyTopic,
replierParams.datawriter_qos()))
, m_dataReader(dds::sub::DataReader<REQUEST>(replierParams.subscriber(),
ReplyRequestBase<REQUEST, REPLY>::m_requestTopic,
replierParams.datareader_qos()))
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("connext.replier"))
{}
void send_reply(connext::WriteSample<REPLY> &reply,
const rti::core::SampleIdentity &identity,
bool bLastSample = true)
{
connext::WriteSampleRef<REPLY> replyRef;
replyRef.set_data(&reply.data());
replyRef.set_info(&reply.info());
return send_reply(replyRef, identity, bLastSample);
}
void send_reply(connext::WriteSampleRef<REPLY> &reply,
const rti::core::SampleIdentity &identity,
bool bLastSample = true)
{
DEBUG_LOG(m_logger) << "send_reply for topic "
<< ReplyRequestBase<REQUEST, REPLY>::m_requestTopic.name()
<< ", sampleIdentity: " << DebugInfo::toReadable(identity);
rti::core::SampleFlag sf = reply.info().flag();
if (bLastSample)
sf &= ~rti::core::SampleFlag::intermediate_reply_sequence();
else
sf |= rti::core::SampleFlag::intermediate_reply_sequence();
reply.info().flag(sf);
reply.info().related_sample_identity(identity);
DEBUG_LOG(m_logger) << "send_reply for topic "
<< ReplyRequestBase<REQUEST, REPLY>::m_requestTopic.name()
<< ", related sampleIdentity: " << DebugInfo::toReadable(reply.info().related_sample_identity());
m_dataWriter->write(reply.data(), reply.info());
}
bool receive_request(connext::Sample<REQUEST> &request)
{
if (!m_dataReader->take(request.data(), request.info()))
return false;
if (request.info().valid())
{
DEBUG_LOG(m_logger) << "receive_request for topic "
<< ReplyRequestBase<REQUEST, REPLY>::m_requestTopic.name()
<< ", sampleIdentity publication_virtual_sample_identity: "
<< DebugInfo::toReadable(request.info()->publication_virtual_sample_identity())
<< ", original_publication_virtual_sample_identity: "
<< DebugInfo::toReadable(request.info()->original_publication_virtual_sample_identity())
<< ", related_original_publication_virtual_sample_identity: "
<< DebugInfo::toReadable(request.info()->related_original_publication_virtual_sample_identity())
<< " with timestamp_ " << request.info().source_timestamp().to_millisecs();
request.set_identity(request.info()->original_publication_virtual_sample_identity());
}
return request.info().valid();
}
dds::pub::DataWriter<REPLY> get_request_datawriter() const { return m_dataWriter; }
dds::sub::DataReader<REQUEST> get_request_datareader() const { return m_dataReader; }
private:
connext::ReplierParams<REQUEST, REPLY> m_replierParams;
dds::pub::DataWriter<REPLY> m_dataWriter;
dds::sub::DataReader<REQUEST> m_dataReader;
NGL::INGLLogger &m_logger;
};
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <rti/core/SampleIdentity.hpp>
#include <QVector>
namespace FTS
{
template <typename TOPICTYPE_REQUEST, typename TOPICTYPE_REPLY>
class GenericFts
{
public:
GenericFts() = default;
virtual ~GenericFts() = default;
virtual bool sendReply(const rti::core::SampleIdentity &requestIdentity, const TOPICTYPE_REPLY &replyData) = 0;
virtual bool sendReply(const rti::core::SampleIdentity &requestIdentity, const QVector<TOPICTYPE_REPLY> &replyData) = 0;
};
}
+287
View File
@@ -0,0 +1,287 @@
#pragma once
#include <DDS/Core/core.hpp>
#include "generic_fts_internal.h"
#include "connext.h"
#include <DDS/Generic/wait_set_handler.h>
#include <DDS/Generic/generic_base.h>
#include <DDS/Generic/generic_logger.h>
#include <queue>
#include <type_traits>
#include <typeinfo>
#include <sstream>
#ifndef tr_lts
#define tr_lts(ctx, t) (t)
#endif
namespace FTS
{
template <class C>
class HasStatusFunction
{
template <class T>
static std::true_type testSignature(void (T::*)(const Core::FtReplyState&));
template <class T>
static decltype(testSignature(&T::replyState)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
template<typename GENERIC_FTS>
struct TopicNames;
template<typename TOPICTYPE_REQUEST, typename TOPICTYPE_REPLY, typename DUMMY>
class GenericFtsImpl final : public GenericFts<TOPICTYPE_REQUEST,TOPICTYPE_REPLY>
, public GenericFtsInternal
{
public:
using RequestTopic = TOPICTYPE_REQUEST;
using ReplyTopic = TOPICTYPE_REPLY;
using TopicNameHelper = TopicNames<GenericFtsImpl<RequestTopic, ReplyTopic, DUMMY>>;
private:
using SubscriberCallback = std::function<void(const rti::core::SampleIdentity&, const RequestTopic&)>;
template <typename T = ReplyTopic, typename std::enable_if<HasStatusFunction<T>::value, int>::type = 0>
void sendReplyState(Core::FtReplyState replyState, const rti::core::SampleIdentity &requestSampleIdentity)
{
connext::WriteSample<ReplyTopic> reply;
reply.data().replyState(replyState);
bool bLastTelegram = ((replyState != Core::FtReplyState_def::InProgress) &&
(replyState != Core::FtReplyState_def::NotStarted));
m_replier->send_reply(reply, requestSampleIdentity, bLastTelegram);
}
template <typename T = ReplyTopic, typename std::enable_if<!HasStatusFunction<T>::value, int>::type = 0>
void sendReplyState(Core::FtReplyState, const rti::core::SampleIdentity &)
{
}
template <typename T, typename std::enable_if<HasStatusFunction<T>::value, int>::type = 0>
void setReply(T &data, Core::FtReplyState status)
{
data.replyState(status);
}
template <typename T, typename std::enable_if<!HasStatusFunction<T>::value, int>::type = 0>
void setReply(T &, Core::FtReplyState)
{
}
public:
GenericFtsImpl(const dds::domain::DomainParticipant &domainParticipant,
const dds::pub::Publisher &ddsPublisher,
const dds::sub::Subscriber &ddsSubscriber,
DDSGeneric::WaitSetHandler *wsHandler)
: GenericFts<RequestTopic,ReplyTopic>()
, GenericFtsInternal()
, m_wsHandler(wsHandler)
, m_subscriberCallback(nullptr)
, m_logger(DDS::getLogger(TopicNameHelper::functionTopicName(), DDS::DataType::FUNCTION_TOPIC_SERVER))
{
Q_ASSERT(domainParticipant != nullptr);
Q_ASSERT(ddsPublisher != nullptr);
Q_ASSERT(ddsSubscriber != nullptr);
Q_ASSERT(wsHandler != nullptr);
qRegisterMetaType<RequestTopic> (TopicNameHelper::requestTopicTypeName());
connext::ReplierParams<RequestTopic,ReplyTopic> replierParams(domainParticipant);
replierParams.request_topic_name(TopicNameHelper::requestTopicName());
replierParams.reply_topic_name(TopicNameHelper::replyTopicName());
replierParams.publisher(ddsPublisher);
replierParams.subscriber(ddsSubscriber);
replierParams.datareader_qos(readerQos(m_logger));
replierParams.datawriter_qos(writerQos(m_logger));
m_replier = new connext::Replier<RequestTopic, ReplyTopic>(replierParams);
m_readCondition = new dds::sub::cond::ReadCondition(m_replier->get_request_datareader(), dds::sub::status::SampleState::not_read());
m_wsHandler->attachWaitSet(*m_readCondition, this);
}
~GenericFtsImpl()
{
m_wsHandler->detachWaitSet(*m_readCondition, this);
delete m_readCondition;
delete m_replier;
}
bool setSubscriberCallback(const SubscriberCallback &subscriberCallback)
{
if (m_subscriberCallback)
{
WARNING_LOG(m_logger) << "Can not set subscriber callback - already set ...";
return false;
}
m_subscriberCallback = subscriberCallback;
return true;
}
bool sendReply(const rti::core::SampleIdentity &requestSampleIdentity, const ReplyTopic &replyData) override final
{
connext::WriteSampleRef<ReplyTopic> reply;
rti::pub::WriteParams replyParams;
reply.set_data(const_cast<ReplyTopic*>(&replyData));
reply.set_info(&replyParams);
m_replier->send_reply(reply, requestSampleIdentity);
return true;
}
bool sendReply(const rti::core::SampleIdentity &requestIdentity, const QVector<ReplyTopic> &replyData) override final
{
DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - IN sendReply";
for(int i = 0; i < replyData.size(); ++i)
{
if (HasStatusFunction<ReplyTopic>::value)
{
ReplyTopic rt = replyData.at(i);
setReply(rt, i == replyData.size() - 1 ? Core::FtReplyState::Completed : Core::FtReplyState::InProgress);
if (!sendReply(requestIdentity, rt))
return false;
} else {
if (!sendReply(requestIdentity, replyData.at(i)))
return false;
}
}
return true;
}
protected:
void processRequest(const connext::Sample<RequestTopic> &request)
{
DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - processRequest: "
<< connext::DebugInfo::toReadable(request.identity());
qint64 age = qAbs(qint64(request.info().source_timestamp().to_secs()) - time(nullptr));
if (age > 10)
{
std::ostringstream ss;
ss << request.data();
std::string s = ss.str();
int slen = s.length();
static int max_slen = 1024;
if (slen > max_slen)
{
s.resize(max_slen);
s.append("...");
}
DEBUG_LOG(m_logger) << "received a request for topic "
<< TopicNameHelper::requestTopicName()
<< " but it was outdated - ignoring. "
<< " local time: " << (time(nullptr) - 10) << "[s], source timestamp: "
<< qint64(request.info().source_timestamp().to_secs())
<< "[s], -> age: " << age
<< "[s], data: " << s;
ReplyTopic rt;
rt.errorString(tr_lts("dds", "Topic %1 was outdated and therefore ignored. Does the client time match the server time?"));
rt.errorParams({TopicNameHelper::requestTopicName()});
sendReply(request.identity(), rt);
return;
}
sendReplyState(Core::FtReplyState::InProgress, request.identity());
m_subscriberCallback(request.identity(), request.data());
}
void processRequests() override
{
DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - processRequests";
while (true)
{
connext::Sample<RequestTopic> request;
{
std::lock_guard<std::mutex> l(m_requestMutex);
if (m_requests.empty())
{
DEBUG_LOG(m_logger) << "No more requests";
return;
}
DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - read one request for ";
request = m_requests.front();
m_requests.pop();
}
if (!request.info().valid())
{
WARNING_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - No valid data";
continue;
}
processRequest(request);
}
DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - processRequests finished";
}
bool enqueueRequests() override
{
bool bValidDataRead = false;
connext::Sample<RequestTopic> request;
while (m_replier->receive_request(request))
{
if (!request.info().valid())
continue;
bValidDataRead = true;
std::lock_guard<std::mutex> l(m_requestMutex);
m_requests.push(request);
}
return bValidDataRead;
}
private:
connext::Replier<RequestTopic, ReplyTopic> *m_replier;
dds::sub::cond::ReadCondition *m_readCondition;
DDSGeneric::WaitSetHandler *m_wsHandler;
std::queue<connext::Sample<RequestTopic>> m_requests;
std::mutex m_requestMutex;
SubscriberCallback m_subscriberCallback;
NGL::INGLLogger &m_logger;
};
}
+48
View File
@@ -0,0 +1,48 @@
#include "generic_fts_internal.h"
#include <DDS/QosProfile/DDS_DefaultQos.h>
#include <QMetaMethod>
#include <NGLLogger/INGLLogger.h>
#include <qglobal.h>
namespace FTS
{
void GenericFtsInternal::printDefaultQos(NGL::INGLLogger &logger)
{
if (logger.isDebugEnabled())
DDS::QosProfile::print(dds::core::QosProvider::Default(), logger);
}
dds::sub::qos::DataReaderQos GenericFtsInternal::readerQos(NGL::INGLLogger &logger)
{
dds::sub::qos::DataReaderQos qos = DDS::QosProfile::dataReaderDefault(DDS::QosProfile::TopicType::FunctionTopic);
if (logger.isDebugEnabled())
DDS::QosProfile::print(qos, logger);
return qos;
}
dds::pub::qos::DataWriterQos GenericFtsInternal::writerQos(NGL::INGLLogger &logger)
{
dds::pub::qos::DataWriterQos qos = DDS::QosProfile::dataWriterDefault(DDS::QosProfile::TopicType::FunctionTopic);
if (logger.isDebugEnabled())
DDS::QosProfile::print(qos, logger);
return qos;
}
void GenericFtsInternal::dataAvailable()
{
if (!enqueueRequests())
return;
#if QT_VERSION < QT_VERSION_CHECK(5,10,0)
if (!QMetaObject::invokeMethod(this, "processRequests", Qt::QueuedConnection))
abort();
#else
QMetaObject::invokeMethod(this, &GenericFtsInternal::processRequests, Qt::QueuedConnection);
#endif
}
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "generic_fts.h"
#include <DDS/FTS/ddsfts_export.h>
#include <DDS/Generic/wait_set_handler.h>
#include <dds/sub/qos/DataReaderQos.hpp>
#include <dds/pub/qos/DataWriterQos.hpp>
#include <QObject>
namespace FTS
{
class DDSFTS_EXPORT GenericFtsInternal : public QObject
, protected DDSGeneric::WaitSetHandler::CallbackInterface
{
Q_OBJECT
protected:
GenericFtsInternal() = default;
static void printDefaultQos(NGL::INGLLogger &logger);
static dds::sub::qos::DataReaderQos readerQos(NGL::INGLLogger &logger);
static dds::pub::qos::DataWriterQos writerQos(NGL::INGLLogger &logger);
protected:
void dataAvailable() override;
virtual bool enqueueRequests() = 0;
protected Q_SLOTS:
virtual void processRequests() = 0;
};
}
+43
View File
@@ -0,0 +1,43 @@
#
# $Id$
# $HeadURL$
# $Author$
#
FIND_PACKAGE(Threads REQUIRED)
SET(ddsgen_SRCS
dynamic_data_reader.h
generic_base.cpp
generic_base.h
generic_data_reader.h
generic_data_writer.h
generic_logger.cpp
generic_logger.h
subscriber_listener.cpp
subscriber_listener.h
topic_description.h
wait_set_handler.cpp
wait_set_handler.h
)
NGL_ADD_LIBRARY2(AAR SV HLA WS ddsgen SHARED ${ddsgen_SRCS} ${ddsgen_HDRS})
NGL_TARGET_INCLUDE_DIRECTORIES(ddsgen
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../../
${CMAKE_CURRENT_BINARY_DIR}/../../
)
NGL_TARGET_LINK_LIBRARIES(ddsgen
PUBLIC
${CMAKE_THREAD_LIBS_INIT}
Qt5::Core
RTI::nddscpp2
NGLLogger
LtsUtilsCoreHeaderOnly
)
# topic_type_plugin
ADD_LIBRARY(topic_type_plugin INTERFACE)
TARGET_SOURCES(topic_type_plugin INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/topic_type_plugin.h)
TARGET_INCLUDE_DIRECTORIES(topic_type_plugin INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../../)
TARGET_LINK_LIBRARIES(topic_type_plugin INTERFACE ddsgen Qt5::Core)
+354
View File
@@ -0,0 +1,354 @@
#pragma once
#include <dds/core/xtypes/StructType.hpp>
#include <dds/sub/DataReader.hpp>
#include <dds/sub/LoanedSamples.hpp>
#include <dds/sub/SampleInfo.hpp>
#include <dds/topic/find.hpp>
#include <DDS/Generic/generic_base.h>
#include <DDS/Generic/generic_logger.h>
#include <LtsUtilsCore/data_modification_enum.h>
#include <functional>
namespace DDSGeneric
{
using StructType = dds::core::xtypes::StructType;
using DynamicData = dds::core::xtypes::DynamicData;
class DynamicDataReader final
{
public:
using TopicType = DynamicData;
using DataReader = dds::sub::DataReader<DynamicData>;
using DataTopic = dds::topic::Topic<TopicType>;
using SubscriberCallback = std::function<void(const DynamicData &, const std::string &, DataModification)>;
using SubscriberCallbackWithSampleInfo = std::function<void(const DynamicData &, const std::string &, const dds::sub::SampleInfo &)>;
DynamicDataReader()
: m_dataReader(nullptr)
, m_structType(nullptr)
, m_ddsTopic(nullptr)
, m_bHaveSubscriberCallback(false)
, m_logger(DDS::getLogger("DynamicDataReader", DDS::DataType::DYNAMIC_DATA_READER))
{}
~DynamicDataReader()
{
m_ddsTopic = nullptr;
m_dataReader = nullptr;
delete m_structType;
}
const DataReader &getDataReader() const { return m_dataReader; }
const StructType &getStructType() const { return *m_structType; }
bool create(const dds::domain::DomainParticipant &domainParticipant,
const dds::sub::Subscriber &ddsSubscriber,
const std::string &specialTopicName,
const StructType &structType)
{
DEBUG_LOG(m_logger) << "IN DynamicDataReader::create ("
<< m_structType->name()
<< ")";
if (domainParticipant == nullptr)
{
ERROR_LOG(m_logger) << "DomainParticipant does not exist ";
return false;
}
if (m_ddsTopic != nullptr)
{
ERROR_LOG(m_logger) << "Type '"
<< m_structType->name()
<< "' already created";
return false;
}
if (structType.has_parent())
m_structType = new dds::core::xtypes::StructType(structType.name(), structType.parent(), structType.members());
else
m_structType = new dds::core::xtypes::StructType(structType.name(), structType.members());
try
{
const std::string topicName = specialTopicName.empty() ?
DDS::Helper::getShortTopicName(m_structType->name()) :
specialTopicName;
DEBUG_LOG(m_logger) << "Creating topic " << topicName << " with type: "
<< m_structType->name();
m_ddsTopic = dds::topic::find<dds::topic::Topic<DynamicData>>(domainParticipant, topicName);
if (m_ddsTopic == nullptr)
{
rti::core::xtypes::DynamicDataTypeSerializationProperty serialization_property;
serialization_property.min_size_serialized(0);
dds::domain::DomainParticipant dp = domainParticipant;
rti::domain::register_type(dp, structType.name(), structType, serialization_property);
m_ddsTopic = dds::topic::Topic<DynamicData>(domainParticipant, topicName, structType);
}
DEBUG_LOG(m_logger) << "m_ddsTopic created: " << m_ddsTopic.name();
if (m_logger.isDebugEnabled())
{
dds::sub::qos::SubscriberQos subscriberQoS = ddsSubscriber.qos();
dds::core::policy::Partition partition = subscriberQoS.policy<dds::core::policy::Partition>();
dds::core::StringSeq parititionNames = partition.name();
DEBUG_LOG(m_logger) << "Subscribing partitions: " << parititionNames.size();
for (const std::string &name : parititionNames)
DEBUG_LOG(m_logger) << "SubscriberQoS: partition name = " << name;
}
dds::sub::qos::DataReaderQos dataReaderQos = ddsSubscriber.default_datareader_qos();
QString typeName = QString::fromStdString(m_structType->name());
if (typeName.endsWith(QLatin1String("Request")) || typeName.endsWith(QLatin1String("Reply")))
{
dds::core::policy::Durability readerDurability;
dataReaderQos >> readerDurability;
readerDurability.kind(dds::core::policy::DurabilityKind_def::VOLATILE);
dataReaderQos << readerDurability;
}
m_dataReader = DataReader(ddsSubscriber,
m_ddsTopic,
dataReaderQos,
nullptr ,
dds::core::status::StatusMask::all());
if (m_dataReader == nullptr)
{
ERROR_LOG(m_logger) << "Creating dataReader for '" << topicName << "' with type: '"
<< m_structType->name()
<< "' failed";
return false;
}
}
catch (const dds::core::Exception &ex)
{
ERROR_LOG(m_logger) << ex.what();
return false;
}
DEBUG_LOG(m_logger) << "dataReader created ("
<< m_structType->name()
<< ")";
return true;
}
bool hasSamples() const
{
qint64 sampleSize = m_dataReader->datareader_cache_status().sample_count();
DEBUG_LOG(m_logger) << "sampleSize = " << sampleSize;
return sampleSize > 0;
}
dds::sub::LoanedSamples<DynamicData> readAlive(bool &bSeenNewSamples)
{
dds::sub::LoanedSamples<DynamicData> samples =
m_dataReader.select().state(dds::sub::status::InstanceState::alive()).read();
TRACE_LOG(m_logger) << "readAlive for " << DDS::topic_type_name<TopicType>::value()
<< ", count: " << samples.length()
<< ", this: " << this << ", instance handle " << m_dataReader.instance_handle();
bSeenNewSamples = checkNewSamples(samples);
return samples;
}
dds::sub::LoanedSamples<DynamicData> takeAlive(bool &bSeenNewSamples)
{
dds::sub::LoanedSamples<DynamicData> samples =
m_dataReader.select().state(dds::sub::status::InstanceState::alive()).take();
TRACE_LOG(m_logger) << "takeAlive for " << DDS::topic_type_name<TopicType>::value()
<< ", count: " << samples.length()
<< ", this: " << this << ", instance handle " << m_dataReader.instance_handle();
bSeenNewSamples = checkNewSamples(samples);
return samples;
}
dds::sub::LoanedSamples<DynamicData> takeDisposed()
{
dds::sub::LoanedSamples<DynamicData> samples =
m_dataReader.select().state(dds::sub::status::InstanceState::not_alive_disposed()).take();
TRACE_LOG(m_logger) << "takeDisposed for " << DDS::topic_type_name<TopicType>::value()
<< ", count: " << samples.length()
<< ", this: " << this << ", instance handle " << m_dataReader.instance_handle();
return samples;
}
template<typename C>
bool read(C callbackFunc)
{
DEBUG_LOG(m_logger) << "IN read ("
<< DDS::topic_type_name<TopicType>::value()
<< "), this: " << this << ", instance handle " << m_dataReader.instance_handle();
if (m_dataReader == nullptr || m_ddsTopic == nullptr)
{
ERROR_LOG(m_logger) << "No dataReader initialised ";
return false;
}
if (!isValid(callbackFunc))
{
ERROR_LOG(m_logger) << "No subscriber set for "
<< DDS::topic_type_name<TopicType>::value();
return false;
}
dds::sub::LoanedSamples<DynamicData> samples = m_dataReader.select().state(dds::sub::status::DataState::new_data()).read();
DEBUG_LOG(m_logger) << "got " << samples.length() + m_newSamplesDuringRead.size() << " samples from reader for "
<< DDS::topic_type_name<TopicType>::value();
for (const auto &sample : samples)
{
if (sample.info().valid())
readData(sample, m_dataReader->topic_name(), callbackFunc);
}
auto copy = m_newSamplesDuringRead;
m_newSamplesDuringRead.clear();
for (auto &sample : copy)
{
readData(rti::sub::LoanedSample<DynamicData>(sample.sample, &sample.info), m_dataReader->topic_name(), callbackFunc);
delete sample.sample;
}
return checkDisposedInstances(callbackFunc);
}
void setHaveSubscriberCallback(bool bHave)
{
m_bHaveSubscriberCallback = bHave;
}
std::string topicName() const
{
return m_ddsTopic.name();
}
void recreateReader()
{
if (m_dataReader == nullptr)
return;
m_dataReader = DataReader(m_dataReader.subscriber(), m_ddsTopic);
}
private:
#if RTI_DDS_VERSION_MAJOR < 6
inline bool checkNewSamples(dds::sub::LoanedSamples<DynamicData> &samples)
#else
inline bool checkNewSamples(const dds::sub::LoanedSamples<DynamicData> &samples)
#endif
{
if (!m_bHaveSubscriberCallback)
return false;
bool bSeenNewSamples = false;
for (const auto &sample : samples)
{
const dds::sub::status::DataState ds = sample.info()->state();
if (ds.view_state() == dds::sub::status::ViewState::new_view() ||
ds.sample_state() == dds::sub::status::SampleState::not_read())
{
SampleStruct ss;
ss.sample = new DynamicData(sample.data());
ss.info = sample.info();
m_newSamplesDuringRead.emplace_back(ss);
bSeenNewSamples = true;
}
}
return bSeenNewSamples;
}
template<typename C>
bool checkDisposedInstances(C callbackFunc)
{
const dds::sub::status::DataState ds(dds::sub::status::SampleState::any(),
dds::sub::status::ViewState::any(),
dds::sub::status::InstanceState::not_alive_mask());
dds::sub::LoanedSamples<DynamicData> samples = m_dataReader.select().state(ds).take();
if (samples.length() == 0)
return false;
DEBUG_LOG(m_logger) << "got " << samples.length() << " disposed instances from reader for "
<< m_ddsTopic.name();
for (const auto &sample : samples)
{
if (sample.info().valid())
readData(sample, m_dataReader->topic_name(), callbackFunc);
}
return true;
}
static void readData(const rti::sub::LoanedSample<DynamicData> &sample, const std::string &topicName, const SubscriberCallback &callbackFunc)
{
DataModification modType = DataModification::eModified;
const dds::sub::status::DataState ds = sample.info()->state();
if (ds.view_state() == dds::sub::status::ViewState::new_view())
modType = DataModification::eAdded;
if (ds.instance_state() != dds::sub::status::InstanceState::alive())
modType = DataModification::eRemoved;
callbackFunc(sample.data(), topicName, modType);
}
static void readData(const rti::sub::LoanedSample<DynamicData> &sample, const std::string &topicName, const SubscriberCallbackWithSampleInfo &callbackFunc)
{
callbackFunc(sample.data(), topicName, sample.info());
}
static bool isValid(const SubscriberCallback &callbackFunc)
{
return callbackFunc != nullptr;
}
static bool isValid(const SubscriberCallbackWithSampleInfo &callbackFunc)
{
return callbackFunc != nullptr;
}
private:
DataReader m_dataReader;
StructType *m_structType;
DataTopic m_ddsTopic;
struct SampleStruct
{
DynamicData *sample = nullptr;
dds::sub::SampleInfo info;
};
std::vector<SampleStruct> m_newSamplesDuringRead;
bool m_bHaveSubscriberCallback;
NGL::INGLLogger &m_logger;
};
}
+93
View File
@@ -0,0 +1,93 @@
#include "generic_base.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QStringList>
#include <QMetaMethod>
namespace DDS
{
namespace Helper
{
static inline bool endsWith(std::string const &str, std::string const &end)
{
if (end.size() > str.size())
return false;
return std::equal(end.rbegin(), end.rend(), str.rbegin());
}
static inline std::string getSimpleTopicName(const std::string &fqName)
{
const size_t idx = fqName.rfind("::");
if (idx == std::string::npos)
return fqName;
return fqName.substr(idx + 2);
}
std::string getFunctionTopicName(const std::string &fqName)
{
const std::string ftName = getSimpleTopicName(fqName);
static const std::string request("Request");
static const std::string reply("Reply");
if (endsWith(ftName, request))
return ftName.substr(0, ftName.size() - request.size());
if (endsWith(ftName, reply))
return ftName.substr(0, ftName.size() - reply.size());
return ftName;
}
std::string getShortTopicName(const std::string &fqName)
{
return std::string("LTS_") + getSimpleTopicName(fqName);
}
QMetaMethod getMethod(NGL::INGLLogger &logger,
QObject *objectToInform,
const char *slotToExecute,
const char *signalSignature,
bool bWarnOnEmptySlot)
{
QMetaMethod ret;
if(!slotToExecute || !objectToInform)
{
if(bWarnOnEmptySlot)
WARNING_LOG(logger) << "invalid arguments to getMethod()";
return ret;
}
int slotCode = slotToExecute[0] - '0';
if(slotCode != QSLOT_CODE && slotCode != QSIGNAL_CODE)
{
ERROR_LOG(logger) << "Use the SLOT or SIGNAL macro for slot name!";
return ret;
}
slotToExecute++;
const QMetaObject *mo = objectToInform->metaObject();
const QByteArray slot = QMetaObject::normalizedSignature(slotToExecute);
const int indexOfSlot = mo->indexOfSlot(slot.data());
if(indexOfSlot < 0)
{
WARNING_LOG(logger) << "No such slot " << mo->className() << "::" << slot;
return ret;
}
const QByteArray signal = QMetaObject::normalizedSignature(signalSignature);
if(!QMetaObject::checkConnectArgs(signal.data(), slot.data()))
{
WARNING_LOG(logger) << "Incompatible slot arguments " << mo->className() << "::"
<< slot << ", must be: " << signal;
return ret;
}
return mo->method(indexOfSlot);
}
}
}
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include <dds/core/xtypes/DynamicData.hpp>
#include <dds/core/xtypes/DynamicType.hpp>
#include <dds/topic/Topic.hpp>
#include <string>
#include <DDS/Generic/ddsgen_export.h>
class QObject;
class QMetaMethod;
namespace NGL {
class INGLLogger;
}
namespace DDS
{
template<typename T>
struct topic_type_name {
static std::string value() {
return dds::topic::topic_type_name<T>::value();
}
};
template<>
struct topic_type_name<dds::core::xtypes::DynamicData> {
static std::string value() {
return "dds::topic::DynamicData::value()";
}
};
template<>
struct topic_type_name<rti::core::xtypes::DynamicTypeImpl> {
static std::string value() {
return "dds::topic::DynamicTypeImpl::value()";
}
};
namespace Helper
{
DDSGEN_EXPORT std::string getFunctionTopicName(const std::string &fqName);
DDSGEN_EXPORT std::string getShortTopicName(const std::string &fqName);
DDSGEN_EXPORT QMetaMethod getMethod(NGL::INGLLogger &logger,
QObject *objectToInform,
const char *slotToExecute,
const char *signalSignature,
bool bWarnOnEmptySlot = true);
}
}
+382
View File
@@ -0,0 +1,382 @@
#pragma once
#include <dds/sub/DataReader.hpp>
#include <dds/topic/find.hpp>
#include <dds/topic/Topic.hpp>
#include <DDS/Generic/generic_base.h>
#include <DDS/Generic/generic_logger.h>
#include <LtsUtilsCore/data_modification_enum.h>
#include <functional>
namespace DDSGeneric
{
template <typename TOPICTYPE>
class GenericDataReader
{
public:
using TopicType = TOPICTYPE;
using TopicTypeRead = TOPICTYPE;
using DataReader = dds::sub::DataReader<TopicTypeRead>;
using DataTopic = dds::topic::Topic<TopicTypeRead>;
using DataReaderQos = dds::sub::qos::DataReaderQos;
using SubscriberCallback = std::function<void(const TopicTypeRead &, DataModification)>;
using SubscriberCallbackWithSampleInfo = std::function<void(const TopicTypeRead &, const dds::sub::SampleInfo &)>;
GenericDataReader()
: m_dataReader(nullptr)
, m_ddsTopic(nullptr)
, m_bHaveSubscriberCallback(false)
, m_logger(DDS::getLogger(DDS::topic_type_name<TopicType>::value(), DDS::DataType::DATA_READER))
{}
virtual ~GenericDataReader()
{
m_ddsTopic = nullptr;
m_dataReader = nullptr;
}
DataReader getDataReader() const { return m_dataReader; }
bool create(const dds::domain::DomainParticipant &domainParticipant,
const dds::sub::Subscriber &ddsSubscriber,
const std::string &topicName = DDS::Helper::getShortTopicName(DDS::topic_type_name<TopicType>::value()),
bool bCreateDataReader = true)
{
DEBUG_LOG(m_logger) << "IN GenericDataReader::create ("
<< DDS::topic_type_name<TopicType>::value()
<< "), this: " << this;
if (domainParticipant == nullptr)
{
ERROR_LOG(m_logger) << "DomainParticipant does not exist ";
return false;
}
if (m_ddsTopic != nullptr)
{
ERROR_LOG(m_logger) << "Type '"
<< DDS::topic_type_name<TopicType>::value()
<< "' already created";
return false;
}
try
{
DEBUG_LOG(m_logger) << "Creating read topic " << topicName << " with type: "
<< DDS::topic_type_name<TopicType>::value();
m_ddsTopic = dds::topic::find<DataTopic>(domainParticipant, topicName);
if (m_ddsTopic == nullptr)
m_ddsTopic = DataTopic(domainParticipant, topicName);
if (m_logger.isDebugEnabled())
{
dds::sub::qos::SubscriberQos subscriberQoS = ddsSubscriber.qos();
dds::core::policy::Partition partition = subscriberQoS.policy<dds::core::policy::Partition>();
dds::core::StringSeq parititionNames = partition.name();
DEBUG_LOG(m_logger) << "Subscribing partitions: " << parititionNames.size();
for (const std::string &name : parititionNames)
DEBUG_LOG(m_logger) << " partition name = " << name;
}
if (bCreateDataReader)
{
dds::sub::qos::DataReaderQos drQos = domainParticipant->default_datareader_qos();
m_dataReader = DataReader(ddsSubscriber, m_ddsTopic, drQos);
if (m_dataReader == nullptr)
{
ERROR_LOG(m_logger) << "Creating dataReader for '" << topicName << "' with type: '"
<< DDS::topic_type_name<TopicType>::value()
<< "' failed";
return false;
}
}
}
catch (const dds::core::Exception &ex)
{
ERROR_LOG(m_logger) << ex.what();
return false;
}
DEBUG_LOG(m_logger) << "dataReader created ("
<< DDS::topic_type_name<TopicType>::value()
<< ")";
return true;
}
bool hasSamples() const
{
qint64 sampleSize = m_dataReader->datareader_cache_status().sample_count();
DEBUG_LOG(m_logger) << "sampleSize = " << sampleSize;
return sampleSize > 0;
}
dds::sub::LoanedSamples<TopicTypeRead> readAlive(bool &bSeenNewSamples)
{
dds::sub::LoanedSamples<TopicTypeRead> samples =
m_dataReader.select().state(dds::sub::status::InstanceState::alive()).read();
TRACE_LOG(m_logger) << "readAlive for " << DDS::topic_type_name<TopicType>::value()
<< ", count: " << samples.length()
<< ", this: " << this << ", instance handle " << m_dataReader.instance_handle();
bSeenNewSamples = checkNewSamples(samples);
return samples;
}
dds::sub::LoanedSamples<TopicTypeRead> takeAlive(bool &bSeenNewSamples)
{
dds::sub::LoanedSamples<TopicTypeRead> samples =
m_dataReader.select().state(dds::sub::status::InstanceState::alive()).take();
TRACE_LOG(m_logger) << "takeAlive for " << DDS::topic_type_name<TopicType>::value()
<< ", count: " << samples.length()
<< ", this: " << this << ", instance handle " << m_dataReader.instance_handle();
bSeenNewSamples = checkNewSamples(samples);
return samples;
}
dds::sub::LoanedSamples<TopicTypeRead> takeDisposed()
{
dds::sub::LoanedSamples<TopicTypeRead> samples =
m_dataReader.select().state(dds::sub::status::InstanceState::not_alive_disposed()).take();
TRACE_LOG(m_logger) << "takeDisposed for " << DDS::topic_type_name<TopicType>::value()
<< ", count: " << samples.length()
<< ", this: " << this << ", instance handle " << m_dataReader.instance_handle();
return samples;
}
template<typename C>
bool read(C callbackFunc)
{
DEBUG_LOG(m_logger) << "IN read ("
<< DDS::topic_type_name<TopicType>::value()
<< "), this: " << this << ", instance handle " << m_dataReader.instance_handle();
if (m_dataReader == nullptr)
{
ERROR_LOG(m_logger) << "No dataReader initialised ";
return false;
}
if (!isValid(callbackFunc))
{
ERROR_LOG(m_logger) << "No subscriber set for "
<< DDS::topic_type_name<TopicType>::value();
return false;
}
std::vector<SampleStruct> samples;
{
dds::sub::LoanedSamples<TopicTypeRead> loanedSamples = m_dataReader.select().state(dds::sub::status::DataState::new_data()).read();
samples.reserve(loanedSamples.length());
for (const auto &sample : loanedSamples)
{
if (!sample.info().valid())
continue;
samples.emplace_back(sample.data(), sample.info());
}
}
DEBUG_LOG(m_logger) << "got " << samples.size() + m_newSamplesDuringRead.size() << " samples from reader for "
<< DDS::topic_type_name<TopicType>::value();
for (const SampleStruct &ss : samples)
readData(ss.sample, ss.info, callbackFunc);
if (!m_newSamplesDuringRead.empty())
{
std::vector<SampleStruct> copy;
std::swap(copy, m_newSamplesDuringRead);
for (const SampleStruct &ss : copy)
readData(ss.sample, ss.info, callbackFunc);
}
return checkDisposedInstances(callbackFunc);
}
void setHaveSubscriberCallback(bool bHave)
{
m_bHaveSubscriberCallback = bHave;
}
std::string topicName() const
{
return m_ddsTopic.name();
}
DataReader dataReader() const { return m_dataReader; }
virtual void recreateReader()
{
if (m_dataReader == nullptr)
return;
m_dataReader = DataReader(m_dataReader.subscriber(), m_ddsTopic);
}
private:
#if RTI_DDS_VERSION_MAJOR < 6
inline bool checkNewSamples(dds::sub::LoanedSamples<TopicTypeRead> &samples)
#else
inline bool checkNewSamples(const dds::sub::LoanedSamples<TopicTypeRead> &samples)
#endif
{
if (!m_bHaveSubscriberCallback)
return false;
bool bSeenNewSamples = false;
for (const auto &sample : samples)
{
const dds::sub::status::DataState ds = sample.info()->state();
if (ds.view_state() == dds::sub::status::ViewState::new_view() ||
ds.sample_state() == dds::sub::status::SampleState::not_read())
{
m_newSamplesDuringRead.emplace_back(sample.data(), sample.info());
bSeenNewSamples = true;
}
}
return bSeenNewSamples;
}
template<typename C>
bool checkDisposedInstances(C callbackFunc)
{
const dds::sub::status::DataState ds(dds::sub::status::SampleState::any(),
dds::sub::status::ViewState::any(),
dds::sub::status::InstanceState::not_alive_mask());
dds::sub::LoanedSamples<TopicTypeRead> samples = m_dataReader.select().state(ds).take();
if (samples.length() == 0)
return false;
DEBUG_LOG(m_logger) << "got " << samples.length() << " disposed instances from reader for "
<< DDS::topic_type_name<TopicType>::value();
for (const auto &sample : samples)
{
if (sample.info().valid())
readData(sample.data(), sample.info(), callbackFunc);
}
return true;
}
static void readData(const TopicType &sample, const dds::sub::SampleInfo &info, const SubscriberCallback &callbackFunc)
{
DataModification modType = DataModification::eModified;
const dds::sub::status::DataState ds = info.state();
if (ds.view_state() == dds::sub::status::ViewState::new_view())
modType = DataModification::eAdded;
if (ds.instance_state() != dds::sub::status::InstanceState::alive())
modType = DataModification::eRemoved;
callbackFunc(sample, modType);
}
static void readData(const TopicType &sample, const dds::sub::SampleInfo &info, const SubscriberCallbackWithSampleInfo &callbackFunc)
{
callbackFunc(sample, info);
}
static bool isValid(const SubscriberCallback &callbackFunc)
{
return callbackFunc != nullptr;
}
static bool isValid(const SubscriberCallbackWithSampleInfo &callbackFunc)
{
return callbackFunc != nullptr;
}
protected:
DataReader m_dataReader;
DataReaderQos m_dataReaderQos;
DataTopic m_ddsTopic;
struct SampleStruct
{
TopicTypeRead sample;
dds::sub::SampleInfo info;
SampleStruct() = default;
SampleStruct(const TopicTypeRead &s, const dds::sub::SampleInfo &i)
: sample(s)
, info(i)
{}
};
std::vector<SampleStruct> m_newSamplesDuringRead;
bool m_bHaveSubscriberCallback;
NGL::INGLLogger &m_logger;
};
template<typename TOPICTYPE>
class GenericDataReaderContentFilter final
: public GenericDataReader<TOPICTYPE>
{
using BaseClass = GenericDataReader<TOPICTYPE>;
public:
using TopicType = TOPICTYPE;
using DataReader = dds::sub::DataReader<TopicType>;
using BaseClass::BaseClass;
bool create(const dds::domain::DomainParticipant &domainParticipant,
const dds::sub::Subscriber &ddsSubscriber,
const std::string &specialTopicName,
const dds::topic::Filter &filter)
{
if (!BaseClass::create(domainParticipant, ddsSubscriber, specialTopicName, false))
return false;
m_filter = filter;
try
{
qint64 ptr = reinterpret_cast<qint64>(this);
const QByteArray name = QByteArray(DDS::topic_type_name<TopicType>::value().c_str())
+ "- content filter 0x"
+ QByteArray::number(ptr, 16);
m_filteredTopic = dds::topic::ContentFilteredTopic<TopicType>(BaseClass::m_ddsTopic, name.data(), m_filter);
BaseClass::m_dataReader = DataReader(ddsSubscriber, m_filteredTopic);
}
catch (const dds::core::Exception &ex)
{
ERROR_LOG(BaseClass::m_logger) << ex.what();
return false;
}
DEBUG_LOG(BaseClass::m_logger) << "dataReader with filter created ("
<< DDS::topic_type_name<TopicType>::value()
<< ")";
return true;
}
dds::topic::Filter &filter() { return m_filter; }
const dds::topic::Filter &filter() const { return m_filter; }
virtual void recreateReader()
{
if (BaseClass::m_dataReader == nullptr)
return;
BaseClass::m_dataReader = DataReader(BaseClass::m_dataReader.subscriber(), m_filteredTopic);
}
private:
dds::topic::ContentFilteredTopic<TopicType> m_filteredTopic = dds::topic::ContentFilteredTopic<TopicType>(nullptr);
dds::topic::Filter m_filter = dds::topic::Filter(std::string());
};
}
+143
View File
@@ -0,0 +1,143 @@
#pragma once
#include <dds/pub/DataWriter.hpp>
#include <dds/sub/SampleInfo.hpp>
#include <dds/topic/find.hpp>
#include <dds/topic/Topic.hpp>
#include <DDS/Generic/generic_logger.h>
#include <DDS/Generic/generic_base.h>
namespace DDSGeneric
{
template <typename TOPICTYPE>
class GenericDataWriter final
{
public:
using TopicType = TOPICTYPE;
using DataWriter = dds::pub::DataWriter<TopicType>;
using DataTopic = dds::topic::Topic<TopicType>;
GenericDataWriter()
: m_dataWriter(nullptr)
, m_ddsTopic(nullptr)
, m_logger(DDS::getLogger(DDS::topic_type_name<TopicType>::value(), DDS::DataType::DATA_WRITER))
{}
bool create(const dds::domain::DomainParticipant &participant,
const dds::pub::Publisher &ddsPublisher,
const std::string &topicName = DDS::Helper::getShortTopicName(DDS::topic_type_name<TopicType>::value()))
{
DEBUG_LOG(m_logger) << "IN GenericDataWriter::create ("
<< DDS::topic_type_name<TopicType>::value()
<< ")";
if (participant == nullptr)
{
ERROR_LOG(m_logger) << "Participant not available, aborting. ";
return false;
}
if (m_ddsTopic != nullptr)
{
ERROR_LOG(m_logger) << "Type '"
<< DDS::topic_type_name<TopicType>::value()
<< "' already created";
return false;
}
try {
DEBUG_LOG(m_logger) << "Creating write topic " << topicName << " with type: "
<< DDS::topic_type_name<TopicType>::value();
m_ddsTopic = dds::topic::find<DataTopic>(participant, topicName);
if (m_ddsTopic == nullptr)
m_ddsTopic = DataTopic(participant, topicName);
m_dataWriter = DataWriter(ddsPublisher,
m_ddsTopic,
ddsPublisher.default_datawriter_qos());
if (m_dataWriter == nullptr)
{
ERROR_LOG(m_logger) << "Initialisation of "
<< DDS::topic_type_name<TopicType>::value()
<< " failed ";
return false;
}
}
catch ( const dds::core::Exception &ex )
{
ERROR_LOG(m_logger) << ex.what();
return false;
}
DEBUG_LOG(m_logger) << "dataWriter created ("
<< DDS::topic_type_name<TopicType>::value()
<< ")";
return true;
}
bool write(const TopicType &data,
const dds::core::optional<dds::sub::SampleInfo> &si = dds::core::optional<dds::sub::SampleInfo>())
{
DEBUG_LOG(m_logger) << "IN write ("
<< DDS::topic_type_name<TopicType>::value()
<< ") ";
if (m_dataWriter == nullptr) {
ERROR_LOG(m_logger) << "No dataWriter initialised ";
return false;
}
if (si.is_set())
{
rti::pub::WriteParams writeParams;
rti::core::SampleIdentity sampleIdentity(si.get()->source_guid(), si.get()->publication_sequence_number());
writeParams.related_sample_identity(sampleIdentity);
m_dataWriter->write(data, writeParams);
} else {
m_dataWriter.write(data);
}
return true;
}
bool remove(const TopicType &data, bool errorMsgIfNotExists = true)
{
DEBUG_LOG(m_logger) << "IN remove ("
<< DDS::topic_type_name<TopicType>::value()
<< ") ";
if (m_dataWriter == nullptr) {
ERROR_LOG(m_logger) << "No dataWriter initialised ";
return false;
}
const dds::core::InstanceHandle ih = m_dataWriter.lookup_instance(data);
if (ih.is_nil()) {
if (errorMsgIfNotExists)
ERROR_LOG(m_logger) << "Could not find instance for topic "
<< DDS::topic_type_name<TopicType>::value()
<< " for unregistering!";
return false;
}
m_dataWriter.dispose_instance(ih);
return true;
}
DataWriter dataWriter() const { return m_dataWriter; }
private:
DataWriter m_dataWriter;
DataTopic m_ddsTopic;
NGL::INGLLogger &m_logger;
};
}
+59
View File
@@ -0,0 +1,59 @@
#include "generic_logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace DDS
{
static const char *genericLoggerName(DataType ddsDataType)
{
switch (ddsDataType)
{
case DataType::DATA_TYPE_UNKNOWN:
return "Unknown.";
case DataType::DATA_READER:
return "DataReader.";
case DataType::DATA_WRITER:
return "DataWriter.";
case DataType::DYNAMIC_DATA_WRITER:
return "DynamicDataWriter.";
case DataType::DYNAMIC_DATA_READER:
return "DynamicDataReader.";
case DataType::DATA_TOPIC_SERVER:
return "DTS.";
case DataType::DATA_TOPIC_CLIENT:
return "DTC.";
case DataType::FUNCTION_TOPIC_SERVER:
return "FTS.";
case DataType::FUNCTION_TOPIC_CLIENT:
return "FTC.";
}
return "";
}
LOGGER_T getLogger(const char *name, DataType dt)
{
static const char ns[] = "DDS.";
const char *dtName = genericLoggerName(dt);
const size_t dtNameLen = strlen(dtName);
std::string loggerName;
const size_t len = strlen(name);
loggerName.reserve(sizeof(ns) + len + dtNameLen);
loggerName.append(ns, sizeof(ns) - 1);
loggerName.append(dtName, dtNameLen);
loggerName.append(name, len);
try {
return NGL::NGLLoggerBootstrap::getNGLRootLogger().
getNGLLogger( loggerName );
} catch ( const std::exception &ex )
{
fprintf(stderr, "Error retrieving logger: %s", ex.what());
abort();
}
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <NGLLogger/INGLLogger.h>
#include <DDS/Generic/ddsgen_export.h>
namespace DDS
{
enum class DataType
{
DATA_TYPE_UNKNOWN = 0,
DATA_WRITER = 1,
DATA_READER = 2,
DYNAMIC_DATA_WRITER = 3,
DYNAMIC_DATA_READER = 4,
DATA_TOPIC_SERVER = 8,
DATA_TOPIC_CLIENT = 9,
FUNCTION_TOPIC_SERVER = 12,
FUNCTION_TOPIC_CLIENT = 13
};
using LOGGER_T = NGL::INGLLogger&;
DDSGEN_EXPORT LOGGER_T getLogger(const char *name, DataType dt);
inline LOGGER_T getLogger(const std::string &loggerName, DataType dt)
{
return getLogger(loggerName.data(), dt);
}
}
+191
View File
@@ -0,0 +1,191 @@
#include "subscriber_listener.h"
#include "generic_base.h"
#include "generic_logger.h"
namespace DDSGeneric
{
SubscriberListener::SubscriberListener()
: m_bPrepareForShutdown(false)
, m_logger(DDS::getLogger("SubscriberListener", DDS::DataType::DATA_TOPIC_CLIENT))
{
qRegisterMetaType<std::string>("std::string");
DEBUG_LOG(m_logger) << "SubscriberListener initialized, this " << this;
}
void SubscriberListener::addCallback(const std::string &topicName,
const std::string &topicTypeName,
const dds::sub::AnyDataReader &dataReader,
SubscriberListenerCallback *cb)
{
DEBUG_LOG(m_logger) << "addSubscriberCallback for " << topicName << ", type " << topicTypeName
<< ", cb " << cb << ", this " << this;
QWriteLocker l(&m_accessLock);
if (m_bPrepareForShutdown)
return;
m_droCallbacks.insertMulti(std::make_pair(topicName, topicTypeName), fillData(dataReader, cb));
}
void SubscriberListener::removeCallback(const std::string &topicName,
const std::string &topicTypeName,
const dds::sub::AnyDataReader &dataReader,
SubscriberListenerCallback *cb)
{
DEBUG_LOG(m_logger) << "IN removeSubscriberCallback for " << topicName << ", type " << topicTypeName
<< ", cb " << cb << ", this " << this;
QWriteLocker l(&m_accessLock);
m_droCallbacks.remove(std::make_pair(topicName, topicTypeName), fillData(dataReader, cb));
}
void SubscriberListener::enaDisaCallback(const std::string &topicName,
const std::string &topicTypeName,
const dds::sub::AnyDataReader &dataReader,
SubscriberListenerCallback *cb,
bool bEnable)
{
const std::pair<std::string, std::string> key(std::make_pair(topicName, topicTypeName));
const cbData cbDataSearch(fillData(dataReader, cb));
bool bFound = false;
{
QWriteLocker l(&m_accessLock);
if (m_bPrepareForShutdown)
return;
QMultiMap<std::pair<std::string, std::string>, cbData>::iterator cbIt = m_droCallbacks.find(key);
for( ; cbIt != m_droCallbacks.end() && cbIt.key() == key; ++cbIt)
{
const cbData &data = cbIt.value();
if (data != cbDataSearch)
continue;
bFound = true;
cbIt.value().enabled = bEnable;
break;
}
}
if (!bFound)
WARNING_LOG(m_logger) << "Can not " << (bEnable ? "enable" : "disable")
<< " callback for " << topicName << ", type " << topicTypeName
<< ", cb " << cb << ", this " << this << " - not found!";
}
void SubscriberListener::prepareShutdown()
{
QWriteLocker l(&m_accessLock);
m_droCallbacks.clear();
m_bPrepareForShutdown = true;
}
void SubscriberListener::on_data_available(dds::sub::AnyDataReader &reader)
{
const std::string &topicName = reader.topic_name();
const std::string &topicTypeName = reader.type_name();
const std::pair<std::string, std::string> key(std::make_pair(topicName, topicTypeName));
QReadLocker l(&m_accessLock);
if (m_droCallbacks.isEmpty() || m_bPrepareForShutdown)
return;
DEBUG_LOG(m_logger) << "Data available for " << topicName << ", type " << topicTypeName << ", this " << this;
QMultiMap<std::pair<std::string, std::string>, cbData>::iterator cbIt = m_droCallbacks.find(key);
const QMultiMap<std::pair<std::string, std::string>, cbData>::iterator cbEnd = m_droCallbacks.end();
for( ; cbIt != cbEnd && cbIt.key() == key; ++cbIt)
{
const cbData &data = cbIt.value();
if (data.dataReader != reader)
continue;
if (!data.enabled)
continue;
if (data.methodToCall.isValid())
{
data.methodToCall.invoke(data.cb, Qt::QueuedConnection,
Q_ARG(std::string, topicTypeName),
Q_ARG(std::string, topicName));
cbIt.value().enabled = false;
}
break;
}
}
void SubscriberListener::on_liveliness_changed(dds::sub::AnyDataReader &reader,
const dds::core::status::LivelinessChangedStatus &livelinessStatus)
{
const std::string &topicName = reader.topic_name();
const std::string &topicTypeName = reader.type_name();
DEBUG_LOG(m_logger) << "on_liveliness_changed: " << livelinessStatus.alive_count()
<< ", " << livelinessStatus.not_alive_count()
<< ", " << livelinessStatus.alive_count_change()
<< ", " << livelinessStatus.not_alive_count_change()
<< " for type " << topicTypeName;
bool bNewWriters = false;
bool bNoWritersAnymore = false;
if (livelinessStatus.alive_count() == 0 &&
livelinessStatus.alive_count_change() < 0)
bNoWritersAnymore = true;
if (livelinessStatus.alive_count() != 0 &&
livelinessStatus.alive_count_change() == livelinessStatus.alive_count())
bNewWriters = true;
if (!bNewWriters && !bNoWritersAnymore)
return;
QReadLocker l(&m_accessLock);
if (m_droCallbacks.isEmpty())
return;
const std::pair<std::string, std::string> key(std::make_pair(topicName, topicTypeName));
QMultiMap<std::pair<std::string, std::string>, cbData>::iterator cbIt = m_droCallbacks.find(key);
const QMultiMap<std::pair<std::string, std::string>, cbData>::iterator cbEnd = m_droCallbacks.end();
for( ; cbIt != cbEnd && cbIt.key() == key; ++cbIt)
{
const cbData &data = cbIt.value();
if (data.dataReader != reader)
continue;
if (bNoWritersAnymore)
{
DEBUG_LOG(m_logger) << "bNoWritersAnymore for " << topicName << ", type " << topicTypeName;
data.cb->noWriterAnymore(topicTypeName, topicName);
}
else if (bNewWriters)
{
data.cb->writerAvailable(topicTypeName, topicName);
if (!data.enabled)
continue;
if (data.methodToCall.isValid())
{
DEBUG_LOG(m_logger) << "methodcall is valid for " << topicName << ", type " << topicTypeName;
data.methodToCall.invoke(data.cb, Qt::QueuedConnection,
Q_ARG(std::string, topicTypeName),
Q_ARG(std::string, topicName));
cbIt.value().enabled = false;
}
}
}
}
SubscriberListener::cbData SubscriberListener::fillData(const dds::sub::AnyDataReader &dataReader, SubscriberListenerCallback *cb) const
{
const char *signal = SIGNAL(droDataAvailable(std::string,std::string));
const char *slot = SLOT(droDataAvailable(std::string,std::string));
QMetaMethod method = DDS::Helper::getMethod(m_logger,
cb,
slot,
signal);
return cbData(cb, dataReader, method);
}
}
+140
View File
@@ -0,0 +1,140 @@
#pragma once
#include <DDS/Generic/ddsgen_export.h>
#include <dds/sub/SubscriberListener.hpp>
#include <dds/sub/AnyDataReader.hpp>
#include <dds/topic/Topic.hpp>
#include <QMetaMethod>
#include <QReadWriteLock>
#include <QObject>
namespace NGL {
class INGLLogger;
}
namespace DDSGeneric
{
class DDSGEN_EXPORT SubscriberListenerCallback : public QObject
{
Q_OBJECT
public:
virtual ~SubscriberListenerCallback() = default;
public Q_SLOTS:
virtual void droDataAvailable(const std::string &topicType, const std::string &topicName) = 0;
virtual void noWriterAnymore(const std::string &topicType, const std::string &topicName) = 0;
virtual void writerAvailable(const std::string &topicType, const std::string &topicName) = 0;
};
class DDSGEN_EXPORT SubscriberListener final : public dds::sub::SubscriberListener
{
public:
SubscriberListener();
template<typename TopicType>
void addCallback(const dds::sub::DataReader<TopicType> &dataReader,
SubscriberListenerCallback *cb)
{
addCallback(dataReader->topic_name(), dataReader->type_name(), dds::sub::AnyDataReader(dataReader), cb);
}
template<typename TopicType>
void removeCallback(const dds::sub::DataReader<TopicType> &dataReader,
SubscriberListenerCallback *cb)
{
removeCallback(dataReader->topic_name(), dataReader->type_name(), dds::sub::AnyDataReader(dataReader), cb);
}
template<typename TopicType>
void enableCallback(const dds::sub::DataReader<TopicType> &dataReader,
SubscriberListenerCallback *cb)
{
enaDisaCallback(dataReader->topic_name(), dataReader->type_name(), dds::sub::AnyDataReader(dataReader), cb, true);
}
template<typename TopicType>
void disableCallback(const dds::sub::DataReader<TopicType> &dataReader,
SubscriberListenerCallback *cb)
{
enaDisaCallback(dataReader->topic_name(), dataReader->type_name(), dds::sub::AnyDataReader(dataReader), cb, false);
}
static dds::core::status::StatusMask mask()
{
return (dds::core::status::StatusMask::liveliness_changed() | dds::core::status::StatusMask::data_available());
}
void prepareShutdown();
protected:
void addCallback(const std::string &topicName,
const std::string &topicTypeName,
const dds::sub::AnyDataReader &dataReader,
SubscriberListenerCallback *cb);
void removeCallback(const std::string &topicName,
const std::string &topicTypeName,
const dds::sub::AnyDataReader &dataReader,
SubscriberListenerCallback *cb);
void enaDisaCallback(const std::string &topicName,
const std::string &topicTypeName,
const dds::sub::AnyDataReader &dataReader,
SubscriberListenerCallback *cb,
bool bEnable);
void on_data_on_readers(dds::sub::Subscriber &) override {}
void on_liveliness_changed(dds::sub::AnyDataReader &reader,
const dds::core::status::LivelinessChangedStatus &livelinessStatus) override;
void on_requested_deadline_missed(dds::sub::AnyDataReader&,
const dds::core::status::RequestedDeadlineMissedStatus&) override {}
void on_requested_incompatible_qos(dds::sub::AnyDataReader&,
const dds::core::status::RequestedIncompatibleQosStatus&) override {}
void on_sample_rejected(dds::sub::AnyDataReader&,
const dds::core::status::SampleRejectedStatus&) override {}
void on_data_available(dds::sub::AnyDataReader &reader) override;
void on_subscription_matched(dds::sub::AnyDataReader&,
const dds::core::status::SubscriptionMatchedStatus&) override {}
void on_sample_lost(dds::sub::AnyDataReader&,
const dds::core::status::SampleLostStatus&) override {}
private:
struct cbData
{
SubscriberListenerCallback *cb = nullptr;
bool enabled = true;
dds::sub::AnyDataReader dataReader;
QMetaMethod methodToCall;
bool operator ==(const cbData &o) const
{
return cb == o.cb && methodToCall == o.methodToCall && dataReader == o.dataReader;
}
bool operator !=(const cbData &o) const
{
return !operator ==(o);
}
cbData(SubscriberListenerCallback *cb_,
const dds::sub::AnyDataReader &dataReader_,
const QMetaMethod &methodToCall_)
: cb(cb_)
, enabled(true)
, dataReader(dataReader_)
, methodToCall(methodToCall_)
{}
};
cbData fillData(const dds::sub::AnyDataReader &dataReader, SubscriberListenerCallback *cb) const;
QMultiMap<std::pair<std::string, std::string>, cbData> m_droCallbacks;
QReadWriteLock m_accessLock;
bool m_bPrepareForShutdown;
NGL::INGLLogger &m_logger;
};
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <dds/core/xtypes/StructType.hpp>
namespace DDSGeneric
{
struct TopicDescription
{
TopicDescription()
: type(dds::core::xtypes::StructType("Unknown")) {}
TopicDescription(const std::string &name_, const std::string &typeName_, const dds::core::xtypes::StructType &type_)
: name(name_)
, typeName(typeName_)
, type(type_)
{}
std::string name;
std::string typeName;
dds::core::xtypes::StructType type;
bool operator==(const TopicDescription &ti) const
{
return (name.compare(ti.name) == 0) &&
(typeName.compare(ti.typeName) == 0) &&
(type == ti.type);
}
bool isEmpty() const
{
return name.empty();
}
};
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <QObject>
#include <QMap>
#include <QString>
#include <DDS/Generic/topic_description.h>
#include <Common/ProjectConfig/project_defines.h>
namespace RoutingService
{
class TopicDispatcherInterface;
}
namespace DDSGeneric
{
class TopicTypePlugin
{
public:
virtual ~TopicTypePlugin() = default;
virtual QMap<std::string, DDSGeneric::TopicDescription> getDataTopics() const = 0;
virtual QMap<std::string, DDSGeneric::TopicDescription> getFunctionTopics() const = 0;
virtual std::vector<RoutingService::TopicDispatcherInterface *> topicsToRouteToWs(IDL::PartitionType partitionType) const = 0;
virtual std::vector<RoutingService::TopicDispatcherInterface *> topicsToRouteToSv(IDL::PartitionType partitionType) const = 0;
virtual QString getServiceShortName() const = 0;
};
}
Q_DECLARE_INTERFACE(DDSGeneric::TopicTypePlugin, "topic_type")
+147
View File
@@ -0,0 +1,147 @@
#include "wait_set_handler.h"
#include "generic_logger.h"
#include "generic_base.h"
namespace DDSGeneric
{
WaitSetHandler::WaitSetHandler()
: m_logger(DDS::getLogger("WaitSetHandler", DDS::DataType::FUNCTION_TOPIC_SERVER))
{
m_waitSet.attach_condition(m_abortCondition);
m_task = std::async(std::launch::async, [this]() { this->waitSetTask();});
}
WaitSetHandler::~WaitSetHandler()
{
m_abortCondition.trigger_value(true);
m_task.wait();
}
bool WaitSetHandler::attachWaitSet(const dds::core::cond::Condition &cond, CallbackInterface *cb)
{
DEBUG_LOG(m_logger) << "AttachWaitSet with callback " << cb;
WaitSetData wsd(cond, cb);
std::lock_guard<std::mutex> l(m_accessMutex);
auto it = m_waitSetData.find(wsd.condition->native_condition());
if (it != m_waitSetData.end())
{
WARNING_LOG(m_logger) << "Waitset " << cb << " already registered";
return false;
}
m_waitSet.attach_condition(wsd.condition);
wsd.enabled = true;
wsd.needWaitSetAttach = false;
m_waitSetData.insert(std::make_pair(wsd.condition->native_condition(), wsd));
return true;
}
bool WaitSetHandler::detachWaitSet(const dds::core::cond::Condition &cond, CallbackInterface *cb)
{
DEBUG_LOG(m_logger) << "DetachWaitSet with callback " << cb;
WaitSetData wsd(cond, cb);
std::lock_guard<std::mutex> l(m_accessMutex);
auto it = m_waitSetData.find(wsd.condition->native_condition());
if (it == m_waitSetData.end())
{
WARNING_LOG(m_logger) << "Waitset for " << cb << " not registered";
return false;
}
m_waitSet.detach_condition(wsd.condition);
m_waitSetData.erase(it);
return true;
}
bool WaitSetHandler::enableWaitSet(const dds::core::cond::Condition &cond, CallbackInterface *cb)
{
DEBUG_LOG(m_logger) << "EnableWaitSet with callback " << cb;
WaitSetData wsd(cond, cb);
std::lock_guard<std::mutex> l(m_accessMutex);
auto it = m_waitSetData.find(wsd.condition->native_condition());
if (it == m_waitSetData.end())
{
ERROR_LOG(m_logger) << "Could not find WaitSetData - cannot enable waitSet for " << cb;
return false;
}
if (it->second.needWaitSetAttach)
m_waitSet.attach_condition(wsd.condition);
it->second.enabled = true;
it->second.needWaitSetAttach = false;
return true;
}
bool WaitSetHandler::disableWaitSet(const dds::core::cond::Condition &cond, CallbackInterface *cb)
{
DEBUG_LOG(m_logger) << "DisableWaitSet with callback " << cb;
WaitSetData wsd(cond, cb);
std::lock_guard<std::mutex> l(m_accessMutex);
auto it = m_waitSetData.find(wsd.condition->native_condition());
if (it == m_waitSetData.end())
{
ERROR_LOG(m_logger) << "Could not find WaitSetData - cannot disable waitSet for " << cb;
return false;
}
m_waitSet.detach_condition(wsd.condition);
it->second.enabled = false;
it->second.needWaitSetAttach = true;
return true;
}
int WaitSetHandler::attachedConditionCount() const
{
return m_waitSet.conditions().size();
}
void WaitSetHandler::waitSetTask()
{
while (true)
{
dds::core::cond::WaitSet::ConditionSeq activeConditionsSeq = m_waitSet.wait();
for (const dds::core::cond::Condition &cond : activeConditionsSeq)
{
if (cond == m_abortCondition)
{
m_waitSet->detach_all();
return;
}
std::lock_guard<std::mutex> l(m_accessMutex);
const DDS_Condition *ddsCond = cond->native_condition();
auto it = m_waitSetData.lower_bound(ddsCond);
while (it != m_waitSetData.end())
{
WaitSetData &wsd = it->second;
if (wsd.enabled)
{
DEBUG_LOG(m_logger) << "Condition triggered for " << wsd.callback;
wsd.callback->dataAvailable();
}
++it;
if (it == m_waitSetData.end() || it->first != ddsCond)
break;
}
}
}
}
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include <dds/core/cond/Condition.hpp>
#include <dds/core/cond/GuardCondition.hpp>
#include <dds/core/cond/WaitSet.hpp>
#include <future>
#include <map>
#include <DDS/Generic/ddsgen_export.h>
namespace NGL
{
class INGLLogger;
}
namespace DDSGeneric
{
class DDSGEN_EXPORT WaitSetHandler final
{
public:
class CallbackInterface
{
public:
virtual ~CallbackInterface() = default;
virtual void dataAvailable() = 0;
};
WaitSetHandler();
~WaitSetHandler();
bool attachWaitSet(const dds::core::cond::Condition &cond, CallbackInterface *cb);
bool detachWaitSet(const dds::core::cond::Condition &cond, CallbackInterface *cb);
bool enableWaitSet(const dds::core::cond::Condition &cond, CallbackInterface *cb);
bool disableWaitSet(const dds::core::cond::Condition &cond, CallbackInterface *cb);
int attachedConditionCount() const;
private:
struct WaitSetData final
{
dds::core::cond::Condition condition;
CallbackInterface *callback = nullptr;
bool enabled = true;
bool needWaitSetAttach = false;
WaitSetData(const dds::core::cond::Condition &cond, CallbackInterface *cb)
: condition(cond)
, callback(cb)
{}
};
private:
void waitSetTask();
private:
dds::core::cond::WaitSet m_waitSet;
dds::core::cond::GuardCondition m_abortCondition;
std::future<void> m_task;
std::mutex m_accessMutex;
std::map<const DDS_Condition *, WaitSetData> m_waitSetData;
NGL::INGLLogger &m_logger;
};
}
+35
View File
@@ -0,0 +1,35 @@
#
# $Id$
# $HeadURL$
# $Author$
#
SET(SRCS
DDS_DefaultQos.h
DDS_DefaultQos.cpp
)
IF(TABLET_MODE)
NGL_INSTALL(WS AAR HLA SV FILES "default_profile_tablet.xml" DESTINATION "./share/Lts/DDS" RENAME "default_profile.xml")
NGL_INSTALL(WS AAR HLA SV FILES "default_profile.xml" DESTINATION "./share/Lts/DDS" RENAME "default_profile_ws.xml")
ELSE()
INSTALL_SHARED_FILES(SRCS
FILES "default_profile.xml"
DESTINATION "DDS"
SV WS AAR HLA
)
ENDIF()
NGL_ADD_LIBRARY2(AAR SV HLA WS ddsQoS SHARED ${SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(ddsQoS
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../../
)
NGL_TARGET_LINK_LIBRARIES(ddsQoS
PRIVATE
RTI::nddsc
RTI::nddscpp2
NGLLogger
ngltools
PUBLIC
Qt5::Core
)
+254
View File
@@ -0,0 +1,254 @@
#include "DDS_DefaultQos.h"
#include <QUrl>
#include <ngltools/ngltools_filesystem.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace DDS
{
namespace QosProfile
{
static void printPolicyMap(const std::map<std::string, std::string> &policyMap, NGL::INGLLogger &logger);
QString defaultProfileXml()
{
return NGL::findFile(NGL::FileType::ConfigFile, QStringLiteral("default_profile.xml"));
}
bool setDefault(const QStringList &qosFileList)
{
NGL::INGLLogger &logger = NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("DDS.DefaultQos");
const QStringList qosStr = qosFileList.isEmpty() ?
QStringList(defaultProfileXml()) :
qosFileList;
if (logger.isDebugEnabled())
{
DEBUG_LOG(logger) << "findFile - m_qosFileList = ";
for (const QString &file : qosStr)
DEBUG_LOG(logger) << "# profile: " << file;
}
try
{
dds::core::StringSeq urls;
urls.reserve(qosStr.size());
for (const QString &file : qosStr)
{
const QString url = QUrl::fromLocalFile(file).toString();
DEBUG_LOG(logger) << "Using qosFile: " << file << " - " << url;
urls.emplace_back(url.toStdString());
}
dds::core::QosProvider defaultProvider = dds::core::QosProvider::Default();
rti::core::QosProviderParams qosProviderParams = defaultProvider->default_provider_params();
qosProviderParams.url_profile(urls);
defaultProvider->default_provider_params(qosProviderParams);
print(defaultProvider, logger);
}
catch (const dds::core::Exception &ex)
{
ERROR_LOG(logger) << ex.what();
return false;
}
return true;
}
static QString fixupSchemeName(const QUrl &url)
{
QString str = url.toString();
str = str.replace(QLatin1String("tcpv4lan"), QLatin1String("tcpv4_lan"));
return QStringLiteral("0@") + str;
}
dds::domain::qos::DomainParticipantQos domainParticipantShmUdp()
{
return domainParticipantDefault(ConnectionType::ShmUdp, QUrl());
}
dds::domain::qos::DomainParticipantQos domainParticipantShmUdpLocalhost()
{
return domainParticipantDefault(ConnectionType::ShmUdpLocalhost, QUrl());
}
dds::domain::qos::DomainParticipantQos domainParticipantShm()
{
return domainParticipantDefault(ConnectionType::Shm, QUrl());
}
dds::domain::qos::DomainParticipantQos domainParticipantShmTcp(const QUrl &tcpConnectionUrl)
{
return domainParticipantDefault(ConnectionType::Tcp, tcpConnectionUrl);
}
dds::domain::qos::DomainParticipantQos domainParticipantDefault(ConnectionType type, const QUrl &tcpConnectionUrl)
{
dds::core::QosProvider defaultProvider = dds::core::QosProvider::Default();
dds::domain::qos::DomainParticipantQos dpQos = defaultProvider.participant_qos();
switch (type)
{
case ConnectionType::Tcp:
{
dpQos = defaultProvider.participant_qos("LtsProfiles::DomainParticipant.Tcp");
if (tcpConnectionUrl.scheme() == QLatin1String("tcpv4lan"))
{
rti::core::policy::Discovery discovery;
dpQos >> discovery;
const dds::core::StringSeq seq({fixupSchemeName(tcpConnectionUrl).toStdString()});
discovery.initial_peers(seq);
dpQos << discovery;
}
else
{
rti::core::policy::Property props;
dpQos >> props;
props.set({"dds.transport.TCPv4.tcp1.server_bind_port", std::to_string(tcpConnectionUrl.port(7400))});
dpQos << props;
}
break;
}
case ConnectionType::Shm:
dpQos = defaultProvider.participant_qos("LtsProfiles::DomainParticipant.Shm");
break;
case ConnectionType::ShmUdp:
dpQos = defaultProvider.participant_qos("LtsProfiles::DomainParticipant.ShmUdp");
break;
case ConnectionType::ShmUdpLocalhost:
dpQos = defaultProvider.participant_qos("LtsProfiles::DomainParticipant.ShmUdpLocalhost");
break;
case ConnectionType::Server:
dpQos = defaultProvider.participant_qos("LtsProfiles::DomainParticipant.Server");
break;
case ConnectionType::Client:
dpQos = defaultProvider.participant_qos("LtsProfiles::DomainParticipant.Client");
break;
case ConnectionType::Tablet:
dpQos = defaultProvider.participant_qos("LtsProfiles::DomainParticipant.Tablet");
break;
}
return dpQos;
}
dds::pub::qos::DataWriterQos dataWriterDefault(TopicType type)
{
dds::core::QosProvider defaultProvider = dds::core::QosProvider::Default();
dds::pub::qos::DataWriterQos dwQos = defaultProvider.datawriter_qos();
switch (type)
{
case TopicType::DataTopic:
break;
case TopicType::FunctionTopic:
dwQos = defaultProvider.datawriter_qos("LtsProfiles::DataReaderWriter.FT");
break;
}
return dwQos;
}
dds::sub::qos::DataReaderQos dataReaderDefault(TopicType type)
{
dds::core::QosProvider defaultProvider = dds::core::QosProvider::Default();
dds::sub::qos::DataReaderQos drQos = defaultProvider.datareader_qos();
switch (type)
{
case TopicType::DataTopic:
break;
case TopicType::FunctionTopic:
drQos = defaultProvider.datareader_qos("LtsProfiles::DataReaderWriter.FT");
break;
}
return drQos;
}
dds::sub::qos::DataReaderQos dataReaderDefault( ConnectionType conType)
{
dds::core::QosProvider defaultProvider = dds::core::QosProvider::Default();
dds::sub::qos::DataReaderQos drQos = defaultProvider.datareader_qos();
switch (conType)
{
case ConnectionType::Shm:
drQos = defaultProvider.datareader_qos("LtsProfiles::DomainParticipant.Shm");
break;
case ConnectionType::ShmUdp:
drQos = defaultProvider.datareader_qos("LtsProfiles::DomainParticipant.ShmUdp");
break;
case ConnectionType::ShmUdpLocalhost:
drQos = defaultProvider.datareader_qos("LtsProfiles::DomainParticipant.ShmUdpLocalhost");
break;
case ConnectionType::Server:
drQos = defaultProvider.datareader_qos("LtsProfiles::DomainParticipant.Server");
break;
case ConnectionType::Client:
drQos = defaultProvider.datareader_qos("LtsProfiles::DomainParticipant.Client");
break;
case ConnectionType::Tablet:
drQos = defaultProvider.datareader_qos("LtsProfiles::DomainParticipant.Tablet");
break;
default:
break;
}
return drQos;
}
void print(dds::core::QosProvider provider, NGL::INGLLogger &logger)
{
print(provider.participant_qos(), logger);
print(provider.datawriter_qos(), logger);
print(provider.datareader_qos(), logger);
}
void print(const dds::domain::qos::DomainParticipantQos &qos, NGL::INGLLogger &logger)
{
DEBUG_LOG(logger) << "Qos for DomainParticipant: ";
printPolicyMap(qos->property.get_all(), logger);
}
void print(const dds::sub::qos::DataReaderQos &readerQos, NGL::INGLLogger &logger)
{
DEBUG_LOG(logger) << "Qos for dataReader: ";
DEBUG_LOG(logger) << "resourceLimit.max_samples " << readerQos->resource_limits.max_samples();
printPolicyMap(readerQos->property.get_all(), logger);
}
void print(const dds::pub::qos::DataWriterQos &writerQos, NGL::INGLLogger &logger)
{
DEBUG_LOG(logger) << "Default Qos for dataWriter: ";
DEBUG_LOG(logger) << "ReliabilityKind: " << writerQos->reliability->kind();
DEBUG_LOG(logger) << "Publish mode: " << writerQos->publish_mode.kind();
printPolicyMap(writerQos->property.get_all(), logger);
}
void printPolicyMap(const std::map<std::string, std::string> &policyMap, NGL::INGLLogger &logger)
{
for (const auto &it : policyMap)
{
const std::string &policyName = it.first;
const std::string &policyContent = it.second;
DEBUG_LOG(logger) << "policy " << policyName << ": " << policyContent ;
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include <QStringList>
#include <QUrl>
#include <dds/core/QosProvider.hpp>
#include <QosProfile/ddsqos_export.h>
namespace NGL
{
class INGLLogger;
}
namespace DDS
{
namespace QosProfile
{
enum class ConnectionType
{
ShmUdp,
ShmUdpLocalhost,
Shm,
Tcp,
Server,
Client,
Tablet
};
enum class TopicType
{
DataTopic,
FunctionTopic
};
DDSQOS_EXPORT QString defaultProfileXml();
DDSQOS_EXPORT bool setDefault(const QStringList &qosFileList = QStringList());
DDSQOS_EXPORT dds::domain::qos::DomainParticipantQos domainParticipantDefault(ConnectionType type, const QUrl &tcpConnectionUrl);
DDSQOS_EXPORT dds::pub::qos::DataWriterQos dataWriterDefault(TopicType type);
DDSQOS_EXPORT dds::sub::qos::DataReaderQos dataReaderDefault(TopicType type);
DDSQOS_EXPORT dds::sub::qos::DataReaderQos dataReaderDefault(ConnectionType conType);
DDSQOS_EXPORT dds::domain::qos::DomainParticipantQos domainParticipantShmUdp();
DDSQOS_EXPORT dds::domain::qos::DomainParticipantQos domainParticipantShmUdpLocalhost();
DDSQOS_EXPORT dds::domain::qos::DomainParticipantQos domainParticipantShm();
DDSQOS_EXPORT dds::domain::qos::DomainParticipantQos domainParticipantShmTcp(const QUrl &tcpConnectionUrl);
DDSQOS_EXPORT void print(dds::core::QosProvider provider, NGL::INGLLogger &logger);
DDSQOS_EXPORT void print(const dds::domain::qos::DomainParticipantQos &qos, NGL::INGLLogger &logger);
DDSQOS_EXPORT void print(const dds::sub::qos::DataReaderQos &readerQos, NGL::INGLLogger &logger);
DDSQOS_EXPORT void print(const dds::pub::qos::DataWriterQos &writerQos, NGL::INGLLogger &logger);
}
}
+571
View File
@@ -0,0 +1,571 @@
<?xml version="1.0"?>
<!--
Description
XML QoS Profile for a project with DDS
For more information about XML QoS Profiles see Chapter 15 in the
RTI Connext user manual.
-->
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="/opt/RTI/resource/qos_profiles_5.1.0/schema/rti_dds_qos_profiles.xsd">
<!-- QoS Library containing the default QoS profile for the project
A QoS library is a named set of QoS profiles.
-->
<qos_library name="LtsProfiles">
<!-- QoS profile used to configure reliable communication between the DataWriter and DataReader
A QoS profile groups a set of related QoS.
-->
<qos_profile name="Base" base_name="BuiltinQosLibExp::Generic.StrictReliable" is_default_qos="true">
<!-- Participent QoS -->
<participant_qos>
<property>
<value>
<element>
<name>dds.transport.shmem.builtin.receive_buffer_size</name>
<value>20971520</value> <!-- 20MB -->
</element>
</value>
<!--FRAN: allow IP fragmentation (30jun2020)-->
<value>
<element>
<name>dds.transport.UDPv4.builtin.parent.message_size_max</name>
<value>1450</value>
</element>
</value>
<value>
<element>
<name>dds.transport.UDPv4.builtin.multicast_ttl</name>
<value>5</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.period.sec</name>
<value>0</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.period.nanosec</name>
<value>100000000</value>
</element>
<!-- UDP MTU -->
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.bytes_per_token</name>
<value>3072</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.tokens_leaked_per_period</name>
<value>0</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.tokens_added_per_period</name>
<value>1</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.max_tokens</name>
<value>1</value>
</element>
</value>
</property>
<resource_limits>
<!-- type_code is not needed for RTI 5.x and above-->
<type_code_max_serialized_length>0</type_code_max_serialized_length>
<!-- type_object is only needed if someone wants to dynamically decode the structures
without haveing this info otherwise (e.g. programatically) which is not the case
in our system-->
<type_object_max_serialized_length>0</type_object_max_serialized_length>
</resource_limits>
<discovery_config>
<!--FRAN: disable sending DATA(p) packets to domain 0, RTPX (30jun2020)-->
<default_domain_announcement_period>
<sec>DURATION_INFINITE_SEC</sec>
<nanosec>DURATION_INFINITE_NSEC</nanosec>
</default_domain_announcement_period>
<publication_writer_publish_mode>
<kind>ASYNCHRONOUS_PUBLISH_MODE_QOS</kind>
<!-- <flow_controller_name>dds.flow_controller.token_bucket.my_2_5Mbps_fc</flow_controller_name> -->
</publication_writer_publish_mode>
<subscription_writer_publish_mode>
<kind>ASYNCHRONOUS_PUBLISH_MODE_QOS</kind>
<!-- <flow_controller_name>dds.flow_controller.token_bucket.my_2_5Mbps_fc</flow_controller_name> -->
</subscription_writer_publish_mode>
<participant_liveliness_assert_period>
<sec>5</sec>
<nanosec>0</nanosec>
</participant_liveliness_assert_period>
<participant_liveliness_lease_duration>
<sec>20</sec>
<nanosec>0</nanosec>
</participant_liveliness_lease_duration>
<max_liveliness_loss_detection_period>
<sec>10</sec>
<nanosec>0</nanosec>
</max_liveliness_loss_detection_period>
<publication_writer>
<enable_multicast_periodic_heartbeat>1</enable_multicast_periodic_heartbeat>
<!--FRAN: why these values?-->
<min_send_window_size>20</min_send_window_size>
<max_send_window_size>400</max_send_window_size>
<heartbeats_per_max_samples>50</heartbeats_per_max_samples>
<!--FRAN: set to 500ms instead of 50ms (30jun2020)-->
<fast_heartbeat_period>
<sec>0</sec>
<nanosec>500000000</nanosec>
</fast_heartbeat_period>
<late_joiner_heartbeat_period>
<sec>0</sec>
<nanosec>500000000</nanosec>
</late_joiner_heartbeat_period>
<max_nack_response_delay>
<sec>0</sec>
<nanosec>500000000</nanosec>
</max_nack_response_delay>
</publication_writer>
<subscription_writer>
<enable_multicast_periodic_heartbeat>1</enable_multicast_periodic_heartbeat>
<!--FRAN: set to 500ms instead of 50ms (30jun2020)-->
<min_send_window_size>20</min_send_window_size>
<max_send_window_size>400</max_send_window_size>
<heartbeats_per_max_samples>50</heartbeats_per_max_samples>
<fast_heartbeat_period>
<sec>0</sec>
<nanosec>500000000</nanosec>
</fast_heartbeat_period>
<late_joiner_heartbeat_period>
<sec>0</sec>
<nanosec>500000000</nanosec>
</late_joiner_heartbeat_period>
<max_nack_response_delay>
<sec>0</sec>
<nanosec>500000000</nanosec>
</max_nack_response_delay>
</subscription_writer>
</discovery_config>
</participant_qos>
<!-- Publisher QOS -->
<!--Patric: we enabled coherent sets -->
<!-- QoS used to configure the data writer -->
<!--
<publisher_qos>
<presentation>
<access_scope>TOPIC_PRESENTATION_QOS</access_scope>
<coherent_access>true</coherent_access>
</presentation>
</publisher_qos>
<subscriber_qos>
<presentation>
<access_scope>TOPIC_PRESENTATION_QOS</access_scope>
<coherent_access>true</coherent_access>
</presentation>
</subscriber_qos>
-->
<!-- QoS used to configure the data writer -->
<datawriter_qos>
<publish_mode>
<kind>ASYNCHRONOUS_PUBLISH_MODE_QOS</kind>
</publish_mode>
<property>
<value>
<!-- The next setting is used to force dynamic memory
allocation for samples with a serialized size of
larger than 2K. Without setting
pool_buffer_max_size, all memory would be obtained
from a pre-allocated pool, which would greatly
increase the memory usage in an application which
sends large data.
-->
<element>
<name>dds.data_writer.history.memory_manager.fast_pool.pool_buffer_max_size</name>
<value>2048</value>
</element>
</value>
</property>
<reliability>
<kind>RELIABLE_RELIABILITY_QOS</kind>
<!--FRAN: why 60 seconds?-->
<max_blocking_time>
<sec>60</sec>
</max_blocking_time>
</reliability>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
<history>
<kind>KEEP_LAST_HISTORY_QOS</kind>
<depth>1</depth>
</history>
<resource_limits>
<!--<max_samples_per_instance>1</max_samples_per_instance>-->
<max_samples>8000</max_samples>
</resource_limits>
<protocol>
<rtps_reliable_writer>
<!--FRAN: why these values?-->
<min_send_window_size>20</min_send_window_size>
<max_send_window_size>400</max_send_window_size>
<heartbeats_per_max_samples>50</heartbeats_per_max_samples>
</rtps_reliable_writer>
</protocol>
<writer_resource_limits>
<autoregister_instances>true</autoregister_instances>
</writer_resource_limits>
</datawriter_qos>
<datareader_qos>
<reliability>
<kind>RELIABLE_RELIABILITY_QOS</kind>
</reliability>
<reader_data_lifecycle>
<autopurge_nowriter_samples_delay>
<sec>5</sec>
<nanosec>DURATION_ZERO_NSEC</nanosec>
</autopurge_nowriter_samples_delay>
<autopurge_disposed_samples_delay>
<sec>5</sec>
<nanosec>DURATION_ZERO_NSEC</nanosec>
</autopurge_disposed_samples_delay>
</reader_data_lifecycle>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
<!-- Set this field to 0 if you want brokered communication (all communication
passes through the persistence service before being received by the datareader;
i.e. datawriter and datareader cannot 'see' each other directly)
-->
<direct_communication>true</direct_communication>
</durability>
<history>
<kind>KEEP_LAST_HISTORY_QOS</kind>
<depth>1</depth>
</history>
<resource_limits>
<!--<initial_samples>200</initial_samples> -->
<!--<max_samples_per_instance>1</max_samples_per_instance> -->
<!-- We limit max_samples on the reader so that if the
writer is publishing samples faster than the reader is
taking them then the reader will start sending NACKs
for samples received after the number of samples in
its queue reaches max_samples. This behavior will put
pressure on the writer to throttle back its publishing
speed.
-->
<max_samples>8000</max_samples>
</resource_limits>
<reader_resource_limits>
<!-- Determines whether the DataReader pre-allocates
storage for storing fragmented samples. This setting
can be used to limit upfront memory allocation costs
in applications which are dealing with large data
-->
<dynamically_allocate_fragmented_samples>true</dynamically_allocate_fragmented_samples>
<max_samples_per_read>8000</max_samples_per_read>
</reader_resource_limits>
<property>
<value>
<!-- The next setting is used to force dynamic memory
allocation for samples with a serialized size of
larger than 2K. Without setting
pool_buffer_max_size, all memory would be obtained
from a pre-allocated pool, which would greatly
increase the memory usage in an application which
sends large data.
-->
<element>
<name>dds.data_reader.history.memory_manager.fast_pool.pool_buffer_max_size</name>
<value>2048</value>
</element>
</value>
</property>
</datareader_qos>
</qos_profile>
<!--FRAN: who uses this profile?-->
<!--Function topic. tablets->server-->
<qos_profile name="DataReaderWriter.FT" base_name="LtsProfiles::Base">
<datareader_qos>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
<history>
<kind>KEEP_ALL_HISTORY_QOS</kind>
</history>
</datareader_qos>
<datawriter_qos>
<publish_mode>
<kind>ASYNCHRONOUS_PUBLISH_MODE_QOS</kind>
</publish_mode>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
<history>
<kind>KEEP_LAST_HISTORY_QOS</kind>
<depth>1</depth>
</history>
</datawriter_qos>
</qos_profile>
<!--Mostly used by the tablets as a special mode-->
<qos_profile name="DomainParticipant.Shm" base_name="LtsProfiles::Base">
<participant_qos>
<transport_builtin>
<mask>SHMEM</mask>
</transport_builtin>
<discovery>
<multicast_receive_addresses/>
<accept_unknown_peers>false</accept_unknown_peers>
<initial_peers>
<element>30@builtin.shmem://</element>
</initial_peers>
</discovery>
</participant_qos>
</qos_profile>
<!--Used by both tablets and server-->
<qos_profile name="DomainParticipant.ShmUdp" base_name="LtsProfiles::Base">
<participant_qos>
<transport_builtin>
<mask>UDPv4 | SHMEM</mask>
</transport_builtin>
</participant_qos>
</qos_profile>
<!-- Both server and clients. Maybe not used-->
<qos_profile name="DomainParticipant.ShmUdpLocalhost" base_name="DomainParticipant.ShmUdp">
<participant_qos>
<discovery>
<multicast_receive_addresses/>
<accept_unknown_peers>false</accept_unknown_peers>
<initial_peers>
<element>builtin.shmem://</element>
<element>builtin.udpv4://127.0.0.1</element>
</initial_peers>
</discovery>
<property>
<value>
<element>
<name>dds.transport.UDPv4.builtin.multicast_enabled</name>
<value>0</value>
</element>
<element>
<name>dds.transport.UDPv4.builtin.parent.allow_interfaces</name>
<value>127.0.0.1</value>
</element>
</value>
</property>
</participant_qos>
</qos_profile>
<!-- configured for tcp client -> listenport = -1 -->
<qos_profile name="DomainParticipant.Tcp" base_name="LtsProfiles::Base">
<participant_qos>
<transport_builtin>
<mask>MASK_NONE</mask>
</transport_builtin>
<property>
<value>
<element>
<name>dds.transport.load_plugins</name>
<value>dds.transport.TCPv4.tcp1</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.library</name>
<value>nddstransporttcp</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.create_function</name>
<value>NDDS_Transport_TCPv4_create</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.parent.classid</name>
<value>NDDS_TRANSPORT_CLASSID_TCPV4_LAN</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.server_bind_port</name>
<value>-1</value>
</element>
<!--
<element>
<name>dds.transport.TCPv4.tcp1.logging_verbosity_bitmap</name>
<value>159</value>
</element>
-->
<element>
<name>dds.transport.TCPv4.tcp1.disable_nagle</name>
<value>1</value>
</element>
<!--
<element>
<name>dds.transport.TCPv4.tcp1.send_max_wait_sec</name>
<value>-1</value>
</element>
-->
<element>
<name>dds.transport.TCPv4.tcp1.max_packet_size</name>
<value>63020</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.parent.message_size_max</name>
<value>63020</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.recv_socket_buffer_size</name>
<value>8388608</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.send_socket_buffer_size</name>
<value>8388608</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.connection_liveliness.enable</name>
<value>1</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.connection_liveliness.lease_duration</name>
<value>5</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.connection_liveliness.assertions_per_lease_duration</name>
<value>2</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.outstanding_connection_cookies</name>
<value>100</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.outstanding_connection_cookies_life_span</name>
<value>5</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.force_asynchronous_send</name>
<value>1</value>
</element>
</value>
</property>
<discovery_config>
<participant_liveliness_assert_period>
<sec>2</sec>
<nanosec>0</nanosec>
</participant_liveliness_assert_period>
<participant_liveliness_lease_duration>
<sec>5</sec>
<nanosec>0</nanosec>
</participant_liveliness_lease_duration>
<max_liveliness_loss_detection_period>
<sec>2</sec>
<nanosec>0</nanosec>
</max_liveliness_loss_detection_period>
</discovery_config>
</participant_qos>
</qos_profile>
<!--FRAN: 3 new profiles for each kind of host (30jun2020)-->
<!--This implements asymmetric discovery-->
<qos_profile name="DomainParticipant.Client" base_name="DomainParticipant.ShmUdp">
<participant_qos>
<discovery>
<initial_peers>
<element>239.255.0.2</element>
</initial_peers>
<multicast_receive_addresses>
<element>239.255.0.3</element>
</multicast_receive_addresses>
</discovery>
</participant_qos>
<!-- QoS used to configure the data reader -->
<!--Patric: reader want all our LTS Topics via multicast now -->
<datareader_qos>
<multicast>
<value>
<element>
<receive_address>239.255.1.7</receive_address>
</element>
</value>
</multicast>
<reliability>
<kind>RELIABLE_RELIABILITY_QOS</kind>
</reliability>
<reader_data_lifecycle>
<autopurge_nowriter_samples_delay>
<sec>5</sec>
<nanosec>DURATION_ZERO_NSEC</nanosec>
</autopurge_nowriter_samples_delay>
<autopurge_disposed_samples_delay>
<sec>5</sec>
<nanosec>DURATION_ZERO_NSEC</nanosec>
</autopurge_disposed_samples_delay>
</reader_data_lifecycle>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
<!-- Set this field to 0 if you want brokered communication (all communication
passes through the persistence service before being received by the datareader;
i.e. datawriter and datareader cannot 'see' each other directly)
-->
<direct_communication>true</direct_communication>
</durability>
<history>
<kind>KEEP_LAST_HISTORY_QOS</kind>
<depth>1</depth>
</history>
<resource_limits>
<!--<initial_samples>200</initial_samples> -->
<!--<max_samples_per_instance>1</max_samples_per_instance> -->
<!-- We limit max_samples on the reader so that if the
writer is publishing samples faster than the reader is
taking them then the reader will start sending NACKs
for samples received after the number of samples in
its queue reaches max_samples. This behavior will put
pressure on the writer to throttle back its publishing
speed.
-->
<max_samples>8000</max_samples>
</resource_limits>
<reader_resource_limits>
<!-- Determines whether the DataReader pre-allocates
storage for storing fragmented samples. This setting
can be used to limit upfront memory allocation costs
in applications which are dealing with large data
-->
<dynamically_allocate_fragmented_samples>true</dynamically_allocate_fragmented_samples>
<max_samples_per_read>8000</max_samples_per_read>
</reader_resource_limits>
</datareader_qos>
</qos_profile>
<!--FRAN: 3 new profiles for each kind of host (30jun2020)-->
<!--This implements asymmetric discovery-->
<qos_profile name="DomainParticipant.Server" base_name="DomainParticipant.ShmUdp">
<participant_qos>
<discovery>
<initial_peers>
<element>239.255.0.1</element>
<element>239.255.0.3</element>
<element>127.0.0.1</element>
</initial_peers>
<multicast_receive_addresses>
<element>239.255.0.2</element>
</multicast_receive_addresses>
</discovery>
</participant_qos>
</qos_profile>
<!--FRAN: 3 new profiles for each kind of host (30jun2020)-->
<!--This implements asymmetric discovery-->
<qos_profile name="DomainParticipant.Tablet" base_name="DomainParticipant.ShmUdp">
<participant_qos>
<discovery>
<initial_peers>
<element>239.255.0.2</element>
</initial_peers>
<multicast_receive_addresses>
<element>239.255.0.1</element>
</multicast_receive_addresses>
</discovery>
</participant_qos>
</qos_profile>
</qos_library>
</dds>
+544
View File
@@ -0,0 +1,544 @@
<?xml version="1.0"?>
<!--
Description
XML QoS Profile for a project with DDS
For more information about XML QoS Profiles see Chapter 15 in the
RTI Connext user manual.
-->
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="/opt/RTI/resource/qos_profiles_5.1.0/schema/rti_dds_qos_profiles.xsd">
<!-- QoS Library containing the default QoS profile for the project
A QoS library is a named set of QoS profiles.
-->
<qos_library name="LtsProfiles">
<!-- QoS profile used to configure reliable communication between the DataWriter and DataReader
A QoS profile groups a set of related QoS.
-->
<qos_profile name="Base" base_name="BuiltinQosLibExp::Generic.StrictReliable" is_default_qos="true">
<!-- Participent QoS -->
<participant_qos>
<property>
<value>
<element>
<name>dds.transport.shmem.builtin.receive_buffer_size</name>
<value>20971520</value> <!-- 20MB -->
</element>
</value>
<!--FRAN: allow IP fragmentation (30jun2020)-->
<value>
<element>
<name>dds.transport.UDPv4.builtin.parent.message_size_max</name>
<value>1450</value>
</element>
</value>
<value>
<element>
<name>dds.transport.UDPv4.builtin.multicast_ttl</name>
<value>5</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.period.sec</name>
<value>0</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.period.nanosec</name>
<value>100000000</value>
</element>
<!-- UDP MTU -->
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.bytes_per_token</name>
<value>3072</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.tokens_leaked_per_period</name>
<value>0</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.tokens_added_per_period</name>
<value>1</value>
</element>
<element>
<name>dds.flow_controller.token_bucket.my_2_5Mbps_fc.token_bucket.max_tokens</name>
<value>1</value>
</element>
</value>
</property>
<resource_limits>
<!-- type_code is not needed for RTI 5.x and above-->
<type_code_max_serialized_length>0</type_code_max_serialized_length>
<!-- type_object is only needed if someone wants to dynamically decode the structures
without haveing this info otherwise (e.g. programatically) which is not the case
in our system-->
<type_object_max_serialized_length>0</type_object_max_serialized_length>
</resource_limits>
<discovery_config>
<!--FRAN: disable sending DATA(p) packets to domain 0, RTPX (30jun2020)-->
<default_domain_announcement_period>
<sec>DURATION_INFINITE_SEC</sec>
<nanosec>DURATION_INFINITE_NSEC</nanosec>
</default_domain_announcement_period>
<publication_writer_publish_mode>
<kind>ASYNCHRONOUS_PUBLISH_MODE_QOS</kind>
<!-- <flow_controller_name>dds.flow_controller.token_bucket.my_2_5Mbps_fc</flow_controller_name> -->
</publication_writer_publish_mode>
<subscription_writer_publish_mode>
<kind>ASYNCHRONOUS_PUBLISH_MODE_QOS</kind>
<!-- <flow_controller_name>dds.flow_controller.token_bucket.my_2_5Mbps_fc</flow_controller_name> -->
</subscription_writer_publish_mode>
<participant_liveliness_assert_period>
<sec>5</sec>
<nanosec>0</nanosec>
</participant_liveliness_assert_period>
<participant_liveliness_lease_duration>
<sec>20</sec>
<nanosec>0</nanosec>
</participant_liveliness_lease_duration>
<max_liveliness_loss_detection_period>
<sec>10</sec>
<nanosec>0</nanosec>
</max_liveliness_loss_detection_period>
<publication_writer>
<enable_multicast_periodic_heartbeat>1</enable_multicast_periodic_heartbeat>
<!--FRAN: why these values?-->
<min_send_window_size>20</min_send_window_size>
<max_send_window_size>400</max_send_window_size>
<heartbeats_per_max_samples>50</heartbeats_per_max_samples>
<!--FRAN: set to 500ms instead of 50ms (30jun2020)-->
<fast_heartbeat_period>
<sec>0</sec>
<nanosec>500000000</nanosec>
</fast_heartbeat_period>
<late_joiner_heartbeat_period>
<sec>0</sec>
<nanosec>500000000</nanosec>
</late_joiner_heartbeat_period>
<max_nack_response_delay>
<sec>0</sec>
<nanosec>500000000</nanosec>
</max_nack_response_delay>
</publication_writer>
<subscription_writer>
<enable_multicast_periodic_heartbeat>1</enable_multicast_periodic_heartbeat>
<!--FRAN: set to 500ms instead of 50ms (30jun2020)-->
<min_send_window_size>20</min_send_window_size>
<max_send_window_size>400</max_send_window_size>
<heartbeats_per_max_samples>50</heartbeats_per_max_samples>
<fast_heartbeat_period>
<sec>0</sec>
<nanosec>500000000</nanosec>
</fast_heartbeat_period>
<late_joiner_heartbeat_period>
<sec>0</sec>
<nanosec>500000000</nanosec>
</late_joiner_heartbeat_period>
<max_nack_response_delay>
<sec>0</sec>
<nanosec>500000000</nanosec>
</max_nack_response_delay>
</subscription_writer>
</discovery_config>
</participant_qos>
<!-- Publisher QOS -->
<!--Patric: we enabled coherent sets -->
<!-- QoS used to configure the data writer -->
<!--
<publisher_qos>
<presentation>
<access_scope>TOPIC_PRESENTATION_QOS</access_scope>
<coherent_access>true</coherent_access>
</presentation>
</publisher_qos>
<subscriber_qos>
<presentation>
<access_scope>TOPIC_PRESENTATION_QOS</access_scope>
<coherent_access>true</coherent_access>
</presentation>
</subscriber_qos>
-->
<!-- QoS used to configure the data writer -->
<datawriter_qos>
<publish_mode>
<kind>ASYNCHRONOUS_PUBLISH_MODE_QOS</kind>
</publish_mode>
<property>
<value>
<!-- The next setting is used to force dynamic memory
allocation for samples with a serialized size of
larger than 2K. Without setting
pool_buffer_max_size, all memory would be obtained
from a pre-allocated pool, which would greatly
increase the memory usage in an application which
sends large data.
-->
<element>
<name>dds.data_writer.history.memory_manager.fast_pool.pool_buffer_max_size</name>
<value>2048</value>
</element>
</value>
</property>
<reliability>
<kind>RELIABLE_RELIABILITY_QOS</kind>
<!--FRAN: why 60 seconds?-->
<max_blocking_time>
<sec>60</sec>
</max_blocking_time>
</reliability>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
<history>
<kind>KEEP_LAST_HISTORY_QOS</kind>
<depth>1</depth>
</history>
<resource_limits>
<!--<max_samples_per_instance>1</max_samples_per_instance>-->
<max_samples>8000</max_samples>
</resource_limits>
<protocol>
<rtps_reliable_writer>
<!--FRAN: why these values?-->
<min_send_window_size>20</min_send_window_size>
<max_send_window_size>400</max_send_window_size>
<heartbeats_per_max_samples>50</heartbeats_per_max_samples>
</rtps_reliable_writer>
</protocol>
<writer_resource_limits>
<autoregister_instances>true</autoregister_instances>
</writer_resource_limits>
</datawriter_qos>
<!-- QoS used to configure the data reader -->
<!--Patric: reader want all our LTS Topics via multicast now -->
<datareader_qos>
<reliability>
<kind>RELIABLE_RELIABILITY_QOS</kind>
</reliability>
<reader_data_lifecycle>
<autopurge_nowriter_samples_delay>
<sec>5</sec>
<nanosec>DURATION_ZERO_NSEC</nanosec>
</autopurge_nowriter_samples_delay>
<autopurge_disposed_samples_delay>
<sec>5</sec>
<nanosec>DURATION_ZERO_NSEC</nanosec>
</autopurge_disposed_samples_delay>
</reader_data_lifecycle>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
<!-- Set this field to 0 if you want brokered communication (all communication
passes through the persistence service before being received by the datareader;
i.e. datawriter and datareader cannot 'see' each other directly)
-->
<direct_communication>true</direct_communication>
</durability>
<history>
<kind>KEEP_LAST_HISTORY_QOS</kind>
<depth>1</depth>
</history>
<resource_limits>
<!--<initial_samples>200</initial_samples> -->
<!--<max_samples_per_instance>1</max_samples_per_instance> -->
<!-- We limit max_samples on the reader so that if the
writer is publishing samples faster than the reader is
taking them then the reader will start sending NACKs
for samples received after the number of samples in
its queue reaches max_samples. This behavior will put
pressure on the writer to throttle back its publishing
speed.
-->
<max_samples>8000</max_samples>
</resource_limits>
<reader_resource_limits>
<!-- Determines whether the DataReader pre-allocates
storage for storing fragmented samples. This setting
can be used to limit upfront memory allocation costs
in applications which are dealing with large data
-->
<dynamically_allocate_fragmented_samples>true</dynamically_allocate_fragmented_samples>
<max_samples_per_read>8000</max_samples_per_read>
</reader_resource_limits>
<property>
<value>
<!-- The next setting is used to force dynamic memory
allocation for samples with a serialized size of
larger than 2K. Without setting
pool_buffer_max_size, all memory would be obtained
from a pre-allocated pool, which would greatly
increase the memory usage in an application which
sends large data.
-->
<element>
<name>dds.data_reader.history.memory_manager.fast_pool.pool_buffer_max_size</name>
<value>2048</value>
</element>
</value>
</property>
</datareader_qos>
</qos_profile>
<!--FRAN: who uses this profile?-->
<!--Function topic. tablets->server-->
<qos_profile name="DataReaderWriter.FT" base_name="LtsProfiles::Base">
<datareader_qos>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
<history>
<kind>KEEP_ALL_HISTORY_QOS</kind>
</history>
</datareader_qos>
<datawriter_qos>
<publish_mode>
<kind>ASYNCHRONOUS_PUBLISH_MODE_QOS</kind>
</publish_mode>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
<history>
<kind>KEEP_LAST_HISTORY_QOS</kind>
<depth>10</depth>
</history>
</datawriter_qos>
</qos_profile>
<!--Mostly used by the tablets as a special mode-->
<qos_profile name="DomainParticipant.Shm" base_name="LtsProfiles::Base">
<participant_qos>
<transport_builtin>
<mask>SHMEM</mask>
</transport_builtin>
<discovery>
<multicast_receive_addresses/>
<accept_unknown_peers>false</accept_unknown_peers>
<initial_peers>
<element>60@builtin.shmem://</element>
</initial_peers>
</discovery>
<property>
<value>
<element>
<name>dds.transport.shmem.builtin.host_id</name>
<value>1</value>
</element>
</value>
</property>
</participant_qos>
</qos_profile>
<!--Used by both tablets and server-->
<qos_profile name="DomainParticipant.ShmUdp" base_name="LtsProfiles::Base">
<participant_qos>
<transport_builtin>
<mask>UDPv4 | SHMEM</mask>
</transport_builtin>
</participant_qos>
</qos_profile>
<!-- Both server and clients. Maybe not used-->
<qos_profile name="DomainParticipant.ShmUdpLocalhost" base_name="DomainParticipant.ShmUdp">
<participant_qos>
<discovery>
<multicast_receive_addresses/>
<accept_unknown_peers>false</accept_unknown_peers>
<initial_peers>
<element>builtin.shmem://</element>
<element>builtin.udpv4://127.0.0.1</element>
</initial_peers>
</discovery>
<property>
<value>
<element>
<name>dds.transport.UDPv4.builtin.multicast_enabled</name>
<value>0</value>
</element>
<element>
<name>dds.transport.UDPv4.builtin.parent.allow_interfaces</name>
<value>127.0.0.1</value>
</element>
</value>
</property>
</participant_qos>
</qos_profile>
<!-- configured for tcp client -> listenport = -1 -->
<qos_profile name="DomainParticipant.Tcp" base_name="LtsProfiles::Base">
<participant_qos>
<transport_builtin>
<mask>MASK_NONE</mask>
</transport_builtin>
<property>
<value>
<element>
<name>dds.transport.load_plugins</name>
<value>dds.transport.TCPv4.tcp1</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.library</name>
<value>nddstransporttcp</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.create_function</name>
<value>NDDS_Transport_TCPv4_create</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.parent.classid</name>
<value>NDDS_TRANSPORT_CLASSID_TCPV4_LAN</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.server_bind_port</name>
<value>-1</value>
</element>
<!--
<element>
<name>dds.transport.TCPv4.tcp1.logging_verbosity_bitmap</name>
<value>159</value>
</element>
-->
<element>
<name>dds.transport.TCPv4.tcp1.disable_nagle</name>
<value>1</value>
</element>
<!--
<element>
<name>dds.transport.TCPv4.tcp1.send_max_wait_sec</name>
<value>-1</value>
</element>
-->
<element>
<name>dds.transport.TCPv4.tcp1.max_packet_size</name>
<value>63020</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.parent.message_size_max</name>
<value>63020</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.recv_socket_buffer_size</name>
<value>8388608</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.send_socket_buffer_size</name>
<value>8388608</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.connection_liveliness.enable</name>
<value>1</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.connection_liveliness.lease_duration</name>
<value>5</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.connection_liveliness.assertions_per_lease_duration</name>
<value>2</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.outstanding_connection_cookies</name>
<value>100</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.outstanding_connection_cookies_life_span</name>
<value>5</value>
</element>
<element>
<name>dds.transport.TCPv4.tcp1.force_asynchronous_send</name>
<value>1</value>
</element>
</value>
</property>
<discovery_config>
<participant_liveliness_assert_period>
<sec>2</sec>
<nanosec>0</nanosec>
</participant_liveliness_assert_period>
<participant_liveliness_lease_duration>
<sec>5</sec>
<nanosec>0</nanosec>
</participant_liveliness_lease_duration>
<max_liveliness_loss_detection_period>
<sec>2</sec>
<nanosec>0</nanosec>
</max_liveliness_loss_detection_period>
</discovery_config>
</participant_qos>
</qos_profile>
<!--FRAN: 3 new profiles for each kind of host (30jun2020)-->
<!--This implements asymmetric discovery-->
<qos_profile name="DomainParticipant.Client" base_name="DomainParticipant.ShmUdp">
<participant_qos>
<transport_builtin>
<mask>SHMEM</mask>
</transport_builtin>
<discovery>
<accept_unknown_peers>false</accept_unknown_peers>
<initial_peers>
<element>60@builtin.shmem://</element>
</initial_peers>
</discovery>
<property>
<value>
<element>
<name>dds.transport.shmem.builtin.host_id</name>
<value>1</value>
</element>
</value>
</property>
</participant_qos>
</qos_profile>
<!--FRAN: 3 new profiles for each kind of host (30jun2020)-->
<!--This implements asymmetric discovery-->
<qos_profile name="DomainParticipant.Server" base_name="DomainParticipant.ShmUdp">
<participant_qos>
<transport_builtin>
<mask>SHMEM</mask>
</transport_builtin>
<discovery>
<accept_unknown_peers>false</accept_unknown_peers>
<initial_peers>
<element>60@builtin.shmem://</element>
</initial_peers>
</discovery>
<property>
<value>
<element>
<name>dds.transport.shmem.builtin.host_id</name>
<value>1</value>
</element>
</value>
</property>
</participant_qos>
</qos_profile>
<!--FRAN: 3 new profiles for each kind of host (30jun2020)-->
<!--This implements asymmetric discovery-->
<qos_profile name="DomainParticipant.Tablet" base_name="DomainParticipant.ShmUdp">
<participant_qos>
<transport_builtin>
<mask>SHMEM</mask>
</transport_builtin>
<discovery>
<accept_unknown_peers>false</accept_unknown_peers>
<initial_peers>
<element>60@builtin.shmem://</element>
</initial_peers>
</discovery>
<property>
<value>
<element>
<name>dds.transport.shmem.builtin.host_id</name>
<value>1</value>
</element>
</value>
</property>
</participant_qos>
</qos_profile>
</qos_library>
</dds>
Executable
+45
View File
@@ -0,0 +1,45 @@
DDS Definitions
=========================================================================
Abbr | Full name
-------------------------------------------------------------------------
IDL | Interface Definition Language
-------------------------------------------------------------------------
DR | DataReader
DW | DataWriter
-------------------------------------------------------------------------
DDR | DynamicDataReader
DDW | DynamicDataWriter
-------------------------------------------------------------------------
DRO | DataReaderObject
DWO | DataWriterObject
-------------------------------------------------------------------------
DDRO | DynamicDataReaderObject
DDWO | DynamicDataWriterObject
-------------------------------------------------------------------------
Static (defined in idl)
-------------------------------------------------------------------------
DTC | DataTopicClient
DTS | DataTopicServer
-------------------------------------------------------------------------
FTC | FunctionTopicClient
FTS | FunctionTopicServer
-------------------------------------------------------------------------
Dynamic (Topic will be defined in source code)
-------------------------------------------------------------------------
DDTC | DynamicDataTopicClient
DDTS | DynamicDataTopicServer
-------------------------------------------------------------------------
DFTC | DynamicFunctionTopicClient
DFTS | DynamicFunctionTopicServer
-------------------------------------------------------------------------
Tool to publish & subscribe (to) data
-------------------------------------------------------------------------
DTCR | DataTopicContentReceiver
DTCG | DataTopicContentGenerator
-------------------------------------------------------------------------
+14
View File
@@ -0,0 +1,14 @@
#
# $Id$
# $HeadURL$
# $Author$
#
ADD_INTERFACE_LIBRARY(
routing_plugin
${CMAKE_CURRENT_SOURCE_DIR}/routing_plugin.h
${CMAKE_CURRENT_SOURCE_DIR}/../..
ddsgen
ddsQoS
NGLLogger
)
+363
View File
@@ -0,0 +1,363 @@
#include <dds/pub/DataWriter.hpp>
#include <dds/pub/discovery.hpp>
#include <dds/sub/DataReader.hpp>
#include <dds/sub/DataReaderListener.hpp>
#include <dds/topic/find.hpp>
#include <dds/topic/Topic.hpp>
#include <DDS/Generic/generic_base.h>
#include <DDS/QosProfile/DDS_DefaultQos.h>
#include <NGLLogger/INGLLogger.h>
namespace DebugInfo
{
static inline QByteArray toReadable(const rti::core::Guid &guid)
{
return QByteArray(reinterpret_cast<const char *>(&guid[0]), 16).toHex();
}
static inline QByteArray toReadable(const rti::core::SequenceNumber &seqNo,
const rti::core::Guid &guid)
{
return "SeqNo: " + QByteArray::number(seqNo.value()) + ", Guid-DW: " + toReadable(guid);
}
static inline QByteArray toReadable(const rti::core::SampleIdentity &identiy)
{
return toReadable(identiy.sequence_number(), identiy.writer_guid());
}
}
namespace FTS
{
template<typename GENERIC_FTS>
struct TopicNames;
}
namespace FTC
{
template<typename GENERIC_FTC>
struct TopicNames;
}
namespace RoutingService
{
template<typename T>
static dds::topic::Topic<T> getTopic(const dds::domain::DomainParticipant &participant,
const std::string &topicName)
{
using DataTopic = dds::topic::Topic<T>;
auto ret = dds::topic::find<DataTopic>(participant, topicName);
if (ret != nullptr)
return ret;
return DataTopic(participant, topicName);
}
class SampleWriterInterface
{
public:
SampleWriterInterface() = default;
virtual ~SampleWriterInterface() = default;
virtual void writeSamples() = 0;
virtual NGL::INGLLogger &logger() = 0;
};
class DataWriterTaskInterface
{
public:
DataWriterTaskInterface() = default;
virtual ~DataWriterTaskInterface() = default;
virtual void enqueue(SampleWriterInterface *swi) = 0;
};
class TopicDispatcherInterface
{
public:
TopicDispatcherInterface() = default;
virtual ~TopicDispatcherInterface() = default;
virtual bool init(DataWriterTaskInterface *dwt,
const dds::domain::DomainParticipant &dpIn,
const dds::sub::Subscriber &subIn,
const dds::domain::DomainParticipant &dpOut,
const dds::pub::Publisher &pubOut) = 0;
virtual void shutdown() = 0;
};
template<typename TOPIC>
class SampleWriter final : public SampleWriterInterface
{
public:
SampleWriter(dds::sub::LoanedSamples<TOPIC> &&samples,
const dds::pub::DataWriter<TOPIC> &dataWriter,
bool bWithOriginalIdentity,
NGL::INGLLogger &logger)
: m_loanedSamples(std::move(samples))
, m_dataWriter(dataWriter)
, m_bWithOriginalIdentity(bWithOriginalIdentity)
, m_logger(logger)
{}
rti::core::SampleIdentity getIdentity(const rti::core::SampleIdentity &identity) const
{
static const auto s_unknownSequenceNumber(rti::core::SequenceNumber::unknown());
static const auto s_unknownGui(rti::core::Guid::unknown());
static const auto s_unknownSampleIdentity(rti::core::SampleIdentity::unknown());
if (!m_bWithOriginalIdentity)
return s_unknownSampleIdentity;
if (identity.sequence_number() == s_unknownSequenceNumber)
return s_unknownSampleIdentity;
if (identity.writer_guid() == s_unknownGui)
return s_unknownSampleIdentity;
return identity;
}
void writeSamples() override
{
TRACE_LOG(m_logger) << "Writing samples for " << DDS::topic_type_name<TOPIC>::value()
<< ", sample count: " << m_loanedSamples.length();
rti::pub::WriteParams writeParams;
for (const auto &sample : m_loanedSamples)
{
try
{
writeParams.identity(getIdentity(sample.info()->original_publication_virtual_sample_identity()));
writeParams.related_sample_identity(getIdentity(sample.info()->related_original_publication_virtual_sample_identity()));
writeParams.handle(sample.info().instance_handle());
writeParams.flag(sample.info()->flag());
TRACE_LOG(m_logger) << "identity: " << DebugInfo::toReadable(writeParams.identity())
<< ", related_sample_identity: " << DebugInfo::toReadable(writeParams.related_sample_identity());
const auto &state = sample.info().state();
const dds::sub::status::InstanceState &instanceState = state.instance_state();
if (instanceState == dds::sub::status::InstanceState::alive())
{
m_dataWriter->write(sample.data(), writeParams);
}
else if (instanceState == dds::sub::status::InstanceState::not_alive_disposed())
{
TRACE_LOG(m_logger) << " - is disposed";
m_dataWriter->dispose_instance(writeParams);
}
else if (instanceState == dds::sub::status::InstanceState::not_alive_no_writers())
{
TRACE_LOG(m_logger) << " - is unregistered";
m_dataWriter->unregister_instance(writeParams);
}
else
WARNING_LOG(m_logger) << "Unknown instance state: " << instanceState;
}
catch (const dds::core::Exception &ex)
{
ERROR_LOG(m_logger) << "writeSamples exception: " << ex.what();
}
}
m_dataWriter->flush();
}
NGL::INGLLogger &logger() override { return m_logger; }
private:
dds::sub::LoanedSamples<TOPIC> m_loanedSamples;
dds::pub::DataWriter<TOPIC> m_dataWriter;
bool m_bWithOriginalIdentity;
NGL::INGLLogger &m_logger;
};
template<typename T>
class DataTopicListener final : public dds::sub::DataReaderListener<T>
{
public:
using DataReader = dds::sub::DataReader<T>;
DataTopicListener(DataWriterTaskInterface *dwt,
const std::string &topicName,
DDS::QosProfile::TopicType topicType,
NGL::INGLLogger &logger)
: m_dwt(dwt)
, m_dataWriter(nullptr)
, m_topicName(topicName)
, m_topicType(topicType)
, m_logger(logger)
{}
bool init(const dds::domain::DomainParticipant &dpOut,
const dds::pub::Publisher &pubOut)
{
if (!m_dwt)
return false;
DEBUG_LOG(m_logger) << "Creating DataWriter for domain " << dpOut.domain_id();
m_dataWriter = dds::pub::DataWriter<T>(pubOut,
getTopic<T>(dpOut, m_topicName),
DDS::QosProfile::dataWriterDefault(m_topicType));
dds::domain::DomainParticipant &dpOutNonConst = const_cast<dds::domain::DomainParticipant &>(dpOut);
dds::pub::ignore(dpOutNonConst, m_dataWriter.instance_handle());
return true;
}
void on_data_available(dds::sub::DataReader<T> &reader) override
{
TRACE_LOG(m_logger) << "Data available for " << DDS::topic_type_name<T>::value();
auto ws = new SampleWriter<T>(reader.take(), m_dataWriter, m_topicType == DDS::QosProfile::TopicType::FunctionTopic, m_logger);
m_dwt->enqueue(ws);
}
void on_requested_deadline_missed(dds::sub::DataReader<T> &, const dds::core::status::RequestedDeadlineMissedStatus &) override {}
void on_requested_incompatible_qos(dds::sub::DataReader<T> &,
const dds::core::status::RequestedIncompatibleQosStatus &) override {}
void on_sample_rejected(dds::sub::DataReader<T> &,
const dds::core::status::SampleRejectedStatus &) override {}
void on_liveliness_changed(dds::sub::DataReader<T> &,
const dds::core::status::LivelinessChangedStatus &) override {}
void on_subscription_matched(dds::sub::DataReader<T> &,
const dds::core::status::SubscriptionMatchedStatus &) override {}
void on_sample_lost(dds::sub::DataReader<T> &,
const dds::core::status::SampleLostStatus &) override {}
static dds::core::status::StatusMask mask()
{
return (dds::core::status::StatusMask::data_available());
}
protected:
DataWriterTaskInterface *m_dwt;
dds::pub::DataWriter<T> m_dataWriter;
std::string m_topicName;
DDS::QosProfile::TopicType m_topicType;
NGL::INGLLogger &m_logger;
};
template<typename TOPIC>
class TopicDispatcher : public TopicDispatcherInterface
{
protected:
explicit TopicDispatcher(NGL::INGLLogger &logger, const std::string &topicName, DDS::QosProfile::TopicType topicType)
: m_dataReader(nullptr)
, m_topicName(topicName)
, m_topicType(topicType)
, m_logger(logger)
{
}
~TopicDispatcher()
{
shutdown();
}
bool init(DataWriterTaskInterface *dwt,
const dds::domain::DomainParticipant &dpIn,
const dds::sub::Subscriber &subIn,
const dds::domain::DomainParticipant &dpOut,
const dds::pub::Publisher &pubOut) override
{
DEBUG_LOG(m_logger) << "Initializing TopicDispatcher " << "(" << this << ")";
if (m_dataReader != nullptr)
return false;
m_dataReader = dds::sub::DataReader<TOPIC>(subIn,
getTopic<TOPIC>(dpIn, m_topicName),
DDS::QosProfile::dataReaderDefault(m_topicType));
DEBUG_LOG(m_logger) << "Creating DataReader for domain " << dpIn.domain_id() << ", ptr: " << m_dataReader.delegate().get();
m_dtl.reset(new DataTopicListener<TOPIC>(dwt, m_topicName, m_topicType, m_logger));
if (!m_dtl->init(dpOut, pubOut))
return false;
m_dataReader.listener(m_dtl.get(), DataTopicListener<TOPIC>::mask());
return true;
}
void shutdown() override
{
DEBUG_LOG(m_logger) << "Cleaning up TopicDispatcher " << "(" << this << ")";
if (m_dataReader != nullptr)
{
DEBUG_LOG(m_logger) << "shutting down DataReader: " << m_dataReader.delegate().get();
m_dataReader.listener(nullptr, dds::core::status::StatusMask::none());
try
{
m_dataReader.close();
}
catch (const dds::core::Exception &ex)
{
ERROR_LOG(m_logger) << "topic close ex: " << ex.what();
}
}
m_dtl = nullptr;
m_dataReader = nullptr;
}
private:
dds::sub::DataReader<TOPIC> m_dataReader;
std::unique_ptr<DataTopicListener<TOPIC>> m_dtl;
std::string m_topicName;
DDS::QosProfile::TopicType m_topicType;
NGL::INGLLogger &m_logger;
};
template<typename TOPIC>
class DataTopicDispatcher final : public TopicDispatcher<TOPIC>
{
public:
explicit DataTopicDispatcher(NGL::INGLLogger &logger)
: TopicDispatcher<TOPIC>(logger,
DDS::Helper::getShortTopicName(dds::topic::topic_type_name<TOPIC>::value()),
DDS::QosProfile::TopicType::DataTopic)
{}
};
template<typename FTS_TOPIC>
class FunctionTopicReplyDispatcher final : public TopicDispatcher<typename FTS_TOPIC::ReplyTopic>
{
public:
explicit FunctionTopicReplyDispatcher(NGL::INGLLogger &logger)
: TopicDispatcher<typename FTS_TOPIC::ReplyTopic>(logger,
FTS::TopicNames<FTS_TOPIC>::replyTopicName(),
DDS::QosProfile::TopicType::FunctionTopic)
{}
};
template<typename FTC_TOPIC>
class FunctionTopicRequestDispatcher final : public TopicDispatcher<typename FTC_TOPIC::RequestTopic>
{
public:
explicit FunctionTopicRequestDispatcher(NGL::INGLLogger &logger)
: TopicDispatcher<typename FTC_TOPIC::RequestTopic>(logger,
FTC::TopicNames<FTC_TOPIC>::requestTopicName(),
DDS::QosProfile::TopicType::FunctionTopic)
{}
};
}