331 lines
11 KiB
C++
Executable File
331 lines
11 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* ExerciseManager
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
* Created by Christian Fröbel, NLE, 2019-08-02
|
|
*/
|
|
|
|
#include "exercise_manager.h"
|
|
#include "active_exercises.h"
|
|
#include "dds_handler.h"
|
|
#include "dds_handler_exercise.h"
|
|
#include "main_handler.h"
|
|
#include "training_area.h"
|
|
#include "config_cnr.h"
|
|
#include "positions_cnr.h"
|
|
#include <ExerciseManagement/exm_dtc.h>
|
|
#include <QUuid>
|
|
#include <QDateTime>
|
|
#include <QJsonDocument>
|
|
#include <memory>
|
|
|
|
|
|
namespace CombatNetRadio
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
void merge(WebSocketInterface::Messages::ConfigCnrMessage::ConfigCnrData &data, const WebSocketInterface::Messages::ConfigCnrMessage::ConfigCnrData &toBeMerged)
|
|
{
|
|
Q_ASSERT(data.setId == toBeMerged.setId);
|
|
|
|
data.radioCircuit.reserve(data.radioCircuit.size() + toBeMerged.radioCircuit.size());
|
|
|
|
for (const auto &entry : toBeMerged.radioCircuit)
|
|
data.radioCircuit.append(entry);
|
|
}
|
|
|
|
void merge(WebSocketInterface::Messages::PositionsCnrMessage::PositionsCnrMessageData &data, const WebSocketInterface::Messages::PositionsCnrMessage::PositionsCnrMessageData &toBeMerged)
|
|
{
|
|
data.radioCircuit.reserve(data.radioCircuit.size() + toBeMerged.radioCircuit.size());
|
|
|
|
for (const auto &pos : toBeMerged.radioCircuit)
|
|
data.radioCircuit.append(pos);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
/**
|
|
* Will be instantiated for every active Exercise -- even when the Exercise
|
|
* does not have an active FrequencyPlanning. This is because there might be another
|
|
* active Exercise with a global CnrConfig in its active FrequencyPlanning.
|
|
* Then each active Exercise must provide the positions for the global CnrConfig
|
|
* regardsless of whether it has an active FrequencyPlanning or not.
|
|
*/
|
|
struct ExerciseManager::ExerciseParts
|
|
{
|
|
explicit ExerciseParts(int exerciseId, ExerciseManager *exerciseManager, DdsHandler *ddsHandler)
|
|
: m_ddsHandlerEx(new DdsHandlerExercise(ddsHandler->domainParticipant(), exerciseId))
|
|
, configCnr(new ConfigCnr(exerciseId, ddsHandler->avmDtc(), ddsHandler->ismDtc()))
|
|
, positionsCnr(exerciseId ? new PositionsCnr(exerciseId, exerciseManager, m_ddsHandlerEx->ppmDtc(), ddsHandler->avmDtc()) : nullptr)
|
|
{
|
|
m_ddsHandlerEx->enableDtcs();
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<DdsHandlerExercise> m_ddsHandlerEx;
|
|
|
|
public:
|
|
std::unique_ptr<ConfigCnr> configCnr;
|
|
std::unique_ptr<PositionsCnr> positionsCnr;
|
|
};
|
|
|
|
|
|
ExerciseManager::ExerciseManager(ActiveExercises *activeExercises, DdsHandler *ddsHandler)
|
|
: m_activeExercises(activeExercises)
|
|
, m_ddsHandler(ddsHandler)
|
|
, m_logger(getLogger("ExerciseManager"))
|
|
{
|
|
Q_ASSERT(activeExercises);
|
|
Q_ASSERT(ddsHandler);
|
|
|
|
m_positionsUpdateTimer.setSingleShot(false);
|
|
m_essentialsUpdateTimer.setSingleShot(true);
|
|
m_essentialsUpdateTimer.setInterval(200);
|
|
|
|
connect(m_activeExercises, &ActiveExercises::activated, this, &ExerciseManager::onExerciseActivated);
|
|
connect(m_activeExercises, &ActiveExercises::deactivated, this, &ExerciseManager::onExerciseDeactivated);
|
|
connect(&m_positionsUpdateTimer, &QTimer::timeout, this, &ExerciseManager::onPositionsUpdateTimeout);
|
|
connect(&m_essentialsUpdateTimer, &QTimer::timeout, this, &ExerciseManager::essentialsUpdate); // signal -> signal
|
|
|
|
auto result = m_parts.emplace(0, ExerciseParts(0, this, m_ddsHandler));
|
|
connect(result.first->second.configCnr.get(), &ConfigCnr::changed, &m_essentialsUpdateTimer, QOverload<>::of(&QTimer::start));
|
|
}
|
|
|
|
ExerciseManager::~ExerciseManager() = default;
|
|
|
|
WebSocketInterface::Messages::ReplyMessage ExerciseManager::trainingAreaData(const QString &uuid) const
|
|
{
|
|
if (!m_trainingArea)
|
|
return WebSocketInterface::Messages::ErrorMessage(1, uuid, QStringLiteral("There is no active Training Area"));
|
|
|
|
return WebSocketInterface::Messages::TrainingAreaMessage(m_trainingArea->trainingArea(), uuid);
|
|
}
|
|
|
|
std::vector<WebSocketInterface::Messages::ReplyMessage> ExerciseManager::radioStations(const QString &uuid) const
|
|
{
|
|
if (!m_trainingArea)
|
|
return {};
|
|
|
|
std::vector<WebSocketInterface::Messages::ReplyMessage> result;
|
|
const auto &radioStations = m_trainingArea->radioStations();
|
|
result.reserve(radioStations.size());
|
|
|
|
for (const auto &radioStation : radioStations)
|
|
result.push_back(WebSocketInterface::Messages::RadioStationMessage(radioStation, uuid));
|
|
|
|
return result;
|
|
}
|
|
|
|
WebSocketInterface::Messages::ReplyMessage ExerciseManager::configCnr(const QString &uuid)
|
|
{
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION;
|
|
|
|
const WebSocketInterface::Messages::ErrorMessage noActivePlanning(1, uuid, QStringLiteral("No active frequency planning found"));
|
|
|
|
if (m_parts.empty())
|
|
{
|
|
DEBUG_LOG(m_logger) << "No active frequency planning due to empty m_parts.";
|
|
|
|
return noActivePlanning;
|
|
}
|
|
|
|
bool isFirst = true;
|
|
WebSocketInterface::Messages::ConfigCnrMessage::ConfigCnrData configCnrData;
|
|
for (const auto &keyValue : m_parts)
|
|
{
|
|
if (!keyValue.second.configCnr->hasActiveFrequencyPlanning())
|
|
continue;
|
|
|
|
if (isFirst)
|
|
{
|
|
configCnrData = keyValue.second.configCnr->data();
|
|
isFirst = false;
|
|
continue;
|
|
}
|
|
|
|
merge(configCnrData, keyValue.second.configCnr->data());
|
|
}
|
|
|
|
if (configCnrData.radioCircuit.empty())
|
|
{
|
|
DEBUG_LOG(m_logger) << "No active frequency planning due to empty radio circuits.";
|
|
|
|
return noActivePlanning;
|
|
}
|
|
|
|
return WebSocketInterface::Messages::ConfigCnrMessage(configCnrData, uuid);
|
|
}
|
|
|
|
WebSocketInterface::Messages::ReplyMessage ExerciseManager::positionsCnr(const QString &uuid) const
|
|
{
|
|
const WebSocketInterface::Messages::ErrorMessage noPositions(1, uuid, QStringLiteral("No positions available"));
|
|
|
|
if (m_parts.empty())
|
|
return noPositions;
|
|
|
|
bool isFirst = true;
|
|
WebSocketInterface::Messages::PositionsCnrMessage::PositionsCnrMessageData positionsData;
|
|
for (const auto &keyValue : m_parts)
|
|
{
|
|
if (!keyValue.second.positionsCnr)
|
|
continue;
|
|
|
|
if (isFirst)
|
|
{
|
|
positionsData = keyValue.second.positionsCnr->data();
|
|
isFirst = false;
|
|
continue;
|
|
}
|
|
|
|
merge(positionsData, keyValue.second.positionsCnr->data());
|
|
}
|
|
|
|
if (positionsData.radioCircuit.empty())
|
|
return noPositions;
|
|
|
|
return WebSocketInterface::Messages::PositionsCnrMessage(positionsData, uuid);
|
|
}
|
|
|
|
void ExerciseManager::onModifyFrequencyPlanningStatusReply(const Core::FunctionReplyObject &replyObject)
|
|
{
|
|
if (replyObject.error())
|
|
ERROR_LOG(m_logger) << "Error while modifying FrequencyPlanningStatus: " << replyObject.str();
|
|
else
|
|
INFO_LOG(m_logger) << "onModifyFrequencyPlanningStatusReply ok";
|
|
}
|
|
|
|
void ExerciseManager::onClearFrequencyPlanningStatusReply(const Core::FunctionReplyObject &replyObject)
|
|
{
|
|
if (replyObject.error())
|
|
ERROR_LOG(m_logger) << "Error while clearing FrequencyPlanningStatus: " << replyObject.str();
|
|
else
|
|
INFO_LOG(m_logger) << "onClearFrequencyPlanningStatusReply ok";
|
|
}
|
|
|
|
void ExerciseManager::onRemoveFrequencyPlanningStatusByClientReply(const Core::FunctionReplyObject &replyObject)
|
|
{
|
|
if (replyObject.error())
|
|
ERROR_LOG(m_logger) << "Error while removing FrequencyPlanningStatus: " << replyObject.str();
|
|
else
|
|
INFO_LOG(m_logger) << "onRemoveFrequencyPlanningStatusByClientReply ok";
|
|
}
|
|
|
|
void ExerciseManager::setPositionsUpdateInterval(std::chrono::milliseconds interval)
|
|
{
|
|
m_positionsUpdateTimer.start(interval.count());
|
|
}
|
|
|
|
void ExerciseManager::stopPositionsUpdate()
|
|
{
|
|
m_positionsUpdateTimer.stop();
|
|
}
|
|
|
|
void ExerciseManager::onNewClientConnection(qint64 id)
|
|
{
|
|
AudioVideoManagement::ModifyFrequencyPlanningStatusRequest request;
|
|
request.client(id);
|
|
request.isActive(true);
|
|
m_ddsHandler->avmFtc()->modifyFrequencyPlanningStatusRequest(request, this);
|
|
}
|
|
|
|
void ExerciseManager::onClientConnectionClosed(qint64 id)
|
|
{
|
|
AudioVideoManagement::RemoveFrequencyPlanningStatusByClientRequest request;
|
|
request.client(id);
|
|
m_ddsHandler->avmFtc()->removeFrequencyPlanningStatusByClientRequest(request, this);
|
|
}
|
|
|
|
void ExerciseManager::onClearFrequencyPlanningStatus()
|
|
{
|
|
AudioVideoManagement::ClearFrequencyPlanningStatusRequest request;
|
|
m_ddsHandler->avmFtc()->clearFrequencyPlanningStatusRequest(request, this);
|
|
}
|
|
|
|
void ExerciseManager::onConfigCnrSent(qint64 id, const QString &uuid, const QString &requester, bool isActiveClient)
|
|
{
|
|
AudioVideoManagement::ModifyFrequencyPlanningStatusRequest request;
|
|
request.client(id);
|
|
request.uuid(uuid.toStdString());
|
|
request.sent(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch());
|
|
request.requester(requester.toStdString());
|
|
request.isActive(isActiveClient);
|
|
m_ddsHandler->avmFtc()->modifyFrequencyPlanningStatusRequest(request, this);
|
|
}
|
|
|
|
void ExerciseManager::onConfigCnrReceived(qint64 id, const QString &uuid)
|
|
{
|
|
AudioVideoManagement::ModifyFrequencyPlanningStatusRequest request;
|
|
request.client(id);
|
|
request.uuid(uuid.toStdString());
|
|
request.received(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch());
|
|
m_ddsHandler->avmFtc()->modifyFrequencyPlanningStatusRequest(request, this);
|
|
}
|
|
|
|
void ExerciseManager::onConfigCnrProcessed(qint64 id, const QString &uuid, const QJsonObject &configCnrData)
|
|
{
|
|
AudioVideoManagement::ModifyFrequencyPlanningStatusRequest request;
|
|
request.client(id);
|
|
request.uuid(uuid.toStdString());
|
|
request.activated(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch());
|
|
request.content(QJsonDocument(configCnrData).toJson().toStdString());
|
|
request.requester(QJsonDocument(configCnrData).object()[QStringLiteral("requester")].toString().toStdString());
|
|
request.reason(static_cast<AudioVideoManagement::TransmissionReason::inner_enum>(QJsonDocument(configCnrData).object()[QStringLiteral("reason")].toInt(0)));
|
|
request.isActive(true); // Muss ja, sonst würde er das nicht senden...
|
|
m_ddsHandler->avmFtc()->modifyFrequencyPlanningStatusRequest(request, this);
|
|
}
|
|
|
|
QVector<WebSocketInterface::Messages::ReplyMessage::LonLatPoint> ExerciseManager::positionsForGlobal(const AudioVideoManagement::CnrConfig &config) const
|
|
{
|
|
QVector<WebSocketInterface::Messages::ReplyMessage::LonLatPoint> positions;
|
|
for (const auto &keyValue : m_parts)
|
|
{
|
|
if (!keyValue.second.positionsCnr)
|
|
continue;
|
|
|
|
positions.append(keyValue.second.positionsCnr->positionsForGlobal(config));
|
|
}
|
|
return positions;
|
|
}
|
|
|
|
void ExerciseManager::onExerciseActivated(int exerciseId)
|
|
{
|
|
auto result = m_parts.emplace(exerciseId, ExerciseParts(exerciseId, this, m_ddsHandler));
|
|
Q_ASSERT(result.second);
|
|
|
|
connect(result.first->second.configCnr.get(), &ConfigCnr::changed, &m_essentialsUpdateTimer, QOverload<>::of(&QTimer::start));
|
|
|
|
bool isFirstActiveExercise = m_parts.size() == 2;
|
|
if (isFirstActiveExercise)
|
|
{
|
|
ExerciseManagement::Exercise exercise = m_ddsHandler->exmDtc()->exercise(exerciseId);
|
|
m_trainingArea.reset(new TrainingArea(exercise.trainingAreaId(), m_ddsHandler->asmDtc(), m_ddsHandler->ismDtc()));
|
|
}
|
|
|
|
emit essentialsUpdate();
|
|
}
|
|
|
|
void ExerciseManager::onExerciseDeactivated(int exerciseId)
|
|
{
|
|
auto found = m_parts.find(exerciseId);
|
|
Q_ASSERT(found != m_parts.end());
|
|
m_parts.erase(found);
|
|
|
|
bool isLastActiveExercise = m_parts.size() == 1;
|
|
if (isLastActiveExercise)
|
|
m_trainingArea.reset();
|
|
|
|
emit essentialsUpdate();
|
|
}
|
|
|
|
void ExerciseManager::onPositionsUpdateTimeout()
|
|
{
|
|
emit positionsUpdate(positionsCnr(QUuid::createUuid().toString()));
|
|
}
|
|
|
|
} // namespace CombatNetRadio
|