/** * @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 #include #include #include #include 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 m_ddsHandlerEx; public: std::unique_ptr configCnr; std::unique_ptr 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 ExerciseManager::radioStations(const QString &uuid) const { if (!m_trainingArea) return {}; std::vector 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(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 ExerciseManager::positionsForGlobal(const AudioVideoManagement::CnrConfig &config) const { QVector 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