Files
qt5-legatus-legacy/Lts/Proxies/CombatNetRadio/exercise_manager.cpp
T
2026-08-13 18:33:10 +02:00

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