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

+87
View File
@@ -0,0 +1,87 @@
#
# $Id$
# $HeadURL$
# $Author$
#
#
# --- Executable svcWirelessLocationSystem (svcWLS) -------------------------------------------------
#
SET(xml_FILE wls_service.xml)
SET(SRCS_exe
dds_handler.cpp
dds_handler.h
ism_handler.cpp
ism_handler.h
logger.cpp
logger.h
main.cpp
main_handler.cpp
main_handler.h
settings.cpp
settings.h
web_socket_handler.cpp
web_socket_handler.h
wls_handler.cpp
wls_handler.h
wls_participant.cpp
wls_participant.h
wls_participant_handler.cpp
wls_participant_handler.h
wls_sfm_message_converter.cpp
wls_sfm_message_converter.h
wls_sfm_message.cpp
wls_sfm_message.h
)
# make sure xml file is properly installed
CODEGEN_HANDLE_SVC_XML(SRCS_exe ${xml_FILE})
NGL_ADD_SVC_ICON(SRCS_exe)
NGL_ADD_EXECUTABLE(AAR SV svcWirelessLocationSystem ${SRCS_exe})
NGL_TARGET_LINK_LIBRARIES(svcWirelessLocationSystem
Qt5::Core
Qt5::Network
Qt5::WebSockets
svcCoreClient
svcIsmClient
ddsconnector
SfmConverter
LtsCommandLineParser
LtsCoordTrans
LtsUtilsCore
NGLLogger
nglcoreapplication
nglsettings
ngltools
tabTelegrams
)
NGL_INSTALL_TARGET(svcWirelessLocationSystem)
NGL_INSTALL(AAR SV FILES log4cxx_wls.props DESTINATION bin)
NGL_ADD_TARGET_LINK(svcWirelessLocationSystem pWLS)
NGL_ADD_EXECUTABLE(AAR SV wlsJsonClientTest wls_json_client_test.cpp wls_json_client_test.h)
NGL_TARGET_LINK_LIBRARIES(wlsJsonClientTest
Qt5::Core
Qt5::WebSockets
)
NGL_ADD_LIBRARY2(SfmConverter AAR SV SHARED
logger.cpp
logger.h
wls_sfm_message_converter.cpp
wls_sfm_message_converter.h
wls_sfm_message.cpp
wls_sfm_message.h
)
NGL_TARGET_INCLUDE_DIRECTORIES(SfmConverter
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(SfmConverter
Qt5::Core
LtsUtilsCore
NGLLogger
)
+40
View File
@@ -0,0 +1,40 @@
/**
* DdsHandler for DDS
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#include "dds_handler.h"
#include <DDS/Core/pacemaker.h>
namespace WirelessLocationSystem
{
DdsHandler::DdsHandler(int domainId, int exerciseId)
: m_ddsConnector(new DDSConnector::DdsConnector(domainId, IDL::PartitionType::Global, exerciseId))
, m_logger(getLogger("DdsHandler"))
{
DEBUG_LOG(m_logger) << "DdsHandler(" << domainId << ") for partition: " << m_ddsConnector->getDefaultPartition();
}
bool DdsHandler::init(const QStringList &qosFileList)
{
if (!m_ddsConnector->init(qosFileList))
{
ERROR_LOG(m_logger) << "Error initializing dds connections - aborting";
return false;
}
// default initialize Publisher and Subscriber
m_ddsConnector->initPublisherSubscriber();
// Pacemaker
m_pacemaker = std::make_shared<Core::Pacemaker>(m_ddsConnector);
return true;
}
} // namespace WirelessLocationSystem
+45
View File
@@ -0,0 +1,45 @@
/**
* DdsHandler for DDS
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#pragma once
#include <DDS/DDSConnector/dds_connector.h>
#include "logger.h"
namespace Core
{
class Pacemaker;
}
namespace WirelessLocationSystem
{
class DdsHandler
{
public:
DdsHandler(int domainId, int exerciseId);
bool init(const QStringList &qosFileList = QStringList());
std::shared_ptr<Core::Pacemaker> pacemaker() const
{
return m_pacemaker;
}
std::shared_ptr<DDSConnector::DdsConnector> ddsConnector() const
{
return m_ddsConnector;
}
private:
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
std::shared_ptr<Core::Pacemaker> m_pacemaker;
NGL::INGLLogger &m_logger;
};
}
+74
View File
@@ -0,0 +1,74 @@
/**
* IsmHandler for WirelessLocationSystem
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#include "ism_handler.h"
#include "main_handler.h"
namespace WirelessLocationSystem
{
IsmHandler::IsmHandler(QObject *parent)
: QObject(parent)
, m_logger(getLogger("IsmHandler"))
{}
bool IsmHandler::init(MainHandler *mainHandler)
{
if (m_dtcIsm)
return true;
m_dtcIsm = InfrastructureManagement::Interface::Dtc::create(mainHandler->ddsHandler()->ddsConnector());
connect(m_dtcIsm.get(), &InfrastructureManagement::Interface::Dtc::mainStructureElementChanged,
this, &IsmHandler::onMainStructureElementChanged);
if (!m_dtcIsm->enableMainStructureElement())
{
ERROR_LOG(m_logger) << "Can not read data from ISM";
return false;
}
return true;
}
void IsmHandler::onMainStructureElementChanged(const InfrastructureManagement::MainStructureElement &data, DataModification modType)
{
switch (modType)
{
case DataModification::eAdded:
case DataModification::eModified:
{
m_mainStructureElements[data.id()] = data;
if (data.externalWlsId() != 0)
m_externalWlsId2MseId[data.externalWlsId()] = data.id();
QMap<int, int> roomNumber2roomId;
for (const auto &element : data.elements())
{
if (element.structureElementTypeId() != InfrastructureManagement::StructureElementType::Room)
continue;
roomNumber2roomId[element.mountingPointId()] = element.id();
}
m_mseId2RoomId[data.id()] = roomNumber2roomId;
break;
}
case DataModification::eRemoved:
{
m_mainStructureElements.remove(data.id());
m_mseId2RoomId.remove(data.id());
for (auto it = m_externalWlsId2MseId.begin(); it != m_externalWlsId2MseId.end(); ++it)
{
if (it.value() == data.id())
{
m_externalWlsId2MseId.erase(it);
break;
}
}
break;
}
}
}
} // namespace WirelessLocationSystem
+49
View File
@@ -0,0 +1,49 @@
/**
* IsmHandler for WirelessLocationSystem
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#pragma once
#include <QObject>
#include <QHash>
#include "logger.h"
#include <InfrastructureManagement/ism.hpp>
#include <InfrastructureManagement/ism_dtc.h>
#include <LtsUtilsCore/data_modification_enum.h>
namespace WirelessLocationSystem
{
class MainHandler;
class IsmHandler : public QObject
{
Q_OBJECT
public:
explicit IsmHandler(QObject *parent = nullptr);
bool init(MainHandler *mainHandler);
int buildingIdFromExternalWlsId(int externalWlsId) const { return m_externalWlsId2MseId.value(externalWlsId, -1); }
int roomIdFromRoomNumber(int buildingId, int roomNumber) const { return m_mseId2RoomId.value(buildingId).value(roomNumber, -1); }
private Q_SLOTS:
void onMainStructureElementChanged(const InfrastructureManagement::MainStructureElement &data, DataModification modType);
private:
// QMap<mseId, MainStructureElement>
QHash<int, InfrastructureManagement::MainStructureElement> m_mainStructureElements;
// QMap<externalWlsId, mseId>
QHash<int, int> m_externalWlsId2MseId;
// QMap<mseId, QMap<roomNumber, roomId>>
QHash<int, QMap<int, int>> m_mseId2RoomId;
std::shared_ptr<InfrastructureManagement::Interface::Dtc> m_dtcIsm;
LOGGER_T m_logger;
};
} // namespace WirelessLocationSystem
+83
View File
@@ -0,0 +1,83 @@
log4j.reset=true
log4j.rootLogger=WARN, file
# Service: WirelessLocationSystem
###########################################################
#log4j.logger.WLS=DEBUG
#log4j.logger.WLS.DdsHandler=INFO
#log4j.logger.WLS.IsmHandler=INFO
#log4j.logger.WLS.MainHandler=INFO
#log4j.logger.WLS.WebSocketHandler=WARN
#log4j.logger.WLS.WlsHandler=INFO
# File
###########################################################
log4j.appender.file=Lts::RollingFileAppender
log4j.appender.file.MaxFileSize=1MB
log4j.appender.file.MaxBackupIndex=9
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:
+38
View File
@@ -0,0 +1,38 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace WirelessLocationSystem
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "WLS.";
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 WirelessLocationSystem
+23
View File
@@ -0,0 +1,23 @@
/**
* @RDE_HEADER@
* Logging for TrainingAreaBase
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#pragma once
#include <NGLLogger/INGLLogger.h>
namespace WirelessLocationSystem
{
using LOGGER_T = NGL::INGLLogger&;
LOGGER_T getLogger(const char *name);
inline LOGGER_T getLogger(const QString &loggerName)
{
return getLogger(loggerName.toUtf8().constData());
}
} // namespace WirelessLocationSystem
+132
View File
@@ -0,0 +1,132 @@
/**
* @RDE_HEADER@
* main for TetraRadio
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#include <nglapplication/nglcoreapplication.h>
#include <LtsCommandLineParser/LtsCommandLineParser.h>
#include <QNetworkProxy>
#include <memory>
#include "main_handler.h"
#include "logger.h"
#include "settings.h"
#include <LtsUtilsCore/sfm_bytemessage_converter.h>
#include "wls_sfm_message.h"
#include "wls_sfm_message_converter.h"
class CmdLineParser : public LtsCommandLineParser::LtsCommandLineParser
{
public:
CmdLineParser()
: LtsCommandLineParser()
{
addOption({ QStringList() << QLatin1String("c") << QLatin1String("checkData"),
QLatin1String("Testdata"),
QLatin1String("data to check as hex (e.g. '02044c221f8091660bb300001a31313131313131313131313131313131313131313131313131312503'"),
QString()
});
}
bool isCheckData() const { return isSet(QLatin1String("c")); }
QByteArray dataToCheck() const { return value(QLatin1String("c")).toUtf8(); }
};
namespace WirelessLocationSystem
{
bool sendBinarySfmData(const QByteArray &ba, int sendCounter)
{
int chunkLen = 0;
int chunkCount = 1;
if (!WlsParticipant::calcChunkCountAndLen(ba, chunkCount, chunkLen))
{
fprintf(stderr, "Chunk count is not ok - data size: %d\n", ba.size());
return false;
}
int fragmentNumber = 1;
int iPos = 0;
while (iPos < ba.size())
{
const SfmMsgTetra_tx tx(sendCounter,
chunkCount,
fragmentNumber,
ba.mid(iPos, chunkLen));
const auto payload(SfmMessageConverter::generateTxMessage(tx));
const unsigned int fragmentParts = ((payload.size() + WlsHandler::s_maxLenTransparentData - 1) / WlsHandler::s_maxLenTransparentData);
if (fragmentParts != 1)
fprintf(stderr, "Calculation mismatch - only expected one part, got %d\n", fragmentParts);
SfmMsgTetra_rx rx;
rx.setPayload(tx.getPayload());
fprintf(stdout, "Output: len: %d(%d), %s, sendCounter: %d(0x%02x), is fragmented: %d, fragment: %d/%d\n",
payload.size(), tx.getPayload().size(), payload.data(),
rx.sendCounter(), rx.sendCounter(), rx.isFragmented(), rx.fragmentNumber(), rx.fragmentCount());
++sendCounter;
++fragmentNumber;
iPos += chunkLen;
}
return true;
}
static int checkData(const QByteArray &data)
{
const QByteArray ba(QByteArray::fromHex(data));
try
{
const auto msg = LtsUtils::byteStreamToSfmMessage(ba);
fprintf(stdout, "Message payload: len: %d, %s\n", msg.payload().size(), msg.payload().toHex().data());
if (msg.id() == int(SfmMessage::MsgId::TetraMessage))
{
WirelessLocationSystem::SfmMsgTetra_rx rx;
rx.setPayload(msg.payload());
fprintf(stdout, "SfmMsgTetra payload: len: %d, %s, ", rx.payload().size(), rx.payload().toHex().data());
fprintf(stdout, "sendCounter: %d(0x%02x), is fragmented: %d, fragment: %d/%d\n",
rx.sendCounter(), rx.sendCounter(), rx.isFragmented(), rx.fragmentNumber(), rx.fragmentCount());
sendBinarySfmData(rx.payload(), 0x02); // 0x02 -> a value which has to be substituted
}
}
catch (const std::exception &e)
{
fprintf(stderr, "Could not decode message: %s\n", e.what());
return 1;
}
return 0;
}
} // namespace WirelessLocationSystem
int main(int argc, char **argv)
{
NGLCoreApplication app(argc, argv);
QNetworkProxy::setApplicationProxy(QNetworkProxy());
int iRet = -1;
CmdLineParser options;
if (!options.init())
return iRet;
app.createLogger("WLS", 0);
if (options.isCheckData())
return WirelessLocationSystem::checkData(options.dataToCheck());
WirelessLocationSystem::Settings::setIniFile(options.iniFile());
{
std::unique_ptr<WirelessLocationSystem::MainHandler> mainHandler(new WirelessLocationSystem::MainHandler);
if (mainHandler->init(options.domainId()))
{
STARTUP_LOG(app.baseLogger());
iRet = app.exec();
SHUTDOWN_LOG(app.baseLogger());
}
}
return iRet;
}
+65
View File
@@ -0,0 +1,65 @@
/**
* MainHandler for TetraRadio
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#include "main_handler.h"
#include "settings.h"
#include <DDS/Core/pacemaker.h>
namespace WirelessLocationSystem
{
MainHandler::MainHandler(QObject *parent)
: QObject(parent)
, m_ddsHandler(nullptr)
, m_logger(getLogger("MainHandler"))
{}
MainHandler::~MainHandler()
{
m_wlsParticipantHandler = nullptr;
m_wlsHandler = nullptr;
m_wsHandler = nullptr;
m_ismHandler = nullptr;
m_ddsHandler = nullptr;
}
bool MainHandler::init(int domainId)
{
const int exerciseId = 0;
// initialize DDS
m_ddsHandler = std::make_shared<DdsHandler>(domainId, exerciseId);
if (!m_ddsHandler->init())
return false;
m_wlsParticipantHandler = std::make_shared<WlsParticipantHandler>();
m_wsHandler = std::make_shared<WebSocketHandler>();
m_ismHandler = std::make_shared<IsmHandler>();
m_wlsHandler = std::make_shared<WlsHandler>();
if (!m_ismHandler->init(this))
return false;
if (!m_wlsHandler->init(m_wlsParticipantHandler.get(), // TODO: if using shared pointers, then *use* them
m_wsHandler.get(),
Settings::wlsListenPort()))
return false;
if (!m_wsHandler->init(m_wlsHandler,
m_wlsParticipantHandler,
m_ismHandler,
Settings::webSocketListenPort()))
return false;
m_wlsParticipantHandler->init(m_wsHandler.get(), // TODO: if using shared pointers, then *use* them
m_wlsHandler.get());
m_ddsHandler->pacemaker()->setNewState(Core::RunningState::Running);
return true;
}
} // namespace WirelessLocationSystem
+62
View File
@@ -0,0 +1,62 @@
/**
* MainHandler for TetraRadio
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#pragma once
#include <QObject>
#include <memory>
#include "dds_handler.h"
#include "ism_handler.h"
#include "wls_participant_handler.h"
#include "web_socket_handler.h"
#include "wls_handler.h"
#include <InfrastructureManagement/ism_dtc.h>
#include "logger.h"
namespace WirelessLocationSystem
{
class MainHandler : public QObject
{
Q_OBJECT
public:
explicit MainHandler(QObject *parent = nullptr);
~MainHandler();
bool init(int domainId);
std::shared_ptr<DdsHandler> ddsHandler() const
{
return m_ddsHandler;
}
std::shared_ptr<IsmHandler> ismHandler() const
{
return m_ismHandler;
}
std::shared_ptr<WlsHandler> wlsHandler() const
{
return m_wlsHandler;
}
std::shared_ptr<WebSocketHandler> webSocketHandler() const
{
return m_wsHandler;
}
std::shared_ptr<WlsParticipantHandler> participantHandler() const
{
return m_wlsParticipantHandler;
}
private:
std::shared_ptr<DdsHandler> m_ddsHandler;
std::shared_ptr<WlsParticipantHandler> m_wlsParticipantHandler;
std::shared_ptr<WlsHandler> m_wlsHandler;
std::shared_ptr<WebSocketHandler> m_wsHandler;
std::shared_ptr<IsmHandler> m_ismHandler;
LOGGER_T m_logger;
};
} // namespace WirelessLocationSystem
+75
View File
@@ -0,0 +1,75 @@
/**
* Settings for TetraRadio
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#include "settings.h"
#include <QtCore/QSettings>
#include <nglsettings/nglsettings_settings.h>
#include <ngltools/ngltools_filesystem.h>
namespace WirelessLocationSystem
{
class NGLSettingsClass : public NGL::Settings
{
public:
NGLSettingsClass()
: NGL::Settings(s_iniFile.isEmpty() ? NGL::findProjectConfigFile() : s_iniFile)
{}
static void setIniFile(const QString &fn)
{
s_iniFile = fn;
}
static QString s_iniFile;
};
QString NGLSettingsClass::s_iniFile;
Q_GLOBAL_STATIC(NGLSettingsClass, s)
//-----------------------------------------------------------------------------
NGL::Settings *Settings::settings()
{
return s();
}
//-----------------------------------------------------------------------------
void Settings::setIniFile(const QString &ini)
{
NGLSettingsClass::setIniFile(ini);
}
quint16 Settings::wlsListenPort()
{
return settings()->settings()->value(QStringLiteral("WLS/wlsListenPort"), 12002).toInt();
}
quint16 Settings::webSocketListenPort()
{
return settings()->settings()->value(QStringLiteral("WLS/webSocketListenPort"), 12346).toInt();
}
QString Settings::wlsMgrsBasePoint()
{
return settings()->settings()->value(QStringLiteral("WLS/wlsBasePoint"), QStringLiteral("32UMD0000000000000000")).toString();
}
double Settings::wlsZOffset()
{
return settings()->settings()->value(QStringLiteral("WLS/wlsZOffset"), 0).toDouble();
}
int Settings::deadPosTimeout()
{
return settings()->settings()->value(QStringLiteral("WLS/deadPosTimeout"), 5000).toInt();
}
double Settings::wlsSquareDistanceMax()
{
return settings()->settings()->value(QStringLiteral("WLS/squareDistanceMax"), 0.0).toDouble();
}
} // namespace WirelessLocationSystem
+36
View File
@@ -0,0 +1,36 @@
/**
* Settings for TetraRadio
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-14
*/
#pragma once
#include <QString>
namespace NGL
{
class Settings;
}
namespace WirelessLocationSystem
{
namespace Settings
{
//!< The real NGL::Settings - object
NGL::Settings *settings();
void setIniFile(const QString &ini);
quint16 wlsListenPort();
quint16 webSocketListenPort();
double wlsZOffset();
int deadPosTimeout();
double wlsSquareDistanceMax();
QString wlsMgrsBasePoint();
}
} // namespace WirelessLocationSystem
+308
View File
@@ -0,0 +1,308 @@
/**
* Websocket interface to the training area proxy
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-11
*/
#include "web_socket_handler.h"
#include "ism_handler.h"
#include "main_handler.h"
#include "settings.h"
#include "wls_handler.h"
#include "wls_participant_handler.h"
#include <LtsUtilsCore/dds_vector.h>
#include <QJsonDocument>
#include <QPointF>
#include <QWebSocket>
namespace WirelessLocationSystem
{
const char *exerciseIdProperty = "exerciseId";
WebSocketHandler::WebSocketHandler(QObject *parent)
: QObject(parent)
, m_webSocketServer(new QWebSocketServer(QStringLiteral("WLS"), QWebSocketServer::NonSecureMode, this))
, m_basePoint(LtsCoordTrans::UtmCoordinate::fromMgrs(Settings::wlsMgrsBasePoint()))
, m_logger(getLogger("WebSocketHandler"))
{}
WebSocketHandler::~WebSocketHandler()
{}
bool WebSocketHandler::init(std::shared_ptr<WlsHandler> wlsHandler,
std::shared_ptr<WlsParticipantHandler> participantHandler,
std::shared_ptr<IsmHandler> ismHandler,
quint16 listenPort)
{
if (!m_webSocketServer->listen(QHostAddress::Any, listenPort))
{
WARNING_LOG(m_logger) << "Can not listen on port " << listenPort << " - aborting" << m_webSocketServer->errorString();
return false;
}
connect(m_webSocketServer, &QWebSocketServer::newConnection,
this, &WebSocketHandler::onNewConnection);
m_wlsHandler = wlsHandler;
m_participantHandler = participantHandler;
m_ismHandler = ismHandler;
return true;
}
void WebSocketHandler::onNewConnection()
{
QWebSocket *ws = m_webSocketServer->nextPendingConnection();
if (ws == nullptr)
return;
DEBUG_LOG(m_logger) << "New websocket connection from " << ws->peerAddress().toString() << ":" << ws->peerPort();
connect(ws, &QWebSocket::textMessageReceived,
this, &WebSocketHandler::onTextMessageReceived);
connect(ws, &QWebSocket::disconnected,
ws, &QWebSocket::deleteLater);
connect(this, &WebSocketHandler::signalBroadcastMessage,
ws, [this, ws](const QString &msg, int exerciseId)
{
DEBUG_LOG(m_logger) << "broadcast: exercise property is valid? " << ws->property(exerciseIdProperty).isValid() << " for " << ws->peerPort();
bool exerciseIdAvailable = false;
int wsExerciseId = ws->property(exerciseIdProperty).toInt(&exerciseIdAvailable);
if (exerciseIdAvailable && wsExerciseId != exerciseId) // NOTE: send for websocket clients that previously didn't set their exercise id beforehand! (e.g. the WLS-Diagnose tool)
{
DEBUG_LOG(m_logger) << "broadcast: websocket has " << wsExerciseId << ", but " << exerciseId << " was requested";
return;
}
ws->sendTextMessage(msg);
});
// send wls connection state (to all web sockets, should not hurt much)
TrainingAreaBase::TetraIcd::ProxyConnectionState connState;
connState.connectedToSystem = m_wlsHandler->isConnected();
sendTelegram(&connState, 0);
for (const WlsParticipant *participant : qAsConst(m_participantHandler->participants()))
sendDataCommunicationState(participant);
}
void WebSocketHandler::sendTelegramInternal(const TrainingAreaBase::TetraIcd::TrainingAreaTelegram *data,
const QString &type, int exerciseId)
{
QJsonObject obj;
obj[QStringLiteral("data")] = data->toJson();
obj[QStringLiteral("type")] = type;
obj[QStringLiteral("typeId")] = data->id();
// if (data->id() != TrainingAreaBase::TetraIcd::TelegramId::BESTAETIGUNG)
// obj[QStringLiteral("counter")] = m_sendCounter++;
const QString jsonStr(QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact)));
DEBUG_LOG(m_logger) << "jsonStr: " << jsonStr;
emit signalBroadcastMessage(jsonStr, exerciseId);
}
void WebSocketHandler::sendParticipantConnectionStates(int exerciseId)
{
TrainingAreaBase::TetraIcd::WlsParticipantsConnectionState partConnState;
for (const WlsParticipant *p : m_participantHandler->participants())
{
if (p->equipmentNumber() == 0 ||
p->exerciseId() != exerciseId ||
p->tagId().isNull())
continue;
partConnState.equipmentNumber = p->equipmentNumber();
partConnState.dataCommunicationPossible = p->isDataCommunicationPossible();
sendTelegram(&partConnState, exerciseId);
}
}
bool WebSocketHandler::sendWlsStateReport(int exerciseId)
{
TrainingAreaBase::TetraIcd::WlsParticipantsStateReport stateReport;
stateReport.participantStates.reserve(m_participantHandler->participants().size());
for (const WlsParticipant *p : m_participantHandler->participants())
stateReport.participantStates.push_back(p->wlsParticipantState());
sendTelegram(&stateReport, exerciseId);
return true;
}
void WebSocketHandler::onTextMessageReceived(const QString &baJson)
{
using namespace TrainingAreaBase::TetraIcd;
TRACE_LOG(m_logger) << "WebSocketHandler::onTextMessageReceived: " << baJson;
QWebSocket *ws = qobject_cast<QWebSocket*>(sender());
if (ws == nullptr)
return;
if (baJson.isEmpty())
return;
QJsonParseError err;
QJsonDocument doc(QJsonDocument::fromJson(baJson.toUtf8(), &err));
if (err.error != QJsonParseError::NoError)
{
ERROR_LOG(m_logger) << "received invalid json from websocket at " << err.offset << ": " << err.errorString();
return;
}
const QJsonObject obj(doc.object());
// const QJsonValue sendCounter = obj.value(QStringLiteral("counter"));
// if (!sendCounter.isUndefined())
// {
// if (!sendCounter.isDouble())
// return false;
// recvCounter = sendCounter.toInt(-1);
// DEBUG_LOG(m_logger) << "Incoming telegram with Id " << obj.value(QStringLiteral("type")).toString();
// if (recvCounter < 0)
// return false;
// }
const QJsonValue typeId(obj.value(QStringLiteral("typeId")));
if (!typeId.isDouble())
{
ERROR_LOG(m_logger) << "Can not read type id: " << baJson;
return;
}
const QJsonObject data(obj.value(QStringLiteral("data")).toObject());
try
{
switch (typeId.toInt())
{
case Constraints<WlsParticipantsStateRequest>::id(): // 3001 (only used by WLS-Diagnose tool)
{
sendWlsStateReport(-1); // needs to be -1, otherwise the websocket who subscribed to exercise 0 would receive the answer, too
break;
}
case Constraints<ExerciseStateData>::id(): // 2008
{
int exerciseId = ExerciseStateData::fromJson(data).exerciseId;
ws->setProperty(exerciseIdProperty, exerciseId);
DEBUG_LOG(m_logger) << "set exercise id " << exerciseId << " for ws with peer port " << ws->peerPort();
sendParticipantConnectionStates(exerciseId);
break;
}
case Constraints<ExerciseEquipmentAssignmentComplete>::id(): // 2012
m_participantHandler->exerciseEquipmentAssignmentChanged(ExerciseEquipmentAssignmentComplete::fromJson(data).exerciseEquipmentAssignments);
break;
case Constraints<WlsBinarySfmData>::id(): // 3003
m_participantHandler->handleWlsBinarySfmData(WlsBinarySfmData::fromJson(data));
break;
default:
WARNING_LOG(m_logger) << "Handler for telegram Id " << typeId.toInt()
<< " not yet implemented (" << baJson << ")";
return;
}
}
catch (const std::exception &e)
{
WARNING_LOG(m_logger) << "Could not parse json: " << e.what() << " (" << baJson << ")";
return;
}
}
void WebSocketHandler::sendBinarySfmData(const WlsParticipant *participant)
{
if (!participant->isSfmRxDataAvailable())
return;
if (participant->equipmentNumber() == 0 || participant->tagId().isNull())
return;
TrainingAreaBase::TetraIcd::WlsBinarySfmData tele;
tele.header.equipmentNumber = participant->equipmentNumber();
tele.header.timeStamp = participant->lastSeenWlsTelegramTime();
LtsUtils::convert(participant->sfmRxData(), tele.payload);
sendTelegram(&tele, participant->exerciseId());
}
void WebSocketHandler::sendDataCommunicationState(const WlsParticipant *participant)
{
if (participant->equipmentNumber() == 0 || participant->tagId().isNull())
return;
TrainingAreaBase::TetraIcd::WlsParticipantsConnectionState tele;
tele.equipmentNumber = participant->equipmentNumber();
tele.dataCommunicationPossible = participant->isDataCommunicationPossible();
sendTelegram(&tele, participant->exerciseId());
}
void WebSocketHandler::sendGpsPositionUsage(const WlsParticipant *participant)
{
if (!participant->isGpsMode())
return;
// trigger to notify to not use WlsPositions anymore
// see SendToDds::setPosition
forceSendPosition(participant, -2, -2);
}
template <typename T>
void WebSocketHandler::setPosition(T &tele, const WlsParticipant *participant)
{
tele.header.equipmentNumber = participant->equipmentNumber();
tele.header.timeStamp = participant->positionTimestamp();
const WlsParticipant::RelativePosition &pos = participant->position();
const QPointF lonLat(m_basePoint.geographic(pos.x, pos.y));
tele.geoPosition.lon = lonLat.x();
tele.geoPosition.lat = lonLat.y();
tele.geoPosition.z = pos.z;
const int moutObjectId =
m_ismHandler->buildingIdFromExternalWlsId(pos.wlsBuildingId);
const int moutRoomId =
m_ismHandler->roomIdFromRoomNumber(moutObjectId, pos.wlsRoomNumber);
//tele.opsPosition.moutAreaId = pos.wlsAreaId; // needed?
tele.opsPosition.moutObjectId = moutObjectId;
tele.opsPosition.moutRoomId = moutRoomId;
}
void WebSocketHandler::sendPosition(const WlsParticipant *participant)
{
INFO_LOG(m_logger) << "sendPosition equipmentNumber=" << participant->equipmentNumber();
if (!participant || !participant->positionIsValid() || participant->equipmentNumber() == 0)
{
INFO_LOG(m_logger) << "not send - participant is not yet fully initialized";
return;
}
if (participant->isGpsMode())
{
INFO_LOG(m_logger) << "not send - participant is in gps mode";
return;
}
TrainingAreaBase::TetraIcd::PositionReport posValue;
setPosition(posValue, participant);
sendTelegram(&posValue, participant->exerciseId());
}
void WebSocketHandler::forceSendPosition(const WlsParticipant *participant,
int moutObjectId, int moutRoomId)
{
// Convert Telegram to JSON-Object
TrainingAreaBase::TetraIcd::PositionReport posValue;
setPosition(posValue, participant);
posValue.opsPosition.moutObjectId = moutObjectId;
posValue.opsPosition.moutRoomId = moutRoomId;
sendTelegram(&posValue, participant->exerciseId());
}
} // namespace WirelessLocationSystem
+86
View File
@@ -0,0 +1,86 @@
/**
* Websocket interface to the training area proxy
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-11
*/
#pragma once
#include <QObject>
#include <QQueue>
#include <QWebSocketServer>
#include <memory>
#include <TrainingAreaBase/training_area_telegrams.h>
#include <LtsCoordTrans/lts_coord_trans.h>
#include "logger.h"
namespace WirelessLocationSystem
{
class IsmHandler;
class WebSocketWrapper;
class WlsHandler;
class WlsParticipant;
class WlsParticipantHandler;
class WebSocketHandler : public QObject
{
Q_OBJECT
public:
explicit WebSocketHandler(QObject *parent = nullptr);
~WebSocketHandler();
bool init(std::shared_ptr<WlsHandler> wlsHandler,
std::shared_ptr<WlsParticipantHandler> participantHandler,
std::shared_ptr<IsmHandler> ismHandler,
quint16 listenPort);
template <typename T>
bool sendTelegram(const T *data, int exerciseId)
{
if (!data)
return false;
sendTelegramInternal(data,
TrainingAreaBase::TetraIcd::Constraints<T>::type(),
exerciseId);
return true;
}
bool sendWlsStateReport(int exerciseId = 0);
public slots:
void sendBinarySfmData(const WlsParticipant *participant);
void sendDataCommunicationState(const WlsParticipant *participant);
void sendGpsPositionUsage(const WlsParticipant *participant);
void sendPosition(const WlsParticipant *participant);
void forceSendPosition(const WlsParticipant *participant,
int moutObjectId, int moutRoomId);
private slots:
void onNewConnection();
void onTextMessageReceived(const QString &baJson);
signals:
void signalBroadcastMessage(const QString &msg, int exerciseId);
private:
template <typename T>
void setPosition(T &tele, const WlsParticipant *participant);
void sendTelegramInternal(const TrainingAreaBase::TetraIcd::TrainingAreaTelegram *data,
const QString &type, int exerciseId);
void sendParticipantConnectionStates(int exerciseId);
QWebSocketServer *m_webSocketServer;
std::shared_ptr<WlsHandler> m_wlsHandler;
std::shared_ptr<IsmHandler> m_ismHandler;
std::shared_ptr<WlsParticipantHandler> m_participantHandler;
LtsCoordTrans::UtmCoordinate m_basePoint;
LOGGER_T m_logger;
};
} // namespace WirelessLocationSystem
+23
View File
@@ -0,0 +1,23 @@
/**
* Export definitions
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2017-01-13
*/
#pragma once
#include <QtCore/qglobal.h>
#ifndef WLS_SFM_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define WLS_SFM_EXPORT
# else
# if defined(MAKE_WLS_SFM_LIB)
# define WLS_SFM_EXPORT Q_DECL_EXPORT
# else
# define WLS_SFM_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
+527
View File
@@ -0,0 +1,527 @@
/**
* Convert from/to WLS protocol / handler incoming WLS connection
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#include "wls_handler.h"
#include "main_handler.h"
#include "settings.h"
#include "wls_sfm_message_converter.h"
#include "web_socket_handler.h"
#include <QTcpSocket>
#include <QDateTime>
#include <web_socket_handler.h>
namespace WirelessLocationSystem
{
static constexpr unsigned char s_startMark = 0x7F;
static constexpr unsigned char s_endMark = 0x8F;
static inline QByteArray escapeData(const QByteArray &bufferData)
{
QByteArray ret;
ret.reserve(bufferData.size() * 6 / 5);
for (const unsigned char c : bufferData)
{
if (c >= 0x20 && c < 0x7f)
ret += c;
else
ret += "\\x" + QByteArray::number(static_cast<unsigned char>(c), 16);
}
return ret;
}
WlsHandler::WlsHandler(QObject *parent)
: QObject(parent)
, m_logger(getLogger("WlsHandler"))
{
if (Settings::wlsMgrsBasePoint().size() <= 15)
WARNING_LOG(m_logger) << "WLS basepoint is not defined precise enough: " << Settings::wlsMgrsBasePoint();
}
WlsHandler::~WlsHandler()
{
delete m_tcpServer;
}
bool WlsHandler::init(WlsParticipantHandler *participantHandler,
WebSocketHandler *wsHandler,
quint16 listenPort)
{
m_participantHandler = participantHandler;
m_wsHandler = wsHandler;
INFO_LOG(m_logger) << "WlsHandler::init";
delete m_tcpServer;
m_tcpServer = new QTcpServer(this);
connect(m_tcpServer, &QTcpServer::newConnection,
this, &WlsHandler::onIncomingConnection);
if (!m_tcpServer->listen(QHostAddress::Any, listenPort))
{
WARNING_LOG(m_logger) << "Error listening on port " << listenPort;
return false;
}
m_wlsZOffset = Settings::wlsZOffset();
return true;
}
bool WlsHandler::sendWlsMessage(const WlsParticipant *p, const SfmMessage_tx &msg)
{
return sendWlsMessage(p->tagId(), msg);
}
bool WlsHandler::sendWlsMessage(const QByteArray &tagId, const SfmMessage_tx &msg)
{
return sendWlsMessage(tagId, WlsTelegramId::PayloadHostToMobile, SfmMessageConverter::generateTxMessage(msg));
}
bool WlsHandler::sendWlsMessage(const QByteArray &tagId, WlsTelegramId telId, const QByteArray &payload)
{
if (!isConnected())
return false;
if (telId != WlsTelegramId::PayloadHostToMobile)
return false;
auto itSocket = m_tcpSocketByTagId.constFind(tagId);
if (itSocket == m_tcpSocketByTagId.constEnd())
{
ERROR_LOG(m_logger) << "Can't find a TCP-socket for tag id: " << tagId;
return false;
}
const unsigned int fragmentParts = ((payload.size() + s_maxLenTransparentData - 1) / s_maxLenTransparentData);
const QDateTime dt = QDateTime::currentDateTimeUtc();
QByteArray data;
// start/end mark, 3*';', telId (can be 2 digits), timestring, \0
data.reserve(2 + 3 + 2 + 23 + tagId.size() + s_maxLenTransparentData + 1);
for (unsigned int i = 0; i < fragmentParts; i++)
{
data.clear();
data.append(s_startMark); // Startkennung
data.append(QByteArray::number(static_cast<int>(telId)));
data.append(';'); // Trennzeichen
data.append(dt.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz")).toLatin1()); // Aktuelle Zeit
data.append(';'); // Trennzeichen
data.append(tagId); // Tag-ID
data.append(';'); // Trennzeichen
data.append(payload.mid(i * s_maxLenTransparentData, s_maxLenTransparentData));
data.append(s_endMark); // Endekennung
(*itSocket)->write(data);
DEBUG_LOG(m_logger) << "Sending telegram to wls: " << escapeData(data);
}
(*itSocket)->flush();
return true;
}
void WlsHandler::onIncomingConnection()
{
INFO_LOG(m_logger) << "WlsHandler::onIncomingConnection";
QTcpSocket *tcpSocket = m_tcpServer->nextPendingConnection();
if (!tcpSocket)
return;
m_tcpSockets[tcpSocket].clear();
connect(tcpSocket, &QTcpSocket::readyRead,
this, &WlsHandler::onIncomingData);
connect(tcpSocket, &QTcpSocket::disconnected,
this, &WlsHandler::onSocketDisconnected);
// send wls connection state to all web sockets
TrainingAreaBase::TetraIcd::ProxyConnectionState connState;
connState.connectedToSystem = true;
m_wsHandler->sendTelegram(&connState, 0);
}
void WlsHandler::onIncomingData()
{
QTcpSocket *s = qobject_cast<QTcpSocket *>(sender());
if (!s)
return;
auto &tcpBuffer = m_tcpSockets[s];
const QByteArray bufferData = tcpBuffer + s->readAll();
TRACE_LOG(m_logger) << "WlsHandler::onIncomingData bufferData='" << escapeData(bufferData) << "'";
// returns the unparsed part of the buffer
tcpBuffer = parseBuffer(bufferData, s);
}
void WlsHandler::onSocketDisconnected()
{
QTcpSocket *s = qobject_cast<QTcpSocket *>(sender());
if (!s)
return;
INFO_LOG(m_logger) << "Socket disconnected";
m_tcpSockets.remove(s);
// remove tag ID to socket mappings
auto it = m_tcpSocketByTagId.begin();
while (it != m_tcpSocketByTagId.end())
if (it.value() == s)
it = m_tcpSocketByTagId.erase(it);
else
it++;
s->deleteLater();
// send wls connection state to all web sockets
TrainingAreaBase::TetraIcd::ProxyConnectionState connState;
connState.connectedToSystem = !m_tcpSockets.isEmpty();
m_wsHandler->sendTelegram(&connState, 0);
}
QByteArray WlsHandler::parseBuffer(const QByteArray &buffer, QTcpSocket *socket)
{
int iEndPos = 0;
int iCurReadPos = 0;
while (findNextSyncStartByte(buffer, iCurReadPos))
{
const int iStartPos = iCurReadPos;
if (!findNextSyncEndByte(buffer, iCurReadPos))
break;
iEndPos = iCurReadPos;
parseWlsTelegram(buffer.mid(iStartPos + 1, iEndPos - iStartPos - 1), socket);
}
return buffer.mid(iEndPos);
}
void WlsHandler::parseWlsTelegram(const QByteArray &baTele, QTcpSocket *socket)
{
const QList<QByteArray> parts = baTele.split(';');
if (parts.size() < 3)
return;
// insert/update socket for tagId
m_tcpSocketByTagId.insert(parts.at(2), socket);
const WlsTelegramId telegramId = static_cast<WlsTelegramId>(parts.at(0).toInt());
const QDateTime timestamp = QDateTime::fromString(QString::fromUtf8(parts.at(1)), QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz"));
if (telegramId != WlsTelegramId::PlayerPosition)
DEBUG_LOG(m_logger) << "Handling " << telegramId
<< ", data: '" << escapeData(baTele) << "'";
bool bOk = true;
switch (telegramId)
{
case WlsTelegramId::VersionInformation:
bOk = handleVersionsinformation(parts);
break;
case WlsTelegramId::PlayerPosition:
bOk = handlePlayerPosition(parts, timestamp);
break;
case WlsTelegramId::PlayerState:
bOk = handlePlayerState(parts);
break;
case WlsTelegramId::PayloadMobileToHost:
bOk = handleDataPayloadMobileToHost(parts);
break;
case WlsTelegramId::PayloadHostToMobile:
WARNING_LOG(m_logger) << "Protocol error - got a message which should not be sent from Wls (PayloadHostToMobile)";
break;
}
if (!bOk)
WARNING_LOG(m_logger) << "Error handling telegram: '" << baTele << "'";
}
bool WlsHandler::findNextSyncStartByte(const QByteArray &buffer, int &iCurReadPos)
{
// Suchen nach s_startMark
while (iCurReadPos < buffer.size())
{
const unsigned char data = buffer.at(iCurReadPos);
if (data == s_startMark)
return true;
++iCurReadPos;
}
return false;
}
bool WlsHandler::findNextSyncEndByte(const QByteArray &buffer, int &iCurReadPos)
{
// Suchen nach s_endMark
while (iCurReadPos < buffer.size())
{
const unsigned char data = buffer.at(iCurReadPos);
if (data == s_endMark)
return true;
++iCurReadPos;
}
return false;
}
bool WlsHandler::handleVersionsinformation(const QList<QByteArray> &data)
{
if (data.size() < 4)
return false;
DEBUG_LOG(m_logger) << "TX(WLS): Version = " << data.at(3);
return true;
}
bool WlsHandler::handlePlayerPosition(const QList<QByteArray> &data, const QDateTime &timestamp)
{
if (data.size() < 6)
return false;
WlsParticipant *p = participantFromTagId(data.at(2));
WlsParticipant::RelativePosition pos;
int iPos = 3;
if (!parseDoubleCm(data.at(iPos++), pos.x))
return false;
if (!parseDoubleCm(data.at(iPos++), pos.y))
return false;
if (!parseDoubleCm(data.at(iPos++), pos.z))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.wlsAreaId))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.wlsBuildingId))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.wlsRoomNumber))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.playerOrientation))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.weaponOrientation))
return false;
pos.z += m_wlsZOffset;
p->setPosition(pos, timestamp.toMSecsSinceEpoch());
INFO_LOG(m_logger) << p->getPlayerIdStr() << ": handlePlayerPosition"
<< " x=" << pos.x << ", y=" << pos.y << ", z=" << pos.z
<< ", wlsAreaId=" << pos.wlsAreaId
<< ", wlsBuildingId=" << pos.wlsBuildingId
<< ", wlsRoomNumber=" << pos.wlsRoomNumber
<< ", zone=" << p->wlsZone()
<< ", gpsMode=" << p->gpsMode()
<< ", dataCommunicationPossible: " << p->dataCommunication()
<< ", " << timestamp.toString();
return true;
}
bool WlsHandler::handlePlayerState(const QList<QByteArray> &data)
{
if (data.size() < 6)
return false;
WlsParticipant *p = participantFromTagId(data.at(2));
int timeout;
if (!parseInteger(data.at(3), timeout))
return false;
int tacticalState;
if (!parseInteger(data.at(4), tacticalState))
return false;
int voltage;
if (!parseInteger(data.at(5), voltage))
return false;
p->voltage = voltage / 10.; // 1/10V
INFO_LOG(m_logger) << p->getPlayerIdStr() << ": handlePlayerState"
<< " timeout=" << timeout
<< " ,tacticalState=" << tacticalState
<< " ,voltage=" << p->voltage;
return true;
}
bool WlsHandler::checkPayloadIsHex(const QByteArray &payload) const
{
for (const char c : payload)
{
if ((c < '0' || c > '9') &&
(c < 'a' || c > 'f') &&
(c < 'A' || c > 'F'))
{
WARNING_LOG(m_logger) << "Payload data is not hex coded ...";
return false;
}
}
return true;
}
bool WlsHandler::handleDataPayloadMobileToHost(const QList<QByteArray> &data)
{
if (data.size() < 4)
return false;
const QByteArray &payload = data.at(3);
const QByteArray &tagId = data.at(2);
if (!checkPayloadIsHex(payload))
return false;
QSharedPointer<SfmMessage_rx> msg = SfmMessageConverter::generateRxMessage(payload);
if (!msg)
{
WARNING_LOG(m_logger) << "Can not convert payload to SfmMessage ... ";
return false;
}
WlsParticipant *p = participantFromTagId(tagId, msg->getId() != SfmMessage::MsgId::EquipmentId);
p->setLastSeenWlsTelegramTime(QDateTime::currentMSecsSinceEpoch());
switch (msg->getId())
{
case SfmMessage::MsgId::EquipmentId:
handlePayload(p, static_cast<SfmMsgEquipmentId *>(msg.data()));
break;
case SfmMessage::MsgId::RequestGpsControl:
handlePayload(p, static_cast<SfmMsgRequestGpsControl_rx *>(msg.data()));
break;
case SfmMessage::MsgId::SfmRestartNotification:
handlePayload(p, static_cast<SfmMsgSfmRestart_rx *>(msg.data()));
break;
case SfmMessage::MsgId::Acknowledge:
handlePayload(p, static_cast<Acknowledge_rx *>(msg.data()));
break;
case SfmMessage::MsgId::TetraMessage:
handlePayload(p, static_cast<SfmMsgTetra_rx *>(msg.data()));
break;
case SfmMessage::MsgId::LicenseState:
case SfmMessage::MsgId::TacticalState:
default:
WARNING_LOG(m_logger) << "RX(WLS): MessageID(" << static_cast<int>(msg->getId()) << ")";
break;
}
return true;
}
void WlsHandler::handlePayload(WlsParticipant *participant, const SfmMsgEquipmentId *sfmTele)
{
const int equipmentNumber = sfmTele->getEquipmentNumber();
INFO_LOG(m_logger) << participant->getPlayerIdStr() << ": handlePayload"
<< " - new equipmentNumber = " << equipmentNumber;
if (participant->equipmentNumber() == 0 ||
participant->equipmentNumber() != equipmentNumber)
{
m_participantHandler->setEquipmentNumber(participant, equipmentNumber);
}
}
void WlsHandler::handlePayload(WlsParticipant *participant, const SfmMsgRequestGpsControl_rx *sfmTele)
{
Q_UNUSED(sfmTele);
sendGpsMode(participant);
}
void WlsHandler::handlePayload(WlsParticipant *participant, const SfmMsgSfmRestart_rx *sfmTele)
{
WARNING_LOG(m_logger) << "Restart notification ... " << participant->tagId();
Q_UNUSED(sfmTele)
participant->setWlsZone(-1);
// TODO: noch EXCON informieren?
}
void WlsHandler::handlePayload(WlsParticipant *participant, const Acknowledge_rx *sfmTele)
{
participant->handleWlsAck(sfmTele);
}
void WlsHandler::handlePayload(WlsParticipant *participant, const SfmMsgTetra_rx *sfmTele)
{
Acknowledge_tx msg;
if (!participant->handleSfmRxData(sfmTele))
msg.setFragmentError(participant->nextSfmRxDataFragmentCounter());
msg.setRecvCounter(sfmTele->sendCounter());
sendWlsMessage(participant->tagId(), msg);
}
void WlsHandler::sendGpsMode(WlsParticipant *participant)
{
if (!participant)
return;
SfmMsgGpsControl_tx msg;
msg.setGpsOn(participant->isGpsMode());
INFO_LOG(m_logger) << participant->getPlayerIdStr() << ": sendGpsMode"
<< " GpsMode = " << participant->isGpsMode();
sendWlsMessage(participant->tagId(), msg);
}
bool WlsHandler::parseDoubleCm(const QByteArray &data, double &num) const
{
bool bOk;
num = data.toDouble(&bOk);
if (!bOk)
{
WARNING_LOG(m_logger) << "Error parsing floating point number '" << data << "'";
return false;
}
num /= 100; // cm -> m
return true;
}
bool WlsHandler::parseInteger(const QByteArray &data, int &num) const
{
bool bOk;
num = data.toInt(&bOk);
if (!bOk)
{
WARNING_LOG(m_logger) << "Error parsing number '" << data << "'";
return false;
}
return true;
}
WlsParticipant *WlsHandler::participantFromTagId(const QByteArray &tagId, bool bAskForEqIdIfNew)
{
WlsParticipant *p = m_participantHandler->participantByTagId(tagId);
bool bRequestEquipmentNumber = false;
const qint64 tNow = QDateTime::currentMSecsSinceEpoch();
if (!p)
{
p = m_participantHandler->createNewParticipant(tagId);
bRequestEquipmentNumber = bAskForEqIdIfNew;
}
else
{
// Wenn noch keine EquipmentNumber gekommen ist oder die letzte Anfrage dazu älter als 10 Sekunden ist
if (p->equipmentNumber() <= 0 && p->lastEquipmentNumberRequest < tNow - 10 * 1000)
bRequestEquipmentNumber = true;
}
if (bRequestEquipmentNumber)
{
// TODO: wirklich nur wenn der Teilnehmer noch nicht vorhanden ist?
// was, wenn wir das Antworttelegramm nicht bekommen? Dann wird aktuell nie eine AusstgNr hinzugefügt
// -> Manfred Gronak fragen
SfmMsgRequestEquipmentId_tx getEqId;
INFO_LOG(m_logger) << "Requesting Equipment number for tag " << tagId;
sendWlsMessage(tagId, getEqId);
p->lastEquipmentNumberRequest = tNow;
}
return p;
}
std::ostream &operator<<(std::ostream &stream, WlsHandler::WlsTelegramId ti)
{
switch (ti)
{
case WlsHandler::WlsTelegramId::VersionInformation:
return stream << "WlsTelegramId::VersionInformation";
case WlsHandler::WlsTelegramId::PlayerPosition:
return stream << "WlsTelegramId::PlayerPosition";
case WlsHandler::WlsTelegramId::PlayerState:
return stream << "WlsTelegramId::PlayerState";
case WlsHandler::WlsTelegramId::PayloadMobileToHost:
return stream << "WlsTelegramId::PayloadMobileToHost";
case WlsHandler::WlsTelegramId::PayloadHostToMobile:
return stream << "WlsTelegramId::PayloadHostToMobile";
}
return stream << "Unknown WlsTelegramId (" << std::to_string(int(ti)) << ")";
}
} // namespace WirelessLocationSystem
+94
View File
@@ -0,0 +1,94 @@
/**
* Convert from/to WLS protocol / handler incoming WLS connection
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#pragma once
#include <QTcpServer>
#include <QTimer>
#include <memory>
#include "logger.h"
#include "wls_participant_handler.h"
#include "web_socket_handler.h"
#include "wls_sfm_message.h"
namespace WirelessLocationSystem
{
class MainHandler;
class WlsHandler : public QObject
{
Q_OBJECT
private:
enum class WlsTelegramId
{
VersionInformation = 0,
PlayerPosition = 1,
PlayerState = 2,
PayloadMobileToHost = 3,
PayloadHostToMobile = 4
};
friend std::ostream &operator<<(std::ostream &stream, WlsHandler::WlsTelegramId ti);
public:
static constexpr int s_maxLenTransparentData = 80;
explicit WlsHandler(QObject *parent = nullptr);
~WlsHandler();
bool init(WlsParticipantHandler *participantHandler,
WebSocketHandler *wsHandler,
quint16 listenPort);
bool isConnected() const
{
return !m_tcpSockets.isEmpty();
}
bool sendWlsMessage(const WlsParticipant *p, const SfmMessage_tx &msg);
bool sendWlsMessage(const QByteArray &tagId, const SfmMessage_tx &msg);
bool sendWlsMessage(const QByteArray &tagId, WlsTelegramId telId, const QByteArray &payload);
public Q_SLOTS:
void sendGpsMode(WlsParticipant *participant);
private Q_SLOTS:
void onIncomingConnection();
void onIncomingData();
void onSocketDisconnected();
private:
QByteArray parseBuffer(const QByteArray &buffer, QTcpSocket *socket);
void parseWlsTelegram(const QByteArray &baTele, QTcpSocket *socket);
static bool findNextSyncStartByte(const QByteArray &buffer, int &iCurReadPos);
static bool findNextSyncEndByte(const QByteArray &buffer, int &iCurReadPos);
bool checkPayloadIsHex(const QByteArray &payload) const;
bool handleVersionsinformation(const QList<QByteArray> &data);
bool handlePlayerPosition(const QList<QByteArray> &data, const QDateTime &timestamp);
bool handlePlayerState(const QList<QByteArray> &data);
bool handleDataPayloadMobileToHost(const QList<QByteArray> &data);
void handlePayload(WlsParticipant *participant, const SfmMsgEquipmentId *sfmTele);
void handlePayload(WlsParticipant *participant, const SfmMsgRequestGpsControl_rx *sfmTele);
void handlePayload(WlsParticipant *participant, const SfmMsgSfmRestart_rx *sfmTele);
void handlePayload(WlsParticipant *participant, const Acknowledge_rx *sfmTele);
void handlePayload(WlsParticipant *participant, const SfmMsgTetra_rx *sfmTele);
bool parseDoubleCm(const QByteArray &data, double &num) const;
bool parseInteger(const QByteArray &data, int &num) const;
WlsParticipant *participantFromTagId(const QByteArray &tagId, bool bAskForEqIdIfNew = true);
private:
QTcpServer *m_tcpServer = nullptr;
WlsParticipantHandler *m_participantHandler = nullptr;
WebSocketHandler *m_wsHandler = nullptr;
// one buffer per socket
QHash<QTcpSocket *, QByteArray> m_tcpSockets;
QHash<QByteArray, QTcpSocket *> m_tcpSocketByTagId;
double m_wlsZOffset = 0;
LOGGER_T m_logger;
};
} // namespace WirelessLocationSystem
@@ -0,0 +1,64 @@
/**
* Small test program to connect to the wls websocket
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#include "wls_json_client_test.h"
#include <QCoreApplication>
#include <QStringList>
#include <QString>
#include <QTimer>
WlsJsonclientTest::WlsJsonclientTest(const QString &hostname, unsigned short port)
: m_socket(new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this))
, m_hostname(hostname)
, m_port(port)
{
connect(m_socket, &QWebSocket::connected, this, &WlsJsonclientTest::onConnected);
connect(m_socket, &QWebSocket::disconnected, this, &WlsJsonclientTest::onClosed);
connect(m_socket, &QWebSocket::textMessageReceived, this, &WlsJsonclientTest::onTextMessageReceived);
QTimer::singleShot(0, this, &WlsJsonclientTest::run);
}
void WlsJsonclientTest::run()
{
const QUrl url(QStringLiteral("ws://%1:%2/").arg(m_hostname).arg(m_port));
qDebug("Trying to connect to %s", qPrintable(url.toString()));
m_socket->open(url);
}
void WlsJsonclientTest::onConnected()
{
qDebug("Connected to web socket server");
}
void WlsJsonclientTest::onClosed()
{
qWarning("Socket closed - quit app");
QCoreApplication::quit();
}
void WlsJsonclientTest::onTextMessageReceived(const QString &msg)
{
qDebug("Received text msg: '%s'", qPrintable(msg));
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
unsigned short port = 12346;
QString hostname = QStringLiteral("localhost");
if (app.arguments().size() > 1)
hostname = app.arguments().at(1);
if (app.arguments().size() > 2)
port = app.arguments().at(2).toUShort();
WlsJsonclientTest *w = new WlsJsonclientTest(hostname, port);
int ret = app.exec();
delete w;
return ret;
}
+29
View File
@@ -0,0 +1,29 @@
/**
* Small test program to connect to the wls websocket
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#pragma once
#include <QObject>
#include <QWebSocket>
class WlsJsonclientTest : public QObject
{
Q_OBJECT
public:
WlsJsonclientTest(const QString &hostname, unsigned short port);
protected Q_SLOTS:
void run();
void onConnected();
void onClosed();
void onTextMessageReceived(const QString &msg);
private:
QWebSocket *m_socket;
QString m_hostname;
unsigned short m_port;
};
+464
View File
@@ -0,0 +1,464 @@
/**
* A single Participant
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#include "wls_participant.h"
#include "wls_sfm_message.h"
#include "wls_handler.h"
#include "settings.h"
#include <QDateTime>
#include <LtsUtilsCore/dds_vector.h>
#include <LtsUtilsCore/sfm_bytemessage_converter.h>
static constexpr qint64 s_dataCommunicationAliveTimeout = 5 * 1000;
static constexpr int s_maxWlsRetryCount = 3;
namespace WirelessLocationSystem
{
WlsParticipant::WlsParticipant(QObject *parent)
: QObject(parent)
, m_wlsTeleTimeoutTimer(new QTimer(this))
, m_logger(getLogger("WlsParticipant"))
{
m_wlsTeleTimeoutTimer->setSingleShot(true);
m_wlsTeleTimeoutTimer->setTimerType(Qt::VeryCoarseTimer);
m_wlsTeleTimeoutTimer->setInterval(s_dataCommunicationAliveTimeout);
connect(m_wlsTeleTimeoutTimer, &QTimer::timeout, this, &WlsParticipant::onWlsTeleTimeout);
}
QByteArray WlsParticipant::getPlayerIdStr() const
{
QByteArray ret;
if (m_equipmentNumber > 0)
ret = QByteArray::number(m_equipmentNumber);
else
ret = "unknown";
if (m_tagId.isEmpty())
ret += "(unknown tag id)";
else
ret += "(" + m_tagId + ")";
return ret;
}
void WlsParticipant::setEquipmentNumber(int newEquipmentNumber)
{
if (m_equipmentNumber == newEquipmentNumber)
return;
m_equipmentNumber = newEquipmentNumber;
emit dataCommunicationStateChanged(this);
emit positionChanged(this);
}
void WlsParticipant::setPosition(const RelativePosition &newPosition,
qint64 tsCurrentPos)
{
if (tsCurrentPos == m_lastSeenPosition ||
(((tsCurrentPos - m_lastSeenPosition) <= Settings::deadPosTimeout()) && comparePos(m_relativePosition, newPosition)))// same timestamp or same pos for 5 sec
{
DEBUG_LOG(m_logger) << "Check for pos Update";
DEBUG_LOG(m_logger) << "Is Outdated= " <<((tsCurrentPos - m_lastSeenPosition) <= Settings::deadPosTimeout());
if (((tsCurrentPos - m_lastSeenPosition) <= Settings::deadPosTimeout()) && comparePos(m_relativePosition, newPosition))
{
DEBUG_LOG(m_logger) << "Same Position or not older than 5000msecs " << tsCurrentPos - m_lastSeenPosition;
return;
}
const qint64 tsNow = QDateTime::currentMSecsSinceEpoch();
if (tsCurrentPos == m_lastSeenPosition && m_lastValidPosition + 9 * 60 * 1000 > tsNow) // younger than 9 min -> ignore //should never be true as agilion always send with new timestamp
return; // position did not change
// did not move in the last 9min, send a new position report to excon
m_lastValidPosition = tsNow;
if (isDataCommunicationPossible())
emit positionChanged(this);
return;
}
const int newWlsZone = WlsParticipant::calcWlsZone(newPosition.wlsAreaId,
newPosition.wlsBuildingId,
newPosition.wlsRoomNumber);
setWlsZone(newWlsZone);
// to create a new position report on restart / after 9 min timeout
m_lastSeenPosition = tsCurrentPos;
m_lastValidPosition = tsCurrentPos;
m_relativePosition = newPosition;
m_relativePosition.isValid = true;
if (isDataCommunicationPossible())
emit positionChanged(this);
}
void WlsParticipant::checkGpsOnOffTimeoutTimer(qint64 tNow)
{
// full blown tetra mode -> nothing to do
if (m_gpsMode == GpsMode::On ||
m_dataCommunication == DataCommunication::Off)
return;
if (m_lastSeenDataCommunication > tNow - s_maxWlsRetryCount * s_dataCommunicationAliveTimeout)
return;
DEBUG_LOG(m_logger) << getPlayerIdStr() << ": lastSeenDataCommunication: "
<< m_lastSeenDataCommunication << ", now: "
<< (tNow - s_maxWlsRetryCount * s_dataCommunicationAliveTimeout)
<< " (" << tNow << "), "
<< m_dataCommunication << ", "
<< m_gpsMode;
// no alive from participant -> switch back to
if (m_dataCommunication == DataCommunication::On)
{
m_dataCommunication = DataCommunication::Off;
emit dataCommunicationStateChanged(this);
}
if (m_gpsMode == GpsMode::Off)
{
m_gpsMode = GpsMode::On;
emit gpsOnOffChanged(this);
}
m_wlsZone = 0;
}
void WlsParticipant::setLastSeenWlsTelegramTime(qint64 ts)
{
m_lastSeenDataCommunication = ts;
// current only allowed when wlsZone > 3
// m_relativePosition.dataCommunication = WlsParticipant::DataCommunication::On;
}
void WlsParticipant::setWlsZone(int newWlsZone)
{
const auto oldGpsMode = m_gpsMode;
const auto oldDataComm = m_dataCommunication;
if (newWlsZone >= 0) // < 0 == restart
{
if (WlsParticipant::mustSendGpsOff(m_wlsZone, newWlsZone))
m_gpsMode = GpsMode::Off;
else if (WlsParticipant::mustSendGpsOn(m_wlsZone, newWlsZone))
m_gpsMode = GpsMode::On;
m_dataCommunication = (newWlsZone > 3)
? DataCommunication::On
: DataCommunication::Off;
INFO_LOG(m_logger) << getPlayerIdStr() << ": wlsZone old: " << m_wlsZone << ", new: " << newWlsZone
<< ", new gps mode: " << m_gpsMode
<< ", new data comm mode: " << m_dataCommunication
<< ", " << this;
}
else
{
INFO_LOG(m_logger) << getPlayerIdStr() << ": Reset zone/data comunication status - sfm was restarted";
m_gpsMode = GpsMode::On;
m_dataCommunication = DataCommunication::Unknown;
m_lastSeenPosition = -1;
}
m_wlsZone = newWlsZone;
if (oldDataComm != m_dataCommunication)
{
if (m_dataCommunication == DataCommunication::On)
m_lastSeenDataCommunication = QDateTime::currentMSecsSinceEpoch();
emit dataCommunicationStateChanged(this);
}
if (oldGpsMode != m_gpsMode)
emit gpsOnOffChanged(this);
}
bool WlsParticipant::handleSfmRxData(const SfmMsgTetra_rx *sfmTele)
{
if (!isRecvCounterCorrect(sfmTele->sendCounter()))
{
WARNING_LOG(m_logger) << "Got data telegram with counter " << int(sfmTele->sendCounter())
<< " last one was " << int(m_lastRecvCounter) << " - ignoring";
return false;
}
m_bSfmPayloadReady = false;
if (sfmTele->isFragmented())
{
if (m_sfmRxPayloadFragmentNumber + 1 != sfmTele->fragmentNumber())
{
WARNING_LOG(m_logger) << getPlayerIdStr() << ": Missing a fragment (waiting for "
<< (m_sfmRxPayloadFragmentNumber + 1)
<< ", got " << sfmTele->fragmentNumber() << ")";
if (sfmTele->fragmentNumber() != 1)
return false;
// assume the last fragmented telegram did not came through, start over
m_sfmRxPayloadFragmentNumber = 0;
}
if (m_sfmRxPayloadFragmentNumber == 0) // first fragment - clear buffer
m_sfmRxPayloadData.clear();
++m_sfmRxPayloadFragmentNumber;
m_sfmRxPayloadData += sfmTele->payload();
m_bSfmPayloadReady = (sfmTele->fragmentCount() == sfmTele->fragmentNumber());
if (m_bSfmPayloadReady) // last fragment - reset fragment counter
m_sfmRxPayloadFragmentNumber = 0;
}
else
{
// not fragmented
if (m_sfmRxPayloadFragmentNumber != 0)
WARNING_LOG(m_logger) << getPlayerIdStr()
<< ": Got unfragmented telegram but currently waiting for next fragment of a fragmented telegram";
m_sfmRxPayloadFragmentNumber = 0; // assume the last fragmenation did not came through
m_sfmRxPayloadData = sfmTele->payload();
m_bSfmPayloadReady = true;
}
if (m_bSfmPayloadReady)
emit wlsBinarySfmDataComplete(this);
return true;
}
bool WlsParticipant::calcChunkCountAndLen(const QByteArray &data, int &chunkCount, int &chunkLen)
{
// hex as ascii -> / 2
// STX
// msgid (2bytes, current msgIds are all fine - no substitution needed),
// msg length is in message, we assume it must be subsituted (using 0x02 for checks)
// fragment byte is correctly calculated
// checksum byte (2 bytes worst case)
// ETX
constexpr int maxBytesPerChunk = (WlsHandler::s_maxLenTransparentData / 2) - (1 + 2 + 2 + 1);
chunkLen = 0;
chunkCount = 1;
bool bChunkCountIsOk = true;
QByteArray chunk;
for (int i = chunkCount; i < 16; ++i) // max. 15 chunks
{
bChunkCountIsOk = true;
chunkLen = (data.size() + i - 1) / i;
for (int j = 0; j < i; ++j)
{
const char fragByte = SfmMsgTetra_tx::fragmentByte(i, j + 1);
chunk.clear();
chunk += 0x02; // a send counter which needs to be substituted
chunk += fragByte;
chunk += data.mid(j * chunkLen, chunkLen);
const int payloadLen = LtsUtils::lenMaskedData(chunk);
if (payloadLen > maxBytesPerChunk)
{
bChunkCountIsOk = false;
break;
}
}
if (bChunkCountIsOk)
{
chunkCount = i;
break;
}
}
return bChunkCountIsOk;
}
void WlsParticipant::sendBinarySfmData(const std::vector<unsigned char> &data)
{
int chunkLen = 0;
int chunkCount = 0;
const QByteArray ba = LtsUtils::toQByteArray(data);
if (!calcChunkCountAndLen(ba, chunkCount, chunkLen))
{
WARNING_LOG(m_logger) << getPlayerIdStr()
<< ": Internal error calculating chunk size for payload size " << data.size();
return;
}
WlsPayloadData payload;
payload.fragmentCount = chunkCount;
payload.fragmentNumber = 0;
int iPos = 0;
while (iPos < ba.size())
{
++payload.fragmentNumber;
payload.payload = ba.mid(iPos, chunkLen);
m_wlsQueue.enqueue(payload);
iPos += chunkLen;
}
QTimer::singleShot(0, this, &WlsParticipant::checkSendTele);
}
void WlsParticipant::handleWlsAck(const Acknowledge_rx *ack)
{
// the ack recv counter must match our send counter
if (ack->recvCounter() != m_nextSendCounter)
{
WARNING_LOG(m_logger) << "Got ack for telegram with counter " << int(ack->recvCounter())
<< " but expected " << int(m_nextSendCounter) << " - ignoring";
return;
}
if (ack->isOk())
{
if (m_wlsQueue.isEmpty())
{
ERROR_LOG(m_logger) << "got an ack but nothing was sent - something went terribly wrong - resetting send/recv counter";
m_nextSendCounter = 0;
m_lastRecvCounter = 0;
return;
}
const WlsPayloadData &h = m_wlsQueue.head();
if (h.fragmentCount > 1)
{
if (h.fragmentCount == h.fragmentNumber)
m_currentFragmentedTeleQueue.clear(); // sent complete fragmented telegram - can throw away the segments
else
m_currentFragmentedTeleQueue.enqueue(h); // save for possible resend
}
m_wlsQueue.dequeue();
m_wlsTeleTimeoutTimer->stop();
}
else
{
// if we sent a fragmented telegram then we've to start over from the beginning
while (!m_currentFragmentedTeleQueue.isEmpty())
m_wlsQueue.push_front(m_currentFragmentedTeleQueue.dequeue());
}
checkSendTele();
}
void WlsParticipant::checkSendTele()
{
if (m_wlsTeleTimeoutTimer->isActive())
return;
sendNextWlsTelegram(false);
}
void WlsParticipant::sendNextWlsTelegram(bool bIsResend)
{
if (m_wlsQueue.isEmpty())
return;
if (m_dataCommunication == WlsParticipant::DataCommunication::Off)
{
if (bIsResend)
{
// when we try to resend due to timeout but now we know that there is no
// data communication anymore -> throw away
m_wlsQueue.clear();
return;
}
WARNING_LOG(m_logger) << getPlayerIdStr()
<< ": Trying to send a wls telegram but no data communication possible";
// should not happen so leave the assertion to find out the reasons for it
Q_ASSERT(false);
m_wlsQueue.clear();
return;
}
const auto &tele = m_wlsQueue.head();
SfmMsgTetra_tx tx(getNextSendCounter(bIsResend),
tele.fragmentCount,
tele.fragmentNumber,
tele.payload);
emit dataForWls(this, tx);
if (!bIsResend)
m_wlsTeleTimeoutCounter = 0;
m_wlsTeleTimeoutTimer->start();
}
void WlsParticipant::onWlsTeleTimeout()
{
++m_wlsTeleTimeoutCounter;
DEBUG_LOG(m_logger) << getPlayerIdStr()
<< ": Resend WLS connection to participant " << m_equipmentNumber
<< "(" << m_tagId << ")"
<< " m_wlsTeleTimeoutCounter =" << m_wlsTeleTimeoutCounter
<< " s_maxWlsRetryCount=" << s_maxWlsRetryCount;
if (m_wlsTeleTimeoutCounter == s_maxWlsRetryCount)
{
WARNING_LOG(m_logger) << getPlayerIdStr()
<< ": Lost WLS connection to participant " << m_equipmentNumber
<< "(" << m_tagId << ")";
m_lastSeenDataCommunication = -1;
m_wlsTeleTimeoutCounter = 0;
m_nextSendCounter = 0;
m_lastRecvCounter = 0;
checkGpsOnOffTimeoutTimer(QDateTime::currentMSecsSinceEpoch());
return;
}
sendNextWlsTelegram(true);
}
uint8_t WlsParticipant::getNextSendCounter(bool bIsResend)
{
if (bIsResend)
return m_nextSendCounter;
++m_nextSendCounter;
if (m_nextSendCounter == 0)
++m_nextSendCounter;
return m_nextSendCounter;
}
bool WlsParticipant::isRecvCounterCorrect(uint8_t recvCounter)
{
// same as in tetra - the counter may not be monotonically ascending ...
bool bRet = ((recvCounter == 0 && m_lastRecvCounter == 0) ||
recvCounter != m_lastRecvCounter);
m_lastRecvCounter = recvCounter;
return bRet;
}
TrainingAreaBase::TetraIcd::WlsParticipantState WlsParticipant::wlsParticipantState() const
{
TrainingAreaBase::TetraIcd::WlsParticipantState ps;
ps.equipmentNumber = m_equipmentNumber;
ps.inBlockedArea = false;
ps.gpsOn = isGpsMode();
ps.lastSeen = m_lastSeenDataCommunication;
ps.tacticalState = -1;
ps.tagId = QString::fromLatin1(tagId());
ps.voltage = voltage;
ps.relativePosition.x = m_relativePosition.x;
ps.relativePosition.y = m_relativePosition.y;
ps.relativePosition.z = m_relativePosition.z;
ps.relativePosition.wlsAreaId = m_relativePosition.wlsAreaId;
ps.relativePosition.wlsZone = m_wlsZone;
ps.relativePosition.wlsBuildingId = m_relativePosition.wlsBuildingId;
ps.relativePosition.wlsRoomNumber = m_relativePosition.wlsRoomNumber;
ps.relativePosition.playerOrientation = m_relativePosition.playerOrientation;
ps.relativePosition.weaponOrientation = m_relativePosition.weaponOrientation;
ps.relativePosition.isValid = m_relativePosition.isValid;
return ps;
}
std::ostream &operator<<(std::ostream &stream, WlsParticipant::GpsMode gm)
{
switch (gm)
{
case WlsParticipant::GpsMode::On:
return stream << "GpsMode::On";
case WlsParticipant::GpsMode::Off:
return stream << "GpsMode::Off";
}
return stream << "Unknown GpsMode (" << std::to_string(int(gm)) << ")";
}
std::ostream &operator<<(std::ostream &stream, WlsParticipant::DataCommunication dc)
{
switch (dc)
{
case WlsParticipant::DataCommunication::Unknown:
return stream << "DataCommunication::Unknown";
case WlsParticipant::DataCommunication::On:
return stream << "DataCommunication::On";
case WlsParticipant::DataCommunication::Off:
return stream << "DataCommunication::Off";
}
return stream << "Unknown DataCommunication (" << std::to_string(int(dc)) << ")";
}
} // namespace WirelessLocationSystem
+206
View File
@@ -0,0 +1,206 @@
/**
* A single Participant
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#pragma once
#include <QByteArray>
#include <QObject>
#include <QQueue>
#include <QStringBuilder>
#include <QTimer>
//#include <QDebug>
#include <math.h>
#include <TrainingAreaBase/training_area_telegrams.h>
#include "logger.h"
#include "settings.h"
inline Q_CONSTEXPR double pi() { return M_PI; }
inline Q_CONSTEXPR double deg2rad() { return pi() / 180.; }
namespace WirelessLocationSystem
{
class Acknowledge_rx;
class SfmMsgTetra_rx;
class SfmMsgTetra_tx;
class WlsParticipant : public QObject
{
Q_OBJECT
public:
static int calcWlsZone(int wlsAreaId, int wlsBuildingId, int wlsRoomNumber)
{
if (wlsAreaId == 0 && wlsBuildingId == 0 && wlsRoomNumber == 0)
return 3;
if (wlsAreaId != 0 && wlsBuildingId == 1002 && wlsRoomNumber == 1001)
return 4;
if (wlsAreaId != 0 && wlsBuildingId == 0 && wlsRoomNumber == 0)
return 5;
return 6;
}
static bool mustSendGpsOn(int wlsZoneOld, int wlsZoneNew)
{
// restart of service
if (wlsZoneOld == -1)
return wlsZoneNew <= 4;
// normal operation
return (wlsZoneOld >= 4 && wlsZoneNew <= 3);
}
static bool mustSendGpsOff(int wlsZoneOld, int wlsZoneNew)
{
// restart of service
if (wlsZoneOld == -1)
return wlsZoneNew >= 5;
// normal operation
return (wlsZoneOld <= 4 && wlsZoneNew >= 5);
}
enum class GpsMode : char
{
Off,
On
};
enum class DataCommunication : char
{
Unknown,
Off,
On
};
struct RelativePosition
{
double x = 0;
double y = 0;
double z = 0;
int wlsAreaId = 0;
int wlsBuildingId = 0; // = externalWlsId
int wlsRoomNumber = 0;
int playerOrientation = 0;
int weaponOrientation = 0;
bool isValid = false;
} ;
static bool comparePos (const RelativePosition &rhs, const RelativePosition &lhs )
{
double dx = ((lhs.x/deg2rad())-(rhs.x/deg2rad()));
double dy = ((lhs.y/deg2rad())-rhs.y/deg2rad());
double dz = (lhs.z-rhs.z);
double dist = dx*dx + dy*dy + dz*dz;
//qWarning() << "Compare two pos: square distance= " << QString::number(dist,'g',10);
bool ret = dist < Settings::wlsSquareDistanceMax()
&& lhs.wlsAreaId == rhs.wlsAreaId
&& lhs.wlsBuildingId == rhs.wlsBuildingId
&& lhs.wlsRoomNumber == rhs.wlsRoomNumber;
// && lhs.isValid == rhs.isValid;
//qWarning() << "Return Value= " << ret;
return ret;
}
protected:
explicit WlsParticipant(QObject *parent = nullptr);
public:
void setPosition(const RelativePosition &newPosition, qint64 tsCurrentPos);
bool positionIsValid() const { return m_relativePosition.isValid; }
qint64 positionTimestamp() const { return m_lastValidPosition; }
RelativePosition position() const { return m_relativePosition; }
bool isGpsMode() const { return m_gpsMode == WlsParticipant::GpsMode::On; }
// returns true when timeout is reached
void checkGpsOnOffTimeoutTimer(qint64 tNow);
void setLastSeenWlsTelegramTime(qint64 ts);
qint64 lastSeenWlsTelegramTime() const { return m_lastSeenDataCommunication; }
void setWlsZone(int newWlsZone);
int wlsZone() const { return m_wlsZone; }
QByteArray getPlayerIdStr() const;
const QByteArray &tagId() const { return m_tagId; }
void setEquipmentNumber(int newEquipmentNumber);
int equipmentNumber() const { return m_equipmentNumber; }
int exerciseId() const { return m_exerciseId; }
void setExerciseId(int exerciseId) { m_exerciseId = exerciseId; }
bool isDataCommunicationPossible() const { return m_dataCommunication == DataCommunication::On; }
DataCommunication dataCommunication() const { return m_dataCommunication; }
GpsMode gpsMode() const { return m_gpsMode; }
bool handleSfmRxData(const SfmMsgTetra_rx *sfmTele);
bool isSfmRxDataAvailable() const { return m_bSfmPayloadReady; }
uint8_t nextSfmRxDataFragmentCounter() const { return m_sfmRxPayloadFragmentNumber; }
const QByteArray &sfmRxData() const { return m_sfmRxPayloadData; }
static bool calcChunkCountAndLen(const QByteArray &data, int &chunkCount, int &chunkLen);
void sendBinarySfmData(const std::vector<unsigned char> &data);
void handleWlsAck(const Acknowledge_rx *ack);
TrainingAreaBase::TetraIcd::WlsParticipantState wlsParticipantState() const;
// TODO
double voltage = 0;
qint64 lastEquipmentNumberRequest = 0;
Q_SIGNALS:
void positionChanged(WlsParticipant *p);
void gpsOnOffChanged(WlsParticipant *p);
void wlsBinarySfmDataComplete(WlsParticipant *p);
void dataCommunicationStateChanged(WlsParticipant *p);
void dataForWls(WlsParticipant *p, const SfmMsgTetra_tx &data);
private Q_SLOTS:
void checkSendTele();
void sendNextWlsTelegram(bool bIsResend);
void onWlsTeleTimeout();
private:
uint8_t getNextSendCounter(bool bIsResend);
bool isRecvCounterCorrect(uint8_t recvCounter);
private:
struct WlsPayloadData
{
QByteArray payload;
uint8_t retryCount = 0;
uint8_t fragmentCount = 0;
uint8_t fragmentNumber = 0;
};
friend class WlsParticipantHandler;
QByteArray m_tagId;
QByteArray m_sfmRxPayloadData;
QQueue<WlsPayloadData> m_currentFragmentedTeleQueue;
QQueue<WlsPayloadData> m_wlsQueue;
uint8_t m_sfmRxPayloadFragmentNumber = 0;
bool m_bSfmPayloadReady = false;
int m_equipmentNumber = 0;
int m_exerciseId = 0;
// zone 1 and 2 will never be met here - far outside wls area
int m_wlsZone = -1; // 1 .. 6
RelativePosition m_relativePosition;
GpsMode m_gpsMode = GpsMode::On;
DataCommunication m_dataCommunication = DataCommunication::Unknown;
qint64 m_lastSeenDataCommunication = 0;
qint64 m_lastValidPosition = 0;
qint64 m_lastSeenPosition = 0;
QTimer *m_wlsTeleTimeoutTimer = nullptr;
int m_wlsTeleTimeoutCounter = 0;
uint8_t m_nextSendCounter = 0;
uint8_t m_lastRecvCounter = 0;
NGL::INGLLogger &m_logger;
};
std::ostream &operator<<(std::ostream &stream, WlsParticipant::GpsMode gm);
std::ostream &operator<<(std::ostream &stream, WlsParticipant::DataCommunication dc);
} // namespace WirelessLocationSystem
@@ -0,0 +1,116 @@
/**
* Handle Participants which are available in the system (map between Wls id and ParticipantId)
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#include "wls_participant_handler.h"
#include "web_socket_handler.h"
#include "wls_handler.h"
#include <QDateTime>
namespace WirelessLocationSystem
{
WlsParticipantHandler::WlsParticipantHandler(QObject *parent)
: QObject(parent)
, m_gpsOnOffTimeoutTimer(new QTimer(this))
, m_logger(getLogger("WlsParticipantHandler"))
{
connect(m_gpsOnOffTimeoutTimer, &QTimer::timeout, this, &WlsParticipantHandler::onGpsOnOffTimeoutTimer);
m_gpsOnOffTimeoutTimer->setSingleShot(false);
m_gpsOnOffTimeoutTimer->start(1000);
}
WlsParticipantHandler::~WlsParticipantHandler()
{
qDeleteAll(m_participantsByEqId);
}
void WlsParticipantHandler::init(WebSocketHandler *wsHandler,
WlsHandler *wlsHandler)
{
m_wsHandler = wsHandler;
m_wlsHandler = wlsHandler;
}
WlsParticipant *WlsParticipantHandler::createNewParticipant(const QByteArray &tagId)
{
delete m_participantsByTagId.take(tagId);
WlsParticipant *p = new WlsParticipant;
connect(p, &WlsParticipant::gpsOnOffChanged,
m_wlsHandler, &WlsHandler::sendGpsMode);
connect(p, &WlsParticipant::dataForWls,
m_wlsHandler, QOverload<const WlsParticipant *,const SfmMessage_tx &>::of(&WlsHandler::sendWlsMessage));
connect(p, &WlsParticipant::gpsOnOffChanged,
m_wsHandler, &WebSocketHandler::sendGpsPositionUsage);
connect(p, &WlsParticipant::positionChanged,
m_wsHandler, &WebSocketHandler::sendPosition);
connect(p, &WlsParticipant::wlsBinarySfmDataComplete,
m_wsHandler, &WebSocketHandler::sendBinarySfmData);
connect(p, &WlsParticipant::dataCommunicationStateChanged,
m_wsHandler, &WebSocketHandler::sendDataCommunicationState);
p->m_tagId = tagId;
m_participantsByTagId[tagId] = p;
return p;
}
void WlsParticipantHandler::setEquipmentNumber(WlsParticipant *participant, int equipmentNumber)
{
participant->setExerciseId(m_equipNo2ExerciseId.value(equipmentNumber));
auto it = m_participantsByEqId.find(participant->equipmentNumber());
// remove old assignment
if (it != m_participantsByEqId.end())
m_participantsByEqId.erase(it);
participant->setEquipmentNumber(equipmentNumber);
m_participantsByEqId[equipmentNumber] = participant;
}
WlsParticipant *WlsParticipantHandler::participantByTagId(const QByteArray &tagId) const
{
return m_participantsByTagId.value(tagId);
}
void WlsParticipantHandler::exerciseEquipmentAssignmentChanged(const std::unordered_map<int, std::unordered_set<int>> &eea)
{
m_equipNo2ExerciseId.clear();
for (const auto &itEx : eea)
{
const int curExerciseId = itEx.first;
const auto &assignment = itEx.second;
for (int eqId : assignment)
{
m_equipNo2ExerciseId[eqId] = curExerciseId;
WlsParticipant *p = m_participantsByEqId.value(eqId);
if (!p)
continue;
p->setExerciseId(curExerciseId);
}
}
}
void WlsParticipantHandler::handleWlsBinarySfmData(const TrainingAreaBase::TetraIcd::WlsBinarySfmData &data)
{
WlsParticipant *p = m_participantsByEqId.value(data.header.equipmentNumber);
if (!p)
{
WARNING_LOG(m_logger) << "Got binary sfm data for participant with eqId " << data.header.equipmentNumber
<< " but could not find this participant!";
return;
}
p->sendBinarySfmData(data.payload);
}
void WlsParticipantHandler::onGpsOnOffTimeoutTimer()
{
const qint64 tNow = QDateTime::currentMSecsSinceEpoch();
for (WlsParticipant *p : qAsConst(m_participantsByTagId))
p->checkGpsOnOffTimeoutTimer(tNow);
}
} // namespace WirelessLocationSystem
@@ -0,0 +1,57 @@
/**
* Handle Participants which are available in the system (map between Wls id and ParticipantId)
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#pragma once
#include "logger.h"
#include "wls_participant.h"
#include <QHash>
#include <QObject>
#include <QTimer>
#include <unordered_map>
#include <unordered_set>
namespace WirelessLocationSystem
{
class WebSocketHandler;
class WlsHandler;
class WlsParticipantHandler : public QObject
{
Q_OBJECT
public:
explicit WlsParticipantHandler(QObject *parent = nullptr);
~WlsParticipantHandler();
void init(WebSocketHandler *wsHandler,
WlsHandler *wlsHandler);
// ownership stays here
WlsParticipant *createNewParticipant(const QByteArray &tagId);
void setEquipmentNumber(WlsParticipant *participant, int equipmentNumber);
WlsParticipant *participantByTagId(const QByteArray &tagId) const;
const QHash<QByteArray, WlsParticipant *> &participants() const { return m_participantsByTagId; }
void exerciseEquipmentAssignmentChanged(const std::unordered_map<int, std::unordered_set<int>> &eea);
void handleWlsBinarySfmData(const TrainingAreaBase::TetraIcd::WlsBinarySfmData &data);
private Q_SLOTS:
void onGpsOnOffTimeoutTimer();
private:
WebSocketHandler *m_wsHandler = nullptr;
WlsHandler *m_wlsHandler = nullptr;
QHash<QByteArray, WlsParticipant *> m_participantsByTagId;
QHash<int, WlsParticipant *> m_participantsByEqId;
QHash<int, int> m_equipNo2ExerciseId;
QTimer *m_gpsOnOffTimeoutTimer;
NGL::INGLLogger &m_logger;
};
} // namespace WirelessLocationSystem
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<service namespace="WirelessLocationSystem" procNames="WirelessLocationSystem" abbreviation="WLS" exerciseDependency="Global" groups="Core">
<rcs>
<Id Id="$Id: ea_export_script.txt 28914 2016-08-23 12:08:05Z ehrlicher $"/>
<HeadURL HeadURL="$HeadURL: svn://slssvn/TDS/branches/product/devel/trunk/Lts/Common/CodeGen/ea_export_script.txt $"/>
<Author Author="$Author: ehrlicher $"/>
</rcs>
<tables/>
<dbServiceStructs/>
<topicTypes>
<topicType name="dummyStruct">
<column name="id" idlType="long"/>
</topicType>
</topicTypes>
<functionTopics/>
<enumerations>
<enumeration name="dummyEnum">
<attribute key="unknown" value=""/>
</enumeration>
</enumerations>
</service>
+100
View File
@@ -0,0 +1,100 @@
/**
* Messages from/to a SFM (passed through Wls message PayloadMobileToHost / PayloadHostToMobile
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#include "wls_sfm_message.h"
namespace WirelessLocationSystem
{
/// -------------- SfmMsgGpsControl_tx --------------
QByteArray SfmMsgGpsControl_tx::getPayload() const
{
QByteArray payload;
payload.append(isGpsOn() ? 1 : 0);
return payload;
}
/// ---------- SfmMsgEquipmentId_rx ----------
bool SfmMsgEquipmentId::setPayload(const QByteArray &p_payload)
{
if (p_payload.size() != s_payloadSize)
return false;
m_equipmentNumber = p_payload[0] << 8;
m_equipmentNumber |= ((unsigned short)p_payload[1]) & 0xFF;
return true;
}
QByteArray SfmMsgEquipmentId::getPayload() const
{
QByteArray retVal(3, Qt::Uninitialized);
retVal[0] = (m_equipmentNumber >> 8) & 0xFF;
retVal[1] = m_equipmentNumber & 0xFF;
retVal[2] = 0;
return retVal;
}
/// ---------- SfmMsgTacticalState_rx ----------
bool SfmMsgTacticalState_rx::setPayload(const QByteArray &p_payload)
{
if (p_payload.size() != s_payloadSize)
return false;
m_tacticalState = p_payload[0];
return true;
}
/// ---------- SfmMsgTetra ----------
bool SfmMsgTetra_rx::setPayload(const QByteArray &p_data)
{
if (p_data.size() < 3)
return false;
m_sendCounter = p_data[0];
m_fragmentByte = p_data[1];
m_payload = p_data.mid(2);
return true;
}
// ---------- Acknowledge_rx ----------
bool Acknowledge_rx::setPayload(const QByteArray &p_data)
{
if (p_data.size() != s_payloadSize)
return false;
m_recvCounter = p_data[0];
m_ackState = p_data[1];
return true;
}
// ---------- Acknowledge_tx ----------
QByteArray Acknowledge_tx::getPayload() const
{
QByteArray ba(2, Qt::Uninitialized);
ba[0] = m_recvCounter;
ba[1] = m_ackState;
return ba;
}
/// ---------- SfmMsgLizensStatus ----------
bool SfmMsgLicenseState_rx::setPayload(const QByteArray &p_data)
{
if (p_data.size() != s_payloadSize)
return false;
if (p_data[0] != 0 && p_data[0] != 1)
return false;
m_isLicensed = p_data[0] ? true : false;
return true;
}
} // namespace WirelessLocationSystem
+370
View File
@@ -0,0 +1,370 @@
/**
* Messages from/to a SFM (passed through Wls message PayloadMobileToHost / PayloadHostToMobile
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#pragma once
#include <WirelessLocationSystem/sfmconverter_export.h>
#include <QByteArray>
namespace WirelessLocationSystem
{
/**
* \class SfmMessage
*
* \brief Oberklasse fuer Nachrichten von/an das SFM
*
* \author B. Ziegler
*/
class SfmMessage
{
public:
enum class MsgId
{
TacticalState = 37,
SfmRestartNotification = 76,
GpsControl = 79,
RequestGpsControl = 79 | 0x8000, // RTR bit set
EquipmentId = 478,
RequestEquipmentId = 478 | 0x8000, // RTR bit set
Acknowledge = 1098,
LicenseState = 1099,
TetraMessage = 1100
};
protected:
explicit SfmMessage(MsgId id)
: m_id(id)
{}
public:
virtual ~SfmMessage() = default;
MsgId getId() const { return m_id; }
private:
MsgId m_id;
};
/// --------------SfmMessage_tx--------------
/**
* \class SfmMessage_tx
*
* \brief Oberklasse fuer sendbare Nachrichten
*
* \author B. Ziegler
*/
class SfmMessage_tx : public SfmMessage
{
public:
using SfmMessage::SfmMessage;
/**
* \brief Interface zur Abfrage der Payload
*
* \author B. Ziegler
*
* Diese Methode gibt der reine Payload der Nachricht wieder.
* Default: Leeres ByteArray (keine Parameter)
*
* \return Ein ByteArray welches die vollstaendige Payload enthaelt
*/
virtual QByteArray getPayload() const = 0;
};
/// --------------SfmMessage_rx--------------
/**
* \class SfmMessage_rx
*
* \brief Oberklasse fuer empfangbare Nachrichten
*
* \author B. Ziegler
*/
class SfmMessage_rx : public SfmMessage
{
public:
using SfmMessage::SfmMessage;
/**
* \brief Interface zur Püfung und Aufschlüsselung der Payload
*
* \author B. Ziegler
*
* Standard Implementierung geht von einer leeren Payload aus
*
* \param payload Die zu verarbeitende Payload
*
* \return true: Payload konnte korrekt verarbeitet werden
*/
virtual bool setPayload(const QByteArray &payload) = 0;
};
/// ########## Specific Messages ##########
/// ---------- SfmMsgGpsControl_tx ----------
/**
* \class SfmMsgGpsControl_tx
*
* \brief Setzen der GPS-Control
*
* \author B. Ziegler
*/
class SfmMsgGpsControl_tx final : public SfmMessage_tx
{
public:
explicit SfmMsgGpsControl_tx(bool gpsOn = false)
: SfmMessage_tx(SfmMessage::MsgId::GpsControl)
, m_gpsOn(gpsOn)
{}
QByteArray getPayload() const override;
void setGpsOn(bool bOn) { m_gpsOn = bOn; }
bool isGpsOn() const { return m_gpsOn; }
private:
bool m_gpsOn;
};
/// ---------- SfmMsgRequestGpsControl_rx ----------
/**
* \class SfmMsgRequestGpsControl_rx
*
* \brief Request from SFM to get current GPS mode
*
* description
*
* \author Ch. Ehrlicher
*/
class SfmMsgRequestGpsControl_rx final : public SfmMessage_rx
{
public:
SfmMsgRequestGpsControl_rx()
: SfmMessage_rx(SfmMessage::MsgId::RequestGpsControl)
{}
bool setPayload(const QByteArray &payload) override
{
return (payload.size() == 0);
}
};
/// ---------- SfmMsgRequestEquipmentId_tx ----------
/**
* \class SfmMsgRequestEquipmentId_tx
*
* \brief Anfrage der AusstattungsNr
*
* description
*
* \author B. Ziegler
*/
class SfmMsgRequestEquipmentId_tx final : public SfmMessage_tx
{
public:
SfmMsgRequestEquipmentId_tx()
: SfmMessage_tx(SfmMessage::MsgId::RequestEquipmentId)
{}
QByteArray getPayload() const override
{
return QByteArray();
}
};
/// ---------- SfmMsgEquipmentId_rx ----------
/**
* \class SfmMsgEquipmentId_rx
*
* \brief Rueckgabe der AusstattungsNr
*
* \author B. Ziegler
*/
class SFMCONVERTER_EXPORT SfmMsgEquipmentId final : public SfmMessage_rx, public SfmMessage_tx
{
public:
SfmMsgEquipmentId()
: SfmMessage_rx(SfmMessage::MsgId::EquipmentId)
, SfmMessage_tx(SfmMessage::MsgId::EquipmentId)
{}
bool setPayload(const QByteArray &) override;
QByteArray getPayload() const override;
static constexpr int s_payloadSize = 3;
unsigned short getEquipmentNumber() const
{
return m_equipmentNumber;
}
void setEquipmentNumber(quint16 equipmentNumber) { m_equipmentNumber = equipmentNumber; }
private:
unsigned short m_equipmentNumber = 0;
};
/// ---------- SfmMsgSfmRestart_rx ----------
/**
* \class SfmMsgSfmRestart_rx
*
* \brief Anzeigen eines SFM-Restarts
*
* \author B. Ziegler
*/
class SfmMsgSfmRestart_rx final : public SfmMessage_rx
{
public:
SfmMsgSfmRestart_rx()
: SfmMessage_rx(SfmMessage::MsgId::SfmRestartNotification)
{}
bool setPayload(const QByteArray &payload) override
{
return (payload.size() == 0);
}
};
/// ---------- SfmMsgTacticalState_rx ----------
/**
* \class SfmMsgTacticalState_rx
*
* \brief Setzen des VBGs eines Teilnehmers
*
* \author B. Ziegler
*/
class SfmMsgTacticalState_rx final : public SfmMessage_rx
{
public:
SfmMsgTacticalState_rx()
: SfmMessage_rx(SfmMessage::MsgId::TacticalState)
{}
static constexpr int s_payloadSize = 1;
bool setPayload(const QByteArray &) override;
unsigned char getTacticalState() const { return m_tacticalState; }
private:
unsigned char m_tacticalState = 0;
};
/// ---------- SfmMsgTetra ----------
/**
* \class SfmMsgTetra
*
* \brief Eine Tetra-Nachricht die zur Zentrale weitergereicht werden muss
*
* \author B. Ziegler
*/
class SfmMsgTetra_rx final : public SfmMessage_rx
{
public:
SfmMsgTetra_rx()
: SfmMessage_rx(SfmMessage::MsgId::TetraMessage)
{}
bool setPayload(const QByteArray &p_data) override;
uint8_t sendCounter() const { return m_sendCounter; }
bool isFragmented() const { return fragmentCount() > 1; }
uint8_t fragmentNumber() const { return m_fragmentByte & 0x0f; }
uint8_t fragmentCount() const { return (m_fragmentByte & 0xf0) >> 4;}
const QByteArray &payload() const { return m_payload; }
private:
QByteArray m_payload;
uint8_t m_sendCounter = 0;
uint8_t m_fragmentByte = 0;
};
class SfmMsgTetra_tx final : public SfmMessage_tx
{
public:
explicit SfmMsgTetra_tx(uint8_t sendCounter, int fragmentCount, int fragmentNumber, const QByteArray &data)
: SfmMessage_tx(SfmMessage::MsgId::TetraMessage)
{
m_data.reserve(data.size() + 2);
m_data += sendCounter;
m_data += fragmentByte(fragmentCount, fragmentNumber);
m_data += data;
}
static char fragmentByte(int fragmentCount, int fragmentNumber)
{
Q_ASSERT(fragmentCount >= 0 && fragmentCount < 16);
Q_ASSERT(fragmentNumber >= 0 && fragmentNumber <= fragmentCount);
return ((fragmentCount & 0x0f) << 4 ) | (fragmentNumber & 0x0f);
}
QByteArray getPayload() const override { return m_data; }
private:
QByteArray m_data;
};
/// ---------- Ack/NAck ----------
class Acknowledge_rx final : public SfmMessage_rx
{
public:
Acknowledge_rx()
: SfmMessage_rx(SfmMessage::MsgId::Acknowledge)
{}
static constexpr int s_payloadSize = 2;
bool setPayload(const QByteArray &p_data) override;
uint8_t recvCounter() const { return m_recvCounter; }
bool isOk() const { return m_ackState == 0; }
bool isCrcError() const { return (m_ackState & 0x20) == 0x20; }
bool isFragmentError() const { return (m_ackState & 0x10) == 0x10; }
uint8_t missingFragment() const { return (m_ackState & 0x0f)!= 0; }
private:
uint8_t m_recvCounter = 0;
uint8_t m_ackState = 0;
};
class Acknowledge_tx final : public SfmMessage_tx
{
public:
explicit Acknowledge_tx()
: SfmMessage_tx(SfmMessage::MsgId::Acknowledge)
{}
QByteArray getPayload() const override;
void setRecvCounter(uint8_t recvCounter) { m_recvCounter = recvCounter; }
void setCrcError() { m_ackState |= 0x20; }
void setFragmentError(uint8_t missingFrag) { m_ackState |= (0x10 | (0x0f & missingFrag)); }
private:
uint8_t m_recvCounter = 0;
uint8_t m_ackState = 0;
};
/// ---------- MsgLizensStatus ----------
/**
* \class SfmMsgLizensStatus
*
* \brief Eine Lizenzstatus-Nachricht die zur Zentrale weitergereicht werden muss
*
* \author B. Ziegler
*/
class SfmMsgLicenseState_rx final : public SfmMessage_rx
{
public:
SfmMsgLicenseState_rx()
: SfmMessage_rx(SfmMessage::MsgId::LicenseState)
{}
static constexpr int s_payloadSize = 1;
bool setPayload(const QByteArray &p_data) override;
bool isLicensed() const { return m_isLicensed; }
private:
bool m_isLicensed = false;
};
}//namespace WirelessLocationSystem
@@ -0,0 +1,106 @@
/**
* Convert SfmMessages to Wls representation and vice versa
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#include "wls_sfm_message_converter.h"
#include <QByteArray>
#include <QSharedPointer>
#include <LtsUtilsCore/sfm_bytemessage_converter.h>
#include "logger.h"
namespace WirelessLocationSystem
{
namespace SfmMessageConverter
{
/**
* \brief Alloziiert auf Basis der Parameter ein SfmMessage_rx-Kindobjekt
*
* \author B. Ziegler
*
* \param id Die ID der Nachricht
*
* \return Falls möglich ein entsprechendes Kindobjekt, sonst einen Null-Pointer
*/
static SfmMessage_rx *getSpecificSfmMessage(SfmMessage::MsgId p_id)
{
switch (p_id)
{
case SfmMessage::MsgId::SfmRestartNotification:
return new SfmMsgSfmRestart_rx;
case SfmMessage::MsgId::EquipmentId:
return new SfmMsgEquipmentId;
case SfmMessage::MsgId::TacticalState:
return new SfmMsgTacticalState_rx;
case SfmMessage::MsgId::LicenseState:
return new SfmMsgLicenseState_rx;
case SfmMessage::MsgId::TetraMessage:
return new SfmMsgTetra_rx;
case SfmMessage::MsgId::Acknowledge:
return new Acknowledge_rx;
case SfmMessage::MsgId::RequestGpsControl:
return new SfmMsgRequestGpsControl_rx;
default:
break;
}
return nullptr;
}
QByteArray generateTxMessage(const SfmMessage_tx &message)
{
// Converting to LtsUtils::SfmMessage
LtsUtils::SfmMessage msg;
msg.setId(static_cast<int>(message.getId()) & 0x7FFF);
msg.setRtr(static_cast<int>(message.getId()) & 0x8000);
msg.setPayload(message.getPayload());
return LtsUtils::byteStreamFromSfmMessage(msg).toHex();
}
QSharedPointer<SfmMessage_rx> generateRxMessage(const QByteArray &data)
{
LtsUtils::SfmMessage message;
SfmMessage_rx *msg = nullptr;
try
{
message = LtsUtils::byteStreamToSfmMessage(QByteArray::fromHex(data));
}
catch (const LtsUtils::ConversionError &ex)
{
ERROR_LOG(getLogger("SfmMessageConverter")) << "Error parsing bytestream: " << data << " Exception: " << ex.what();
return QSharedPointer<SfmMessage_rx>(msg);
}
/// Erzeugen der entsprechenden Kind-Klasse from LtsUtils::SfmMessage
SfmMessage::MsgId msgId = static_cast<SfmMessage::MsgId>(message.id() | (message.rtr() << 15));
msg = getSpecificSfmMessage(msgId);
if (!msg)
{
ERROR_LOG(getLogger("SfmMessageConverter")) << "Error creating SfmMessage for message with id: " << message.id()
<< ", rtr: " << message.rtr()
<< ", combined: 0x" << QString::number(int(msgId));
}
else if (!msg->setPayload(message.payload()))
{
delete msg;
msg = nullptr;
}
return QSharedPointer<SfmMessage_rx>(msg);
}
} // namespace SfmMessageConverter
} // namespace WirelessLocationSystem
@@ -0,0 +1,48 @@
/**
* Convert SfmMessages to Wls representation and vice versa
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#pragma once
#include <QByteArray>
#include <QSharedPointer>
#include "wls_sfm_message.h"
namespace WirelessLocationSystem
{
namespace SfmMessageConverter
{
/**
* \brief Erzeugt aus einem SfmMessage_tx-Objekt ein QByteArray mit dem entsprechenden Telegrammaufbau
*
* \author B. Ziegler
*
* Bei der Erzeugen werden alle notwendigen Operationen auf dem Telegram durchgeführt, so das es dem
* Telegramaufbau entspricht
*
* \param message Die Nachricht aus der das Telegram gebaut werden soll
*
* \return Das Telegram als QByteArray
*/
SFMCONVERTER_EXPORT QByteArray generateTxMessage(const SfmMessage_tx &message);
/**
* \brief Versucht aus einem QByteArray ein SfmMessage_rx-Kindobjekt zu erzeugen
*
* \author B. Ziegler
*
* Prueft das uebergebene QByteArray auf Korrektheit und erzeugt daraus eine Ableitung der SfmMessage_rx-Klasse
*
* \param data Enthaelt die Rohdaten der Sfm-Nachricht
*
* \return Wenn das QByteArray korrekt war eine entsprechende Ableitung der SfmMessage_rx, sonst einen Null-Pointer
*/
QSharedPointer<SfmMessage_rx> generateRxMessage(const QByteArray &data);
}
}//namespace WirelessLocationSystem