532 lines
17 KiB
C++
Executable File
532 lines
17 KiB
C++
Executable File
/**
|
|
* MainHandler for TetraRadio
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
* Created by Christian Ehrlicher, SLSE, 2016-04-14
|
|
*/
|
|
|
|
#include "main_handler.h"
|
|
#include "settings.h"
|
|
#include "logger.h"
|
|
#include "WebSocketInterface/Messages/reply_message.h"
|
|
#include "WebSocketInterface/json_keys.h"
|
|
|
|
#include <AssetManagement/asm_dtc.h>
|
|
#include <DDS/Core/pacemaker.h>
|
|
#include <ExerciseManagement/exm_dtc.h>
|
|
#include <LtsPluginManager/lts_plugin_loader.h>
|
|
#include <TrainingAreaBase/tab_record_replay_plugin.h>
|
|
#include <TrainingAreaBase/tab_record_replay_plugin_custom.h>
|
|
|
|
#include <nglsettings/nglsettings_connections.h>
|
|
#include <ngldb/nsqlquery.h>
|
|
|
|
#include <QUuid>
|
|
#include <QJsonDocument>
|
|
#include <QSqlDatabase>
|
|
|
|
namespace TrainingAreaBase
|
|
{
|
|
|
|
MainHandler::MainHandler(QObject *parent)
|
|
: QObject(parent)
|
|
, m_ddsHandler(nullptr)
|
|
, m_handlerWebSocket(nullptr)
|
|
, m_logger(getLogger("MainHandler"))
|
|
{}
|
|
|
|
MainHandler::~MainHandler()
|
|
{
|
|
LtsPluginManager::LtsPluginLoader::getInstance()->unloadAllPlugins();
|
|
m_interfaces.clear();
|
|
m_ifcConnectionState.clear();
|
|
|
|
m_rxHandler.reset();
|
|
m_txHandler.reset();
|
|
m_ftsHandler.reset();
|
|
|
|
m_posHandler.reset();
|
|
m_ppmHandler.reset();
|
|
m_pdmHandler.reset();
|
|
m_exmHandler.reset();
|
|
m_ismHandler.reset();
|
|
m_aesHandler.reset();
|
|
|
|
m_ddsHandler.reset();
|
|
m_dbHandler.reset();
|
|
}
|
|
|
|
bool MainHandler::init(int domainId, int exerciseId)
|
|
{
|
|
// initialize database
|
|
m_dbHandler = std::make_shared<DbHandler>();
|
|
|
|
if (!m_dbHandler->init(exerciseId, Settings::settings()))
|
|
return false;
|
|
// initialize DDS
|
|
m_ddsHandler = std::make_shared<DdsHandler>(domainId, exerciseId);
|
|
if (!m_ddsHandler->init())
|
|
return false;
|
|
|
|
m_exmHandler = std::make_shared<ExmHandler>(this);
|
|
connect(m_exmHandler.get(), &ExmHandler::exerciseChanged,
|
|
this, &MainHandler::onExerciseChanged);
|
|
connect(m_exmHandler.get(), &ExmHandler::exercise2RadioGroupChanged,
|
|
this, &MainHandler::onExercise2RadioGroupChanged);
|
|
m_exmHandler->init();
|
|
m_pdmHandler = std::make_shared<PdmHandler>(this);
|
|
if (!m_pdmHandler->init())
|
|
return false;
|
|
if (!m_pdmHandler->initializePdm())
|
|
return false;
|
|
m_ppmHandler = std::make_shared<PpmHandler>(this);
|
|
connect(m_ppmHandler.get(), &PpmHandler::position,
|
|
this, &MainHandler::onPositionChanged);
|
|
|
|
if (!m_ppmHandler->init())
|
|
return false;
|
|
if (exerciseId > 0)
|
|
{
|
|
m_ismHandler = std::make_shared<IsmHandler>(this);
|
|
if (!m_ismHandler->init())
|
|
return false;
|
|
/*
|
|
* Es gibt den Fall der Initialisierung, wenn der TAB Prozess neu gestartet wird.
|
|
* Ist ein ABG zu diesem Zeitpunkt angemeldet und wird danach ein anderer Teilnehmer mit dem selben ABG angemeldet,
|
|
* bleibt das Risiko bestehen, dass zwei Teilnehmer dasselbe ABG zum gleichen Zeitpunkt anzeigen.
|
|
*
|
|
*
|
|
NSqlQuery q(databaseConnection());
|
|
|
|
TrainingAreaBase::RecordReplayPluginCustom *tabRecordReplay;
|
|
tabRecordReplay = new TrainingAreaBase::RecordReplayPluginCustom;
|
|
tabRecordReplay->initDtc(nullptr, exerciseId);
|
|
|
|
TrainingAreaBase::RecordReplayPlugin recordReplay;
|
|
recordReplay.initDtc(nullptr, exerciseId);
|
|
|
|
if (!recordReplay.readParticipantSanitaryMaterialStockReportAt(q, QDateTime::currentMSecsSinceEpoch()))
|
|
{
|
|
ERROR_LOG(m_logger) << "Could not read ParticipantSanitaryMaterialStockReport";
|
|
// return false;
|
|
}
|
|
else
|
|
{
|
|
const auto &latestStock = tabRecordReplay->getParticipantSanitaryMaterialStockReportMap();
|
|
|
|
ERROR_LOG(m_logger) << "Starting to latest ParticipantSanitaryMaterialStock. Size: "
|
|
<< latestStock.size();
|
|
|
|
for (const auto &stock : latestStock)
|
|
{
|
|
ERROR_LOG(m_logger) << "Stock for equipment number " << stock.equipmentNumber();
|
|
if (stock.uniqueId() > 0 && !m_uniqueId2EquipmentNumber.contains(stock.uniqueId()))
|
|
m_uniqueId2EquipmentNumber.insert(stock.uniqueId(), stock.equipmentNumber());
|
|
}
|
|
}
|
|
|
|
tabRecordReplay->deleteLater();
|
|
*/
|
|
}
|
|
m_posHandler = std::make_shared<PositionHandler>(this);
|
|
if (!m_posHandler->init())
|
|
return false;
|
|
|
|
// to TrainingArea
|
|
m_txHandler = std::make_shared<TxMixin>();
|
|
|
|
// from DDS
|
|
m_ftsHandler = std::make_shared<RecvFromDdsFts>(this,
|
|
m_ddsHandler->ftsTab(),
|
|
m_txHandler);
|
|
if (exerciseId > 0)
|
|
{
|
|
m_aesHandler = std::make_shared<RecvFromDdsAes>(this,
|
|
m_txHandler);
|
|
if (!m_aesHandler->init())
|
|
return false;
|
|
}
|
|
|
|
// to DDS
|
|
m_rxHandler = std::make_shared<SendToDds>(this);
|
|
|
|
// to/from TrainingArea
|
|
auto cb = [&](const LtsPluginManager::PluginInstance &pi) { return loadPlugin(pi); };
|
|
LtsPluginManager::LtsPluginLoader::getInstance()->loadPlugins(LtsPluginManager::PluginType::SPECIAL,
|
|
cb,
|
|
QStringList(),
|
|
QStringLiteral("tab"));
|
|
|
|
if (m_interfaces.empty())
|
|
{
|
|
WARNING_LOG(m_logger) << "Could not load any training area plugin - can not send data to training area";
|
|
return false;
|
|
}
|
|
|
|
for (const std::shared_ptr<Plugin::Interface> &ifc : m_interfaces)
|
|
m_txHandler->addTxHandler(ifc->sendToTab());
|
|
|
|
|
|
|
|
// WebSocketServer
|
|
m_handlerWebSocket = std::make_shared<WebSocketInterface::WebSocketInterfaceHandler>(this);
|
|
DEBUG_LOG(m_logger) << "Reset WebSocketInterfaceHandler successful.";
|
|
|
|
if (!m_handlerWebSocket->init())
|
|
{
|
|
ERROR_LOG(m_logger) << "Initialisation WebSocketInterfaceHandler failed.";
|
|
return false;
|
|
}
|
|
m_ddsHandler->pacemaker()->addPort(m_handlerWebSocket->serverWebSocketPort(), Core::PortType::WebSocketServer, QStringLiteral("{\"id\":\"TAB\"}"));
|
|
DEBUG_LOG(m_logger) << "Initialisation WebSocketInterfaceHandler successful.";
|
|
|
|
|
|
m_ddsHandler->pacemaker()->setNewState(Core::RunningState::Running);
|
|
return true;
|
|
}
|
|
|
|
bool MainHandler::useTetraScrambling() const
|
|
{
|
|
return Settings::useTetraScramling();
|
|
}
|
|
|
|
ProjectDataManagement::RadioGroup MainHandler::exerciseRadioGroup() const
|
|
{
|
|
if (exerciseId() == 0)
|
|
{
|
|
// for exercise 0 we have to look for a base grp
|
|
const QMap<int, ProjectDataManagement::RadioGroup> radioGrps = m_pdmHandler->radioGroups();
|
|
|
|
for (auto it = radioGrps.begin(); it != radioGrps.end(); ++it)
|
|
if (it.value().base_group())
|
|
return it.value();
|
|
|
|
return ProjectDataManagement::RadioGroup();
|
|
}
|
|
|
|
const auto ex = m_exmHandler->exercise(exerciseId());
|
|
if (ex.radioGroupId().is_set())
|
|
return m_pdmHandler->radioGroup(ex.radioGroupId().get());
|
|
return ProjectDataManagement::RadioGroup();
|
|
}
|
|
|
|
bool MainHandler::loadPlugin(const LtsPluginManager::PluginInstance &pi)
|
|
{
|
|
INFO_LOG(m_logger) << "Trying to load plugin '" << pi.fullFileName << "'";
|
|
Plugin::Plugin *tabPlugin = qobject_cast<Plugin::Plugin *>(pi.plugin);
|
|
if (!tabPlugin)
|
|
{
|
|
WARNING_LOG(m_logger) << "failed loading a training area plugin - not a TrainingAreaPlugin object!";
|
|
return false;
|
|
}
|
|
INFO_LOG(m_logger) << "... success";
|
|
std::shared_ptr<Plugin::Interface> ifc = tabPlugin->createInterface(this, m_rxHandler, m_txHandler, m_ddsHandler, Settings::settings());
|
|
|
|
if (!ifc || !ifc->init())
|
|
{
|
|
WARNING_LOG(m_logger) << "Could not initialize training area plugin" << pi.pluginName << pi.fileName;
|
|
return false;
|
|
}
|
|
|
|
connect(ifc.get(), &Plugin::Interface::connectionState, this, &MainHandler::onConnectionState);
|
|
connect(ifc.get(), &Plugin::Interface::directMessage, this, &MainHandler::onDirectMessage);
|
|
|
|
m_interfaces.push_back(ifc);
|
|
|
|
m_ccsMap = m_pdmHandler->dtc()->canComponentStrings();
|
|
m_bctMap = m_pdmHandler->dtc()->biteComponentTexts();
|
|
|
|
return true;
|
|
}
|
|
|
|
TetraIcd::ExerciseEnvironmentData MainHandler::exerciseEnvironmentData() const
|
|
{
|
|
TetraIcd::ExerciseEnvironmentData exData;
|
|
exData.exerciseId = exerciseId();
|
|
const auto &baseCoord = m_posHandler->baseCoordinate();
|
|
// 32UPD
|
|
const QString zone = baseCoord.mgrs(0, false);
|
|
if (zone.isEmpty())
|
|
return exData;
|
|
exData.utmZone = baseCoord.zone;
|
|
exData.grid = zone.mid(3, 2).toStdString();
|
|
exData.utmBand = zone.mid(2, 1).at(0).toLatin1();
|
|
exData.timeZone = m_exerciseData.defaultTimeZone();
|
|
|
|
return exData;
|
|
}
|
|
|
|
TetraIcd::ExerciseStateData MainHandler::exerciseStateData() const
|
|
{
|
|
TetraIcd::ExerciseStateData exData;
|
|
|
|
if (m_exerciseData.id() != exerciseId())
|
|
return exData;
|
|
|
|
exData.exerciseId = m_exerciseData.id();
|
|
exData.exerciseState = static_cast<TetraIcd::ExerciseStateData::ExerciseState>(m_exerciseData.state().underlying());
|
|
return exData;
|
|
}
|
|
|
|
TetraIcd::ExerciseEquipmentAssignmentComplete MainHandler::exerciseEquipmentAssignment() const
|
|
{
|
|
TetraIcd::ExerciseEquipmentAssignmentComplete eeac;
|
|
eeac.exerciseEquipmentAssignments = m_ppmHandler->exerciseEquipmentAssignment();
|
|
|
|
const auto exercises = m_exmHandler->exercise2RadioGroup();
|
|
const QMap<int, ProjectDataManagement::RadioGroup> radioGroups = m_pdmHandler->radioGroups();
|
|
for (const auto &it : exercises)
|
|
{
|
|
const ProjectDataManagement::RadioGroup rg = radioGroups.value(it.second);
|
|
if (rg.id() == 0)
|
|
{
|
|
WARNING_LOG(m_logger) << "Got a radio group id " << it.second << " for exercise " << it.first
|
|
<< " but could not find valid data for it - ignoring!";
|
|
continue;
|
|
}
|
|
TrainingAreaBase::TetraIcd::GroupIssiInformation gissi;
|
|
gissi.exerciseId = it.first;
|
|
gissi.serverIssi = rg.issi();
|
|
gissi.broadcastIssi1 = rg.group_issi1();
|
|
gissi.broadcastIssi2 = rg.group_issi2();
|
|
eeac.groupIssiInformation.push_back(std::move(gissi));
|
|
}
|
|
return eeac;
|
|
}
|
|
|
|
QVector<TetraIcd::TrainingAreaTelegram*> MainHandler::aesTelegrams() const
|
|
{
|
|
return m_aesHandler ? m_aesHandler->sentAesTelegrams() : QVector<TetraIcd::TrainingAreaTelegram*>();
|
|
}
|
|
|
|
QSqlDatabase MainHandler::databaseConnection() const
|
|
{
|
|
if (!m_dbHandler || !m_dbHandler->db())
|
|
return QSqlDatabase();
|
|
return m_dbHandler->db()->database();
|
|
}
|
|
|
|
QString MainHandler::databaseSchemaName() const
|
|
{
|
|
if (!m_dbHandler || !m_dbHandler->db())
|
|
return QString();
|
|
return m_dbHandler->db()->schemaName();
|
|
}
|
|
|
|
int MainHandler::domainId() const
|
|
{
|
|
if (!m_ddsHandler)
|
|
return 0;
|
|
if (!m_ddsHandler->ddsConnector())
|
|
return 0;
|
|
return m_ddsHandler->ddsConnector()->getDomainId();
|
|
}
|
|
|
|
void MainHandler::onExerciseChanged(const ExerciseManagement::Exercise &data, DataModification modType)
|
|
{
|
|
if (data.id() != exerciseId())
|
|
return;
|
|
|
|
m_exerciseData = data;
|
|
if (modType == DataModification::eModified)
|
|
m_txHandler->txData(exerciseStateData());
|
|
|
|
// update Interfaces
|
|
for (const std::shared_ptr<Plugin::Interface> &ifc : qAsConst(m_interfaces))
|
|
ifc->exerciseChanged();
|
|
}
|
|
|
|
void MainHandler::onExercise2RadioGroupChanged()
|
|
{
|
|
m_txHandler->txData(exerciseEquipmentAssignment());
|
|
}
|
|
|
|
void MainHandler::onConnectionState(const QString &name, bool connectionState)
|
|
{
|
|
auto itC = m_ifcConnectionState.find(name);
|
|
if (itC != m_ifcConnectionState.end())
|
|
{
|
|
if (itC.value() == connectionState)
|
|
return; // already send to DDS
|
|
itC.value() = connectionState;
|
|
}
|
|
else
|
|
{
|
|
m_ifcConnectionState.insert(name, connectionState);
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << "Publishing connection states: " << m_ifcConnectionState.size();
|
|
|
|
TrainingAreaBase::Connections connections;
|
|
connections.id(1);
|
|
for (auto it = m_ifcConnectionState.cbegin(); it != m_ifcConnectionState.cend(); ++it)
|
|
connections.connections().push_back(
|
|
TrainingAreaBase::ConnectionState(it.key().toStdString(), it.value()));
|
|
|
|
m_ddsHandler->dtsTab()->publish(connections);
|
|
}
|
|
|
|
void MainHandler::onDirectMessage(const QByteArray &jsonMessage)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Got direct message " << jsonMessage;
|
|
WebSocketInterface::Messages::BiteDataMessage::BiteData biteData;
|
|
|
|
QJsonObject obj = QJsonDocument::fromJson(jsonMessage).object();
|
|
|
|
biteData.equipNo = obj.value(WebSocketInterface::JsonKeys::EQUIP_NO()).toInt();
|
|
biteData.isLicensed = obj.value(QStringLiteral("doLicence")).toBool();
|
|
|
|
qint64 biteState = obj.value(QStringLiteral("biteState")).toInt();
|
|
qint64 configState = obj.value(QStringLiteral("configState")).toInt();
|
|
qint64 batteryState = obj.value(QStringLiteral("batteryState")).toInt();
|
|
qint64 configError = obj.value(QStringLiteral("configError")).toInt();
|
|
|
|
QJsonArray biteError = obj.value(QStringLiteral("biteError")).toArray();
|
|
|
|
if (biteData.equipNo <= 0)
|
|
return;
|
|
|
|
// configuration errors
|
|
for (int it = 0 ; it < 32; it++)
|
|
{
|
|
quint32 bitValue = 1U << it;
|
|
QString name;
|
|
QString isOk;
|
|
for (const ProjectDataManagement::CanComponentString &ccs : qAsConst(m_ccsMap))
|
|
{
|
|
if (ccs.bitMask() == bitValue)
|
|
{
|
|
name = m_pdmHandler->dtc()->trCanComponentStringName(ccs.id());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (((biteState & bitValue) == bitValue))
|
|
{
|
|
if (((configState & bitValue) != bitValue))
|
|
{
|
|
isOk = tr("Error");
|
|
}
|
|
}
|
|
else if (((configState & bitValue) == bitValue))
|
|
{
|
|
isOk = tr("Communication error");
|
|
}
|
|
if(!name.isEmpty() && !isOk.isEmpty())
|
|
{
|
|
WebSocketInterface::Messages::BiteDataMessage::ConfigurationError error;
|
|
error.componentName = name;
|
|
error.text = isOk;
|
|
biteData.configurationErrors.append(error);
|
|
}
|
|
}
|
|
|
|
// basic errors
|
|
if(configError != 0)
|
|
{
|
|
WebSocketInterface::Messages::BiteDataMessage::BasicError error;
|
|
error.text = tr("Configcheck error");
|
|
biteData.basicErrors.append(error);
|
|
}
|
|
if (batteryState != 0)
|
|
{
|
|
WebSocketInterface::Messages::BiteDataMessage::BasicError error;
|
|
error.text = tr("Battery Warning");
|
|
biteData.basicErrors.append(error);
|
|
}
|
|
|
|
// componentErrors
|
|
for (const auto &ele : qAsConst(biteError))
|
|
{
|
|
QString text;
|
|
QJsonObject error = ele.toObject();
|
|
ParticipantManagement::BiteError bitError;
|
|
bitError.canDeviceNumber(error.value(QStringLiteral("canDeviceNumber")).toInt());
|
|
bitError.errorNumber(error.value(QStringLiteral("errorNumber")).toInt());
|
|
bitError.subdeviceNumber(error.value(QStringLiteral("subdeviceNumber")).toInt());
|
|
bitError.uniqueId(error.value(QStringLiteral("uniqueId")).toInt());
|
|
|
|
if (bitError.subdeviceNumber() != 0)
|
|
text = (tr("Error: %1 \nComponent: %2 \nSubdev: %3 \nUId: %4 ")
|
|
.arg(getErrorString(bitError),
|
|
getCanDeviceName(bitError.canDeviceNumber()),
|
|
getSubDeviceName(bitError.subdeviceNumber()))
|
|
.arg(QString::number( bitError.uniqueId(), 16 ).toUpper()));
|
|
|
|
else if (bitError.canDeviceNumber() != 0)
|
|
text = (tr("Error: %1 \nComponent: %2")
|
|
.arg(getErrorString(bitError),
|
|
getCanDeviceName(bitError.canDeviceNumber())));
|
|
|
|
WebSocketInterface::Messages::BiteDataMessage::BiteError biteErrorLocal;
|
|
biteErrorLocal.deviceId = bitError.canDeviceNumber();
|
|
biteErrorLocal.subDevice = bitError.subdeviceNumber();
|
|
biteErrorLocal.errorNo = bitError.errorNumber();
|
|
biteErrorLocal.uniqueId = bitError.uniqueId();
|
|
|
|
WebSocketInterface::Messages::BiteDataMessage::ErrorText errorT;
|
|
errorT.lang = QStringLiteral("de_DE");
|
|
errorT.text = text;
|
|
biteErrorLocal.errorTexts.append(errorT);
|
|
|
|
biteData.biteErrors.append(biteErrorLocal);
|
|
}
|
|
|
|
WebSocketInterface::Messages::BiteDataMessage *msg = new WebSocketInterface::Messages::BiteDataMessage(biteData, QStringLiteral("7777"));
|
|
|
|
if (m_handlerWebSocket != nullptr)
|
|
{
|
|
m_handlerWebSocket->sendToAllClients(msg);
|
|
}
|
|
}
|
|
|
|
QString MainHandler::getErrorString(const ParticipantManagement::BiteError &error) const
|
|
{
|
|
const bool subDevice = (error.subdeviceNumber() != 0);
|
|
for (const ProjectDataManagement::BiteComponentText &bct : m_bctMap)
|
|
{
|
|
if (((subDevice && bct.subDeviceId() == error.subdeviceNumber()) ||
|
|
(bct.canDeviceId() == error.canDeviceNumber())) &&
|
|
(bct.errorNumber() == error.errorNumber()))
|
|
return m_pdmHandler->dtc()->trBiteComponentTextName(bct.id());
|
|
}
|
|
return tr("Unknown (%1)").arg(error.errorNumber());
|
|
}
|
|
QString MainHandler::getCanDeviceName(int id) const
|
|
{
|
|
for (const ProjectDataManagement::CanComponentString &ccs : m_ccsMap)
|
|
{
|
|
if (ccs.canDeviceId() == id)
|
|
return m_pdmHandler->dtc()->trCanComponentStringName(ccs.id());
|
|
}
|
|
return tr("Unknown (%1)").arg(id);
|
|
}
|
|
|
|
QString MainHandler::getSubDeviceName(int id) const
|
|
{
|
|
for (const ProjectDataManagement::CanComponentString &ccs : m_ccsMap)
|
|
{
|
|
if (ccs.subDeviceId() == id)
|
|
return m_pdmHandler->dtc()->trCanComponentStringName(ccs.id());
|
|
}
|
|
return tr("Unknown (%1)").arg(id);
|
|
}
|
|
|
|
void MainHandler::onPositionChanged(int equipNo, const ParticipantManagement::ParticipantPosition &pos)
|
|
{
|
|
WebSocketInterface::Messages::PositionMessage::PositionData positionData;
|
|
positionData.equipNo = equipNo;
|
|
positionData.pos = pos.pos();
|
|
positionData.gpsTime = pos.sampleTime();
|
|
|
|
WebSocketInterface::Messages::PositionMessage *msg = new WebSocketInterface::Messages::PositionMessage(positionData, QUuid::createUuid().toString());
|
|
|
|
if (m_handlerWebSocket != nullptr)
|
|
{
|
|
m_handlerWebSocket->sendToAllClients(msg);
|
|
}
|
|
}
|
|
|
|
} // namespace TrainingAreaBase
|