Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

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>
+53
View File
@@ -0,0 +1,53 @@
/**
* Export definitions
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-02-29
*/
#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
+52
View File
@@ -0,0 +1,52 @@
/**
* Helper class to properly fill FunctionReply objects
* $Id$
* $HeadURL$
* $Author$
*/
#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;
}
} // namespace Core
+116
View File
@@ -0,0 +1,116 @@
/**
* Helper class to properly fill FunctionReply objects
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <string>
#include <vector>
#include "core_export.h"
#ifndef tr_lts
#define tr_lts(ctx, t) (t)
#endif
namespace Core
{
/**
* @brief An object to handle replies for function requests
* It tries to simulate QString::arg() behaviour and collects all arguments.
* --> a string must contain '%1' , '%2', ... as argument placeholder like a Qt string
* which is used for translation.
* The complete object can then be passed back to the FTS reply handler which
* puts them into a Core::FunctionReply dds structure.
* On the client side, the data from the Core::FunctionReply structure is
* automatically put into the FunctionReplyObject so that the user can
* easily access the result of the function request.
* To check if there is an error, the function error() can be called.
* To get the error translated string with all arguments replaced, simply call translated()
* To get an untranslated output (e.g. for logging), use std::string operator()
*/
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;
// simulate QString::arg() behaviour
FunctionReplyObject &arg(qint64 val);
FunctionReplyObject &arg(const std::string &str);
FunctionReplyObject &arg(const QString &str);
/**
* @brief true if the object contains an error description
*/
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; }
/**
* @brief convert the error string and arguments to an untranslated std::string
* e.g. for logging output
*/
std::string operator()() const;
/**
* @brief convert and translate the error string and arguments to a translated QString
* The translation is done via QCoreApplication::translate("", errStr)
*/
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;
};
}
+37
View File
@@ -0,0 +1,37 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-02-29
*/
#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();
}
}
} // namespace Core
+19
View File
@@ -0,0 +1,19 @@
/**
* @RDE_HEADER@
* Logging for ExerciseManagement
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-02-29
*/
#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);
} // namespace ExerciseManagement
+122
View File
@@ -0,0 +1,122 @@
/**
* The Pacemaker class implements functionality to publish Service states in DDS.
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 29.02.2016
*/
#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; //No parent as we handle the thread during startTimer
this->moveToThread(m_paceThread); //move us to the new thread
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();
});
// connect(m_publishTimer, &QTimer::destroyed, m_paceThread, &QThread::deleteLater);
m_paceThread->start();
}
} // namespace Core
+62
View File
@@ -0,0 +1,62 @@
/**
* The Pacemaker class implements functionality to publish Service states in DDS.
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 29.02.2016
*/
#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(""));
// only public for special purposes, not needed to call explicitly by default
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;
};
} // namespace Core
+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;
}
} // namespace Core
+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;
};
} // namespace Core
+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
)
+287
View File
@@ -0,0 +1,287 @@
/**
* DDSConnector
* $Id$
* $HeadURL$
* $Author$
*/
#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();
// DdsInterface Publisher
setPublisher(new DDSConnector::Publisher(m_domainParticipant, getDefaultPartition()));
}
void DdsConnector::initSubscriber()
{
if (m_subscriber)
return;
initDomainParticipant();
// DdsInterface Subscriber
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
{
// set DomainParticipant name
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)); //here we overwirte the is_qos_default from the QoS XML
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);
// DomainParticipantFactory
dds::domain::qos::DomainParticipantFactoryQos domainParticipantFactoryQos = dds::domain::DomainParticipant::participant_factory_qos();
// manually enable the domain participant (e.g. to be able to set the domain participant name)
domainParticipantFactoryQos << dds::core::policy::EntityFactory::ManuallyEnable();
dds::domain::DomainParticipant::participant_factory_qos(domainParticipantFactoryQos);
}
void DdsConnector::createWaitSetHandler()
{
if (!m_wsHandler)
m_wsHandler = new DDSGeneric::WaitSetHandler;
}
} // Namespace DDSConnector
+226
View File
@@ -0,0 +1,226 @@
/**
* Initialisation DDS
* $Id$
* $HeadURL$
* $Author$
*/
#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;
/**
* partitionId is ignored for Any, Global and AllExercises
*/
DdsConnector(int domainId,
IDL::PartitionType defaultPartitionType,
int partitionId = 0,
int exerciseId = 0,
const QUrl &tcpConnectionUrl = QUrl());
/**
* @brief Create a new DdsConnector with the given domainparticipant e.g. to access other partition with same domainparticipant)
*/
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();
/**
* @brief initialize the DDSConnector internals, e.g. load QoS profiles
* @param qosFileList a custom Qos xml file (if needed)
* @return true on success
*/
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); // takes ownership
void enableBatchedWriter(bool bEnable);
Subscriber *getSubscriber() { return m_subscriber; }
dds::sub::Subscriber getDdsSubscriber() const;
void setSubscriber(Subscriber *subscriber); // takes ownership
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);
/**
* @brief default initialize publisher / Subscriber / DomainParticipant
* Using m_domainId and m_exerciseId as parameter for Publisher/Subscriber
*/
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;
};
/**
* @brief Convenience class for dds::pub::CoherentSet which takes a DdsConnector
*/
class CoherentSet
{
public:
explicit CoherentSet(const std::shared_ptr<DdsConnector> &connector)
: m_coherentSet(connector->getDdsPublisher())
{}
private:
dds::pub::CoherentSet m_coherentSet;
};
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Common functions for Publisher and Subscriber
* $Id$
* $HeadURL$
* $Author$
*/
#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))
{}
} // namespace DDSConnector
+43
View File
@@ -0,0 +1,43 @@
/**
* Common functions for Publisher and Subscriber
* $Id$
* $HeadURL$
* $Author$
*/
#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;
};
} // namespace DDSConnector
+26
View File
@@ -0,0 +1,26 @@
/**
* Common functions for Publisher and Subscriber
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
namespace DDSConnector
{
template<typename P>
void PubSubBase::updatePartitionQoS(P &pubSub)
{
if (pubSub == nullptr)
return;
auto publisherQos = pubSub.qos();
// use only selected partition
dds::core::policy::Partition partitionQos;
partitionQos.name( m_partitions );
publisherQos << partitionQos;
pubSub.qos(publisherQos);
}
} // namespace DDSConnector
+114
View File
@@ -0,0 +1,114 @@
/**
* @RDE_HEADER@
*
* Publisher
* Uses DTSs to publish data
*
* $Id$
* $HeadURL$
* $Author$
*/
#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;
// some debug output
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();
// set a name for rtiadminconsole
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); // TODO: StatusMask auf NONE setzen?
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); // 64kb
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;
}
} // Namespace DDSConnector
+75
View File
@@ -0,0 +1,75 @@
/**
* @RDE_HEADER@
*
* Publisher for DataSampleGeneration
* Uses DTSs to publish data
*
* $Id$
* $HeadURL$
* $Author$
*/
#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; }
// Write data for each topicType (i.e. T = Lts_ExerciseParticipant)
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);
}
// Use default typeName, if topicName is not specified
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;
// Map for static DTcs
QMap<std::string, DTS::Base*> m_dtsMap;
// Map for dynamic types
// QMap<topicName,DataWriter (defined)>
QMap<std::string, dds::pub::AnyDataWriter*> m_dynamicTypeMap;
};
}
+82
View File
@@ -0,0 +1,82 @@
/**
* @RDE_HEADER@
*
* Subscriber (DDSConnector)
* Uses DTCs/DDTCs to subscribe to data
*
* $Id$
* $HeadURL$
* $Author$
*/
#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;
// some debug output
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();
// set a name for rtiadminconsole
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; // will be deleted due to bind_listener
}
m_subscriberListenerBinder = nullptr;
m_ddsSubscriber = nullptr;
}
} // Namespace DDSConnector
+79
View File
@@ -0,0 +1,79 @@
/**
* @RDE_HEADER@
*
* Subscriber (Common)
* Uses DDTCs to subscribe to data
*
* $Id$
* $HeadURL$
* $Author$
*/
#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;
}
// Create new ddtc object
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})
+71
View File
@@ -0,0 +1,71 @@
/**
* @RDE_HEADER@
* DTC TypeDefinitions
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <LtsUtilsCore/data_modification_enum.h>
#include <dds/sub/LoanedSamples.hpp>
namespace NGLLogger
{
class INGLLogger;
}
namespace DTC
{
class SubscriberListener;
// uses DRO (static)
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;
};
// Typed topic callback (DataReader)
template<typename DRO_OBJECT>
class GenericDtc
: public GenericDtcSC<DRO_OBJECT,
std::function<void(const typename DRO_OBJECT::TopicType &, DataModification)> >
{};
// Typed topic callback (DataReader) with SampleInfo
template<typename DRO_OBJECT>
class GenericDtcSampleInfo
: public GenericDtcSC<DRO_OBJECT,
std::function<void(const typename DRO_OBJECT::TopicType &, const dds::sub::SampleInfo &)> >
{};
// Dynamic topic callback (DynamicDataReader)
template<typename DRO_OBJECT>
class Ddtc
: public GenericDtcSC<DRO_OBJECT,
std::function<void(const typename DRO_OBJECT::TopicType &, const std::string &, DataModification)> >
{};
// Dynamic topic callback (DynamicDataReader) with SampleInfo
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 &)> >
{};
}
+318
View File
@@ -0,0 +1,318 @@
/**
* @RDE_HEADER@
* DataTopicClient (DTC) - Generic
* $Id$
* $HeadURL$
* $Author$
*/
#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));
// empty sequence -> timeout
if (seq.empty())
return false;
}
dr.wait_for_historical_data(dds::core::Duration(1));
}
catch (const dds::core::Exception &)
{
// timeout
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;
// DataReaderObjects (DROs)
DataReaderObject *m_dataReaderObject;
// Callback for data processing
SubscriberCallback m_subscriberCallback;
QAtomicInt m_writerState;
NGL::INGLLogger &m_logger;
};
// Typed topic callback (DataReader)
template<typename DRO_OBJECT>
class GenericDtcImpl final
: public GenericDtcImplSubscriberTemplate<GenericDtc<DRO_OBJECT>>
{
using BaseClass = GenericDtcImplSubscriberTemplate<GenericDtc<DRO_OBJECT>>;
public:
using BaseClass::BaseClass;
};
// Typed topic callback (DataReader) with SampleInfo
template<typename DRO_OBJECT>
class GenericDtcImplSampleInfo final
: public GenericDtcImplSubscriberTemplate<GenericDtcSampleInfo<DRO_OBJECT>>
{
using BaseClass = GenericDtcImplSubscriberTemplate<GenericDtcSampleInfo<DRO_OBJECT>>;
public:
using BaseClass::BaseClass;
};
// Dynamic topic callback (DynamicDataReader)
template<typename DRO_OBJECT>
class DdtcImpl final
: public GenericDtcImplSubscriberTemplate<Ddtc<DRO_OBJECT>>
{
using BaseClass = GenericDtcImplSubscriberTemplate<Ddtc<DRO_OBJECT>>;
public:
using BaseClass::BaseClass;
};
// Dynamic topic callback (DynamicDataReader) with SampleInfo
template<typename DRO_OBJECT>
class DdtcImplSampleInfo final
: public GenericDtcImplSubscriberTemplate<DdtcSampleInfo<DRO_OBJECT>>
{
using BaseClass = GenericDtcImplSubscriberTemplate<DdtcSampleInfo<DRO_OBJECT>>;
public:
using BaseClass::BaseClass;
};
} // namespace DTC
+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})
+34
View File
@@ -0,0 +1,34 @@
/**
* @RDE_HEADER@
* Interface for DataTopicServers (DTSs) - generic
* $Id$
* $HeadURL$
* $Author$
*/
#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;
};
}
+93
View File
@@ -0,0 +1,93 @@
/**
* @RDE_HEADER@
* DataTopicServers (DTSs) - generic
* $Id$
* $HeadURL$
* $Author$
*/
#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:
// DataWriterObjects (DWOs)
DataWriterObject *m_dataWriterObject;
NGL::INGLLogger &m_logger;
};
} // Namespace DTS
+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()
+167
View File
@@ -0,0 +1,167 @@
/**
* @RDE_HEADER@
* DataTopic Unit test for DDS
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-07-24
*/
#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()
{
// Publisher
delete m_testPart;
// Subscriber
delete m_dtcTestPart;
// SubscriberListener is used by m_ddsSubscriber
// => needs to be deleted AFTER delete_contained_entities (which destroys the ddsSubscriber)
delete m_subscriberListener;
}
/**
*
* Publisher
*
**/
bool DataTopicTests::initPublisher()
{
m_domainParticipantPub =
dds::domain::DomainParticipant(m_domainId,
DDS::QosProfile::domainParticipantShm());
dds::pub::qos::PublisherQos publisherQos = m_domainParticipantPub.default_publisher_qos();
// use only selected partition
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);
}
/**
*
* Subscriber
*
**/
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();
// Listening for Topics in partition 1
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());
// Initialise DTC for Topic TestParticipant
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();
}
+72
View File
@@ -0,0 +1,72 @@
/**
* @RDE_HEADER@
* Unit test for DataTopic Server & Client (DTS & DTC)
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-07-24
*/
#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();
// Initialisation
bool initPublisher();
bool initSubscriber();
// Publisher
bool write(const DdsUnitTests::TestParticipant &data);
// Subscriber
DdsUnitTests::TestParticipant getReceivedParticipantData() const { return m_receivedParticipantData; }
void deleteDTC();
void deleteSubscriber();
protected:
// Subscriber Methods
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;
// Publisher
dds::pub::Publisher m_ddsPublisher;
DTS::TestParticipant *m_testPart;
// Subscriber
dds::sub::Subscriber m_ddsSubscriber;
DDSGeneric::SubscriberListener *m_subscriberListener;
DTC::SampleInfo::TestParticipant *m_dtcTestPart;
Qt::HANDLE m_mainThreadId;
// For returning TestData
DdsUnitTests::TestParticipant m_receivedParticipantData;
NGL::INGLLogger& m_logger;
};
+108
View File
@@ -0,0 +1,108 @@
/**
* @RDE_HEADER@
* Unit test for DDS
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-07-24
*/
#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";
// Topic 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;
}
+44
View File
@@ -0,0 +1,44 @@
/**
* @RDE_HEADER@
* Unit test for DataTopics
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-07-24
*/
#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>
+28
View File
@@ -0,0 +1,28 @@
/**
* Export definitions
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-02-29
*/
#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
+261
View File
@@ -0,0 +1,261 @@
/**
* Unit test for function topic server / client (fts/ftc)
* $Id$
* $HeadURL$
* $Author$
*/
#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()
{
// request two times to check if WaitSetHandler etc. is working correct
requestWithState();
// 3 conditions - abortCondition + FunctionRequest + FunctionRequestWithState
QCOMPARE(m_ddsConnectorServer->getWaitSetHandler()->attachedConditionCount(), 3);
requestWithState();
// 3 conditions - abortCondition + FunctionRequest + FunctionRequestWithState
QCOMPARE(m_ddsConnectorServer->getWaitSetHandler()->attachedConditionCount(), 3);
}
//-------------------------------------------------------------------------------------------------------
void FunctionTopicTests::requestWithState()
{
// @StartTime => catch all signals (timing)
QSignalSpy spyRequest(this, &FunctionTopicTests::functionTopicRequestFinished);
QSignalSpy spyReply(this, &FunctionTopicTests::functionTopicReply);
DEBUG_LOG(m_logger) <<"FunctionTopicTests: request";
// Topic
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); // make sure the signal was emitted exactly one time
QTRY_COMPARE(spyReply.count(), 2); // make sure the signal was emitted exactly two times (reply in progress/reply finished)
}
//-------------------------------------------------------------------------------------------------------
void FunctionTopicTests::requestTestCase2()
{
// Start@beginning => all signals need catching (timing)
QSignalSpy spyRequest(this, &FunctionTopicTests::functionTopicRequestFinished);
QSignalSpy spyReply(this, &FunctionTopicTests::functionTopicReply);
DEBUG_LOG(m_logger) <<"FunctionTopicTests: request";
// Topic
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); // make sure the signal was emitted exactly one time
QTRY_COMPARE(spyReply.count(), 1); // make sure the signal was emitted exactly one time (no status member)
}
//-------------------------------------------------------------------------------------------------------
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)));
}
+77
View File
@@ -0,0 +1,77 @@
/**
* Unit test for function topic server / client (fts/ftc)
* $Id$
* $HeadURL$
* $Author$
*/
#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:
// client
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);
// server
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:
// server
std::unique_ptr<FTS::FunctionTopicTest> m_fts;
std::unique_ptr<FTS::FunctionTopicTestWithState> m_ftsWS;
// client
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;
};
+230
View File
@@ -0,0 +1,230 @@
/**
* Unit test for DDS/Generic (Writer/Reader)
* $Id$
* $HeadURL$
* $Author$
*/
#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");
// Callback fuer Listener setzen
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";
// Topic SpatialData
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";
// SpatialData
QTRY_COMPARE(m_spatialData.equipNo(), 999);
QCOMPARE(m_spatialData.changedAt(), 11111);
// ParticipantInExercise
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); } );
}
}
+76
View File
@@ -0,0 +1,76 @@
/**
* @RDE_HEADER@
* Unit test for DDS/Generic
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-07-24
*/
#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;
};
+103
View File
@@ -0,0 +1,103 @@
/**
* @RDE_HEADER@
* DataTopic Unit test for DDS
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-07-24
*/
#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());
// SpatialData will be published to Partition $partitionId
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);
// use only selected partition
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);
// use only selected partition
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;
}
+53
View File
@@ -0,0 +1,53 @@
/**
* @RDE_HEADER@
* Unit test for DataTopic Server & Client (DTS & DTC)
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-07-24
*/
#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
};
// Initialisation
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
{
}
+33
View File
@@ -0,0 +1,33 @@
/**
* @RDE_HEADER@
* FTC GenericFTC_Interface
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <DDS/Core/core.hpp>
#include <future>
class Request2;
namespace FTC
{
// uses FTC (static)
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;
};
} // namespace FTC
+577
View File
@@ -0,0 +1,577 @@
#pragma once
/**
* @RDE_HEADER@
* Generic Function Topic Client (FTC Requester)
* $Id$
* $HeadURL$
* $Author$
*/
#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))
{
// fprintf(stderr, "New ftc for %s @ %s %d\n", TopicNameHelper::replyTopicTypeName(), m_partitionType.c_str(), m_partitionId);
Q_ASSERT(domainParticipant != nullptr);
Q_ASSERT(ddsPublisher != nullptr);
Q_ASSERT(ddsSubscriber != nullptr);
Q_ASSERT(objToInform != nullptr);
//m_requesterParams.service_name("FunctionTopic");
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); // steal & throw away
modernRequests.swap(m_modernRequests); // steal & throw away
}
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 /* direct connection */);
}
}
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; // in-thread!
void cleanupThread() override // in-thread!
{
// called in-thread while thread is preparing to immediately shut down (after this call) and cleans everything that was created also in-thread
requester.reset();
}
void threadDone() override
{
// called after the thread is done and before any new threads may be created
// that's why there's no need to lock the mutex here
// NOTE: not called on ctor!
// NOTE: no need to lock as there is no thread anymore at this point
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 /* direct connection */);
}
}
void work() override // in-thread!
{
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); // NOTE: no need to lock as we're inside the thread and it is guaranteed that we don't hit the condition wait while writing to the variable
// lock
{
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)
{
// ALTER KOMMENTAR - WICHTIG FUER REGRESS-TESTS!:
// related to NLDEVCTRL-844: NASAT: Weiterleitung der Luftunterstützung sporadisch ausser Funktion
// Bei der Simulation von eingehenden NasatWeaponRelease bzw. AreaEffectSimulation:
// :AirSupportImpactModifyREQUEST war zu erkennen, das der folgende WorkerThread
// per lazyInit den connext::Requester erzeugt, dabei aber scheinbar hängen bleibt.
// Jedenfalls bleibt nachfolgende LogMsg aus. Es liess sich nicht besser debuggen.
// Daher wird das connext::Requester bereits hier im mainThread erzeugt.
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());
// QMetaObject::invokeMethod(request.requestWatcher, &Request2::finished, Qt::QueuedConnection /* because in-thread */);
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> &)
{
// no reply.status() -> exactly one reply topic
return false;
}
/**
* @brief
* Waits for the requested data (from FTS) via the reply topic
* After checking on validity of the data the data will be returned the the caller via reply slot
*/
void getRequestedDataClassic(connext::Requester<RequestTopic, ReplyTopic> &requester, ClassicRequest &request)
{
// Receive replies
#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;
// When receive_replies times out,it returns an empty reply collection
if (!requester.receive_reply(reply, MAX_WAIT))
{
WARNING_LOG(m_logger) << "Timed out waiting for reply";
sendError(request, 3);
return;
}
// Send replyData via reply-slot
if (reply.info().valid())
{
if (request.sample.info().identity() != reply.info()->related_original_publication_virtual_sample_identity())
{
// maybe an old, outdated answer from server (with a wrong seqNo) - ignore it
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();
// QueuedConnection to make sure the data is handled in the correct Qt (main) thread
request.replySlot.invoke(m_objToInform, Qt::QueuedConnection,
QArgument<ReplyTopic>(TopicNameHelper::replyTopicTypeName(), reply.data()), // need to explicitly specify the registered typename
Q_ARG(int, request.uniqueId),
Q_ARG(bool, !inProgress)); // finished yes/no
}
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 ";
/* No need to call replies.return_loan();
* the destructor takes care of doing it every time replies goes out of scope
*/
}
}
// -------------------------------------------------------------------------------------------------------------
void getModernRequestedData(connext::Requester<RequestTopic, ReplyTopic> &requester, ModernRequest<ReplyTopic> &request) noexcept(false)
{
// Receive replies
#ifdef WIN32
const dds::core::Duration MAX_WAIT(200);
#else
const dds::core::Duration MAX_WAIT(20);
#endif
while (isRun())
{
connext::Sample<ReplyTopic> reply;
// When receive_replies times out,it returns an empty reply collection
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!");
}
//cancellationPoint();
if (!reply.info().valid())
{
WARNING_LOG(m_logger) << "No valid data available!";
// TODO: good? bad? neutral???
throw std::runtime_error("No valid data available!");
}
if (request.sample.info().identity() != reply.info()->related_original_publication_virtual_sample_identity())
{
// maybe an old, outdated answer from server (with a wrong seqNo) - ignore it
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());
// QMetaObject::invokeMethod(request.requestWatcher, &Request2::finished, Qt::QueuedConnection /* because in-thread */);
QTimer::singleShot(0, request.requestWatcher, &Request2::finished);
return;
/* No need to call replies.return_loan();
* the destructor takes care of doing it every time replies goes out of scope
*/
}
}
// -------------------------------------------------------------------------------------------------------------
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;
}
// -------------------------------------------------------------------------------------------------------------
// for classic requests:
QObject *m_objToInform;
int m_uniqueId { 0 };
// protected by mutex:
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;
};
} // namespace FTC
+330
View File
@@ -0,0 +1,330 @@
#include "generic_ftc_threading.h"
#include <QAtomicInt>
#include <QTimer>
//#include <QRandomGenerator>
#include <QRegularExpression>
#include <chrono>
#include <future>
#ifdef Q_OS_LINUX
#include <pthread.h>
#endif
//using namespace std::chrono_literals;
Threading::~Threading()
{
if (m_threadLifetime != nullptr)
{
m_threadLifetime->stop();
m_threadLifetime.reset();
}
setCleanupNeeded(false); // dtor will clean everything up itself
{
std::lock_guard<std::mutex> l(m_conditionMutex);
setRun(false);
}
if (!m_used)
{
//fprintf(stderr, "This FTC (%s @ %d) was enabled without ever doing a request! Consider calling 'enable' right before doing a request only!\n", m_threadName.toStdString().c_str(), m_partitionId);
}
// fprintf(stderr, "~Threading: joinable? %d (%s)\n", m_thread.joinable(), m_threadName.toStdString().c_str());
if (m_thread.joinable())
{
// fprintf(stderr, "~Threading: joining... (%s)\n", m_threadName.toStdString().c_str());
m_condition.notify_all(); // wake thread from sleeping
m_thread.join(); // wait for thread to die... :(
// fprintf(stderr, "~Threading: joined (%s)\n", m_threadName.toStdString().c_str());
}
}
void Threading::preInit() const
{
m_partitionId = partitionId();
m_partitionType = partitionType();
//fprintf(stderr, "functionTopicName() = %s\n", functionTopicName().toStdString().c_str());
// F = FTC; %1 = partition type; %2 partition id; %3 = function topic name; %4 = thread id
const QString base(QStringLiteral("F%1%2").arg(m_partitionType.left(1)).arg(m_partitionId));
//fprintf(stderr, "base = %s\n", base.toStdString().c_str());
const int baseLen = base.length();
const int maxLen = 16 - 1 - baseLen; // 16 chars allowed; 1 char = 0; base already n chars
// fprintf(stderr, "baseLen = %d\n", baseLen);
// fprintf(stderr, "maxLen = %d\n", maxLen);
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());
// fprintf(stderr, "word = %s\n", word.toStdString().c_str());
words += word;
}
if (words.isEmpty())
{
Q_ASSERT(false);
m_threadName = functionTopicName();
return;
}
int wordsLeft = words.count();
// fprintf(stderr, "wordsLeft = %d\n", wordsLeft);
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;
// fprintf(stderr, "maxWordLen = %d\n", maxWordLen);
// const int rest = lenRest % wordsLeft;
// fprintf(stderr, "rest = %d\n", rest);
if (words.isEmpty())
{
Q_ASSERT(false);
break;
}
// name += words.takeFirst().left(maxWordLen); // clazy: use leftRef
name += words.takeFirst().leftRef(maxWordLen);
wordsLeft--;
}
// fprintf(stderr, "name = %s\n", name.toStdString().c_str());
const int finalLeft = maxLen - name.count();
if (finalLeft < 0 || (finalLeft > 0 && functionTopicName().length() > maxLen))
{
// fprintf(stderr, "[#] Threading::preInit: (%s) maxLen - name.count() = %d - functionTopicName().length() = %d, maxLen = %d\n", name.toStdString().c_str(), maxLen - name.count(), functionTopicName().length(), maxLen);
}
}
else
{
name = orgName;
}
/*mutable*/m_threadName = QStringLiteral("%1%2[%3]").arg(base, name).arg(qrand(), 8, 16, QLatin1Char('0'));
}
const QString &Threading::threadName() const
{
if (m_threadName.isEmpty())
{
// fprintf(stderr, "[!] Threading: threadName called before preInit!\n");
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()) // dying thread still alive? then wait for it to stop :(
{
// fprintf(stderr, "Threading::go: joining... (%s)\n", threadName().toStdString().c_str());
m_condition.notify_all(); // wake from (possible) sleep
m_thread.join();
threadDone();
// fprintf(stderr, "Threading::go: thread exited (%s)\n", threadName().toStdString().c_str());
}
m_alreadyThreaded = 0;
// fprintf(stderr, "Threading::go: creating new thread (%s)\n", threadName().toStdString().c_str());
setRun(true); // NOTE: no need to lock before the thread even exists
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(); // wake thread from possible sleep
if (!m_threadLifetime) // limit thread's lifetime for saving resources, but keep it alive as long as possible to not re-create a new thread for each request
{
m_threadLifetime.reset(new QTimer); // 42.3
m_threadLifetime->setSingleShot(true);
m_threadLifetime->setInterval(11*1000); // 10 seconds + 1 to make it an ugly, undesireable number - for e.g. requests that may be timed to execute all 10 seconds
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)
{
// fprintf(stderr, "Threading::go: thread lifetime exceeded, but thread still has work to do - trying again later (%s)\n", threadName().toStdString().c_str());
m_threadLifetime->start(); // re-start
return;
}
m_threadLifetime->deleteLater();
m_threadLifetime = nullptr;
if (!run)
{
// fprintf(stderr, "[!] Threading::go: thread lifetime exceeded while having no work, but run is already false... doing nothing (%s)\n", threadName().toStdString().c_str());
Q_ASSERT(false);
return;
}
// fprintf(stderr, "Threading::go: thread lifetime exceeded while having no work (%s)\n", threadName().toStdString().c_str());
{
std::lock_guard<std::mutex> l(m_conditionMutex);
setRun(false);
}
m_condition.notify_all(); // wake thread from possible sleep, so it can stop
});
}
m_threadLifetime->start(); // start / re-start
}
void Threading::run(const QString &threadName) // in-thread
{
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);
// fprintf(stderr, "Threading::run: waiting... (%s)\n", tn);
m_condition.wait(conditionLock, [this]{ return (isWorkAvailable() || !isRun()); /* wake up if work available or the thread has to die */ });
// fprintf(stderr, "Threading::run: woke up - work? %d - run? %d (%s)\n", isWorkAvailable(), isRun(), tn);
}
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)
{
// fprintf(stderr, "Threading::run: CancelationPointReached (%s)\n", tn);
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);
}
} // while
if (isCleanupNeeded())
{
// fprintf(stderr, "Threading::run: cleanupThread next! (%s)\n", tn);
cleanupThread();
}
else
{
// fprintf(stderr, "Threading::run: no cleanup needed! (%s)\n", tn);
}
}
+98
View File
@@ -0,0 +1,98 @@
#pragma once
/**
* @RDE_HEADER@
* Thread management for generic FTCs
* $Id: generic_ftc_impl.h 62388 2024-03-27 10:41:26Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/features/swp_2023_devel/Lts/DDS/FTC/generic_ftc_impl.h $
* $Author: wille_m $
*
* Created by Mike Wille, ESZS, 27-03-2024
*/
#include <QObject>
#include <QAtomicInt>
#include <QTimer>
#include <thread>
#include <exception>
#include <condition_variable>
class CancelationPointReached : public std::runtime_error
{
public:
//using std::runtime_error::runtime_error;
explicit CancelationPointReached()
: std::runtime_error("CancelationPointReached")
{}
};
class Threading // : public QObject
{
//Q_OBJECT
public:
Threading() = default;
~Threading();
protected:
void preInit() const;
// managing thread:
void createThread(); // creates a thread if not already running - run before adding requests
void runThread(); // notify thread
// m_conditionMutex
inline bool isRun() const { return (m_run != 0); }
inline void setRun(bool run) { m_run = (run ? 1 : 0); }
// m_conditionMutex
inline void setWorkAvailable(bool workAvailable) { m_workAvailable = (workAvailable ? 1 : 0); }
inline bool isWorkAvailable() const { return (m_workAvailable != 0); }
// m_conditionMutex
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; // in-thread!
virtual void threadDone() = 0;
inline void cancellationPoint() const noexcept(false) // in-thread
{
if (!isRun())
{
// fprintf(stderr, "Threading: cancelation point reached\n");
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); // in-thread
//void stop();
std::unique_ptr<QTimer> m_threadLifetime;
QAtomicInt m_run { 0 };
QAtomicInt m_cleanupNeeded { 1 };
std::thread m_thread; // invalid / not a 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
)
+506
View File
@@ -0,0 +1,506 @@
/**
* Nachbildung der alten connext Request/Reply-API
* $Id$
* $HeadURL$
* $Author$
*/
#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>
// for debugging only
#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());
}
}
// return dds::core::Topic<FOO>, check if topic already exists, create it otherwise
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)
{
// nothing changed and content filter already created -> nothing to do
if (!bNewDataWriter && m_contentFilteredTopic != nullptr)
return;
// create content filter
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)
{
// this will not work if filterName is already used somewhere else. But could this really happen?
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
{
// rti extension
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
{
// WARNING_LOG(m_logger) << "Trying to sent a new request but old is not finished yet!";
}
create_contentfilter(bNewDataWriter);
// first run
if (m_dataReader == nullptr)
{
// create data reader with appropriate content filter
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)
{
// TODO: reply.info() wird in connext_cpp mittels init_sample_info() gefüllt -> nachschauen was da alles wie gesetzt wird
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());
// it's the correct reply due to content filter
bRet = true;
break;
}
dds::core::cond::WaitSet::ConditionSeq activeConditionsSeq = waitset.wait(duration);
if (activeConditionsSeq.empty())
{
ERROR_LOG(m_logger) << "Bailing out!";
break;
}
}
// intermediate_reply_sequence is set when it's not the last one
static const rti::core::SampleFlag irs(rti::core::SampleFlag::intermediate_reply_sequence());
if ((reply.info()->flag() & irs) == 0)
{
if (!m_curInstanceHandle.is_nil())
{
// keyed topic -> enough to only dispose instance
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;
};
}
+50
View File
@@ -0,0 +1,50 @@
/**
* @RDE_HEADER@
* GenericFTS Interface
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <rti/core/SampleIdentity.hpp>
#include <QVector>
/**
* Note:
*
* The FTS calls a slot of the calling object (CO) to send the requestData to the CO.
* This slot needs to be implemented by the calling object (CO):
*
* public Q_SLOTS:
* void handleFunctionTopicRequest(const rti::core::SampleIdentity &requestIdentity, const TOPICTYPE_REQUEST &requestData);
*
* The CO processes the request and sends the feedback (and possible reply data) back to the FTS
* by calling the method:
*
* void sendReply(const rti::core::SampleIdentity &requestIdentity, const QVector<TOPICTYPE_REPLY> &replyData )
*
*
* - The FTS cannot use SIGNALS, because templates and QObject are incompatible.
* - The FTS and the CO are in different threads
*
* => reply data will be stored in a queue (std:queue) and the handling is done by a semaphore (QSemaphore)
*
* */
namespace FTS
{
// uses FTS (static)
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;
};
}
+315
View File
@@ -0,0 +1,315 @@
/**
* @RDE_HEADER@
* Generic Function Topic Server (FTS Replier)
* $Id$
* $HeadURL$
* $Author$
*/
#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 &)
{
// nothing to do here
}
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)
{
// nothing to do here
}
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);
//DEBUG_LOG(m_logger) << "m_domainParticipant: " << m_domainParticipant;
//requesterParams.service_name("FunctionTopic");
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));
//DEBUG_LOG(m_logger) << "=> Static request using request/reply topic names: "
// << requestTopicName << ", reply: " << replyTopicName;
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;
}
/**
* @brief
*
* Send a single reply back to the requester
*
* @param requestIdentity
* @param replyData
*/
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;
}
/**
* @brief
*
* Send a block of reply informations back to the requester
* The replies will have a correctly set replyState member (all will have InProgress, the last on Completed)
*
* @param requestIdentity
* @param replyData
*/
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:
// -------------------------------------------------------------------------------------------------------------------
// FunctionTopics => defined in *.idl
// -------------------------------------------------------------------------------------------------------------------
/**
* @brief
* Get the requested data and send them on to the object that will supply the data
*
* @param request topic data
*/
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) // sec
{
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());
}
/**
* @brief
*
* This method waits for requests from a FTC.
* When it receives a request topic, it will get the requested data
* and send it via DDS Replier back to the FTC
*
*/
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:
// Replier (with request and reply topic (see above)
connext::Replier<RequestTopic, ReplyTopic> *m_replier;
dds::sub::cond::ReadCondition *m_readCondition;
// no ownership
DDSGeneric::WaitSetHandler *m_wsHandler;
std::queue<connext::Sample<RequestTopic>> m_requests;
std::mutex m_requestMutex;
// Callback for data processing
SubscriberCallback m_subscriberCallback;
NGL::INGLLogger &m_logger;
};
} // namespace FTS
+53
View File
@@ -0,0 +1,53 @@
/**
* @RDE_HEADER@
* Generic Function Topic Server (FTS Replier)
* $Id$
* $HeadURL$
* $Author$
*/
#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
}
} // namespace FTS
+39
View File
@@ -0,0 +1,39 @@
/**
* @RDE_HEADER@
* Generic Function Topic Server (FTS Replier)
* $Id$
* $HeadURL$
* $Author$
*/
#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:
// those two functions are executed in another thread
void dataAvailable() override;
virtual bool enqueueRequests() = 0; // returns true when valid data could be read (so waitForRequests should be invoked)
protected Q_SLOTS:
virtual void processRequests() = 0;
};
} // namespace FTS
+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)
+363
View File
@@ -0,0 +1,363 @@
/**
* @RDE_HEADER@
* * DynamicDataReader
* $Id$
* $HeadURL$
* $Author$
*/
#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; // needed for Instatiation of DDTC
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
{
// Definition of topic name:
// simple name without the project namespace prepended with short project name
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")))
{
// DataReaderQos Change needed => FunctionTopics are only send with durability = VOLATILE
dds::core::policy::Durability readerDurability;
dataReaderQos >> readerDurability;
readerDurability.kind(dds::core::policy::DurabilityKind_def::VOLATILE); // DDS_VOLATILE_DURABILITY_QOS
dataReaderQos << readerDurability;
}
m_dataReader = DataReader(ddsSubscriber,
m_ddsTopic,
dataReaderQos,
nullptr /* no DataReaderListener needed */,
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;
}
// Checks, if reader has (still) samples available
bool hasSamples() const
{
qint64 sampleSize = m_dataReader->datareader_cache_status().sample_count();
DEBUG_LOG(m_logger) << "sampleSize = " << sampleSize;
return sampleSize > 0;
}
// Read all available samples (no copy, data reader has data ownership)
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;
}
// Take all available samples (no copy, data reader has data ownership)
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;
}
// retrieve all samples not yet read (= sent out by datawriter between last read and now)
// do not take them because we use the datareader as cache (to be able to do a readAll() or smth. else later on)
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);
}
// no const since rti::sub::LoanedSample<T> ctor does not take const ptr
auto copy = m_newSamplesDuringRead; // needed because due to some eventloop magic it is possible that
// during read data new samples are added ... check!
m_newSamplesDuringRead.clear();
for (auto &sample : copy)
{
// only valid data here
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
{
// do not fill m_newSamplesDuringRead when there is no one who's interested in this...
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();
// new samples and unread samples ...
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());
// those instances are useless after this read -> take them off the datareader
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;
/*
std::cerr << "DataState vs: " << ds.view_state().to_string()
<< ", is: " << ds.instance_state().to_string()
<< ", ss: " << ds.sample_state().to_string()
<< std::endl;
*/
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;
};
} // Namespace DDSGeneric
+98
View File
@@ -0,0 +1,98 @@
/**
* @RDE_HEADER@
* GenericBase
* $Id$
* $HeadURL$
* $Author$
*/
#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)
{
// add short project name instead of long project namespace (with ::)
// i. e. Lts::SpatialData => LTS_SpatialData
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';
// check if it's really a signal or slot
if(slotCode != QSLOT_CODE && slotCode != QSIGNAL_CODE)
{
ERROR_LOG(logger) << "Use the SLOT or SIGNAL macro for slot name!";
return ret;
}
slotToExecute++; // skip code
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);
}
} // namespace Helper
} // namespace DDS
+78
View File
@@ -0,0 +1,78 @@
/**
* @RDE_HEADER@
* GenericBase (used as Base for all Generic<Items>)
*
* Items can be:
* - DataReaderObject (DRO)
* - DataWriterObject (DWO)
* - DynamicDataReaderObject (DDRO)
* - DataTopicClient (DTC)
* - DataTopicServer (DTS)
*
* $Id$
* $HeadURL$
* $Author$
*/
#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);
/**
* @brief
* getMethod checks if the given object has a slot with the given name (slotToExecute)
* and if the signature is correct.
*
* @param objectToInform
* @param slotToExecute
* @param signalSignature
* @return QMetaMethod
*/
DDSGEN_EXPORT QMetaMethod getMethod(NGL::INGLLogger &logger,
QObject *objectToInform,
const char *slotToExecute,
const char *signalSignature,
bool bWarnOnEmptySlot = true);
}
}
+391
View File
@@ -0,0 +1,391 @@
/**
* @RDE_HEADER@
* * GenericDataReader
* $Id$
* $HeadURL$
* $Author$
*/
#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(); //here we get the overwriten profile
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;
}
// Checks, if reader has (still) samples available
bool hasSamples() const
{
qint64 sampleSize = m_dataReader->datareader_cache_status().sample_count();
DEBUG_LOG(m_logger) << "sampleSize = " << sampleSize;
return sampleSize > 0;
}
// Read all available samples (no copy, data reader has data ownership)
// no const because m_dataReader.select() is not const ...
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;
}
// Take all available samples (no copy, data reader has data ownership)
// no const because m_dataReader.select() is not const ...
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;
}
// retrieve all samples not yet read (= sent out by datawriter between last read and now)
// do not take them because we use the datareader as cache (to be able to do a readAll() or smth. else later on)
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);// needed because due to some eventloop magic it is possible that
// during read data new samples are added ...
// only valid data here
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
{
// do not fill m_newSamplesDuringRead when there is no one who's interested in this...
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())
{
// new samples and unread samples ...
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());
// those instances are useless after this read -> take them off the datareader
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;
/*
std::cerr << "DataState vs: " << ds.view_state().to_string()
<< ", is: " << ds.instance_state().to_string()
<< ", ss: " << ds.sample_state().to_string()
<< std::endl;
*/
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());
};
} // Namespace DDSGeneric
+148
View File
@@ -0,0 +1,148 @@
/**
* @RDE_HEADER@
* * GenericDataWriter (including GenericDataWriterBase)
* $Id$
* $HeadURL$
* $Author$
*/
#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;
};
} // Namespace DDSGeneric
+64
View File
@@ -0,0 +1,64 @@
/**
* @RDE_HEADER@
* Logging for DDS modules
* $Id$
* $HeadURL$
* $Author$
*/
#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();
}
}
} // namespace DDS
+41
View File
@@ -0,0 +1,41 @@
/**
* @RDE_HEADER@
* Logging for DDS modules
* $Id$
* $HeadURL$
* $Author$
*/
#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);
}
} // namespace AreaEffectSimulation
+196
View File
@@ -0,0 +1,196 @@
/**
* @RDE_HEADER@
* SubscriberListener for the DataTopicClient (DTC)
* $Id$
* $HeadURL$
* $Author$
*/
#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); // always lower bound
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 & -> avoid copy since they return a const ref
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); // always lower bound
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;
}
// old method, do not use as there is no context switch!
// cb->droDataAvailable(type);
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();
// Same for Topics with PartitionId appended
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); // always lower bound
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
{
//DEBUG_LOG(m_logger) << "IN fillData: ";
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);
//DEBUG_LOG(m_logger) << "signal:" << signal << "slot: " << slot;
//DEBUG_LOG(m_logger) << "method: " << method.signature();
return cbData(cb, dataReader, method);
}
} // namespace DDSGeneric
+155
View File
@@ -0,0 +1,155 @@
/**
* @RDE_HEADER@
* SubscriberListener for the DataTopicClient (DTC)
* $Id$
* $HeadURL$
* $Author$
*/
#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;
}
/*
* The following task are NOT allowed in a listener callback:
*
* - Within any listener callback, creating any entity
* - Within any listener callback, deleting any entity
* - Within any listener callback, enabling any entity
* - Within any listener callback, setting the QoS of any entities
*
* for more information see:
* https://community.rti.com/docs/html/api_cpp/classDDSListener.html
*
**/
namespace DDSGeneric
{
class DDSGEN_EXPORT SubscriberListenerCallback : public QObject
{
Q_OBJECT
public:
virtual ~SubscriberListenerCallback() = default;
public Q_SLOTS:
// indirectly called via signal/slot
virtual void droDataAvailable(const std::string &topicType, const std::string &topicName) = 0;
// attn: directly called -> executed in dds thread
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
{
// ohne enabled
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; //! Logger-Object
};
} // namespace Generic
+42
View File
@@ -0,0 +1,42 @@
/**
* @RDE_HEADER@
*
* DDTC Definition & TopicDescription
*
* $Id$
* $HeadURL$
* $Author$
*/
#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();
}
};
}
+41
View File
@@ -0,0 +1,41 @@
/**
* Plugin Interface to provide custom types
* $Id$
* $HeadURL$
* $Author$
*/
#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;
// QMap<TopicName,TypeInformation>
// => QMap<QString,TypeInformation>
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")
+152
View File
@@ -0,0 +1,152 @@
/**
* @RDE_HEADER@
* Handler which notifes the FTS-instances about new data
* $Id$
* $HeadURL$
* $Author$
*/
#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);
// already there -> do not add a second time
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);
// not there -> no remove possible
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;
}
}
}
}
} // namespace DDSGeneric
+70
View File
@@ -0,0 +1,70 @@
/**
* @RDE_HEADER@
* Handler which notifes the FTS-instances about new data
* $Id$
* $HeadURL$
* $Author$
*/
#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;
};
} // namespace DDSGeneric
+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
)
+259
View File
@@ -0,0 +1,259 @@
/**
* @RDE_HEADER@
* Configuration of the default QoS
* $Id$
* $HeadURL$
* $Author$
*/
#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
{
// QosProvider
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)
{
// '_' is not allowed in a scheme, but rti needs it ...
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"))
{
// client
rti::core::policy::Discovery discovery;
dpQos >> discovery;
const dds::core::StringSeq seq({fixupSchemeName(tcpConnectionUrl).toStdString()});
discovery.initial_peers(seq);
dpQos << discovery;
}
else
{
// server
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; // no changes to default profile
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; // no changes to default profile
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)
{
// for debugging: get the QoS parameter for writerQos & readerQos
// Dp Qos
print(provider.participant_qos(), logger);
// DataWriter Qos
print(provider.datawriter_qos(), logger);
// DataReader Qos
print(provider.datareader_qos(), logger);
}
void print(const dds::domain::qos::DomainParticipantQos &qos, NGL::INGLLogger &logger)
{
// DataReader Qos
DEBUG_LOG(logger) << "Qos for DomainParticipant: ";
printPolicyMap(qos->property.get_all(), logger);
}
void print(const dds::sub::qos::DataReaderQos &readerQos, NGL::INGLLogger &logger)
{
// DataReader Qos
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)
{
// DataWriter Qos
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 ;
}
}
} // namespace QosProfile
} // namespace DDS
+64
View File
@@ -0,0 +1,64 @@
/**
* @RDE_HEADER@
* Configuration of the default QoS:
* - Reading default profile (xml)
* - added Parameter
*
* $Id$
* $HeadURL$
* $Author$
*/
#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, // shm and udp
ShmUdpLocalhost, // shm and udp, but udp only localhost
Shm, // only shm
Tcp, // tcpv4
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);
// shortcuts for domainParticipantDefault(typ, url)
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);
} // namespace QosProfile
} // namespace DDS
+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
)
+368
View File
@@ -0,0 +1,368 @@
/**
* @RDE_HEADER@
* Routing Test - main connection handling
* $Id$
* $HeadURL$
* $Author$
*/
#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>
//
// Debug Helper
//
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
{
// return dds::core::Topic<FOO>, check if topic already exists, create it otherwise
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);
}
//
// Interfaces
//
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;
};
//
// Implementations
//
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
{
// if not valid, return unknown identity
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.source_timestamp(sample.info().source_timestamp());
writeParams.handle(sample.info().instance_handle());
writeParams.flag(sample.info()->flag());
// TODO: investigate about writeParams.flush_on_write() for Function Topics
TRACE_LOG(m_logger) << "identity: " << DebugInfo::toReadable(writeParams.identity())
<< ", related_sample_identity: " << DebugInfo::toReadable(writeParams.related_sample_identity());
const auto &state = sample.info().state(); // returns an object in 5.3.0, needed to make sure it's alive after line below
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));
// make sure to not subscribe to ourself ...
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
{
// no need to check topic name here since the correct datareader was set
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));
// first init, then set listener to make sure all is set up when data arrives
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();
}
}
// should be safe since it was removed from the dataReader above
// (thread sync must be dds internal - otherwise it would never be safe to destroy a listener)
m_dtl = nullptr;
// after m_dtl to make sure there are no outstanding loans (should not happen but who knows)
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;
};
// Helper for DTC
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)
{}
};
// Helper for FTS
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)
{}
};
// Helper for FTC
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)
{}
};
} // namespace RoutingService