/** * Handler for all exercise objects * $Id$ * $HeadURL$ * $Author$ * Created by Hanna E. Rathje, SLSE, 2015-09-03 */ #include "handler_exercise.h" #include "exm_db.h" #include "handler_exercise_phase.h" #include "main_handler.h" #include "object_exercise.h" #include "settings.h" //#include #include #include #include #include #include #include namespace ExerciseManagement { HandlerExercise::HandlerExercise(MainHandler *mainHandler, const std::shared_ptr &ddsHandler, const std::shared_ptr &db, const std::shared_ptr &exTableGenerator, QObject *parent) : QObject(parent) , Interface::Fts::ExerciseCreateRequestHandler(ddsHandler->fts()) , Interface::Fts::ExerciseModifyRequestHandler(ddsHandler->fts()) , Interface::Fts::ExerciseRemoveRequestHandler(ddsHandler->fts()) , Interface::Fts::ExerciseImageModifyRequestHandler(ddsHandler->fts()) , Interface::Fts::GetExerciseImageRequestHandler(ddsHandler->fts()) , m_mainHandler(mainHandler) , m_ddsHandler(ddsHandler) , m_db(db) , m_exTblGen(exTableGenerator) , m_startupExerciseTimer(new QTimer(this)) , m_logger(getLogger("HandlerExercise")) { //m_imageCache.setMaxCost(15); m_startupExerciseTimer->setSingleShot(false); m_startupExerciseTimer->setInterval(2000); connect(m_startupExerciseTimer, &QTimer::timeout, this, &HandlerExercise::onStartNextExercise); } HandlerExercise::~HandlerExercise() { qDeleteAll(m_exeObjMap); } bool HandlerExercise::init() { DEBUG_LOG(m_logger) << "Finish Initialise DbAdministration (dba) "; INFO_LOG(m_logger) << "Data available for services: " << m_exTblGen->getAvailableServices().join(QLatin1Char(',')); QVector dataVec; // read all exercises from database if (!m_db->select(dataVec)) { ERROR_LOG(m_logger) << "Error reading data from database!"; return true; } QVector> exerciseStates; exerciseStates.reserve(dataVec.size()); // create exercise objects for (const Database::ExmExercise &data : qAsConst(dataVec)) { ObjectExercise *ifs(new ObjectExercise(m_ddsHandler, m_db, m_exTblGen, this)); Core::FunctionReplyObject ret = ifs->createObject(data); if (ret.error()) { delete ifs; ERROR_LOG(m_logger) << ret(); return false; } m_exeObjMap.insert(ifs->id(), ifs); exerciseStates.push_back({ifs->id(), ifs->data().exerciseStateId}); } DEBUG_LOG(m_logger) << "m_exeObjMap.size() = " << m_exeObjMap.size(); // make sure to start the important exercises first std::sort(exerciseStates.begin(), exerciseStates.end(), [](const QPair &a, const QPair &b) { if (a.second == b.second) return a.first < b.first; if (a.second == ExerciseState::Started) return true; if (b.second == ExerciseState::Started) return false; if (a.second == ExerciseState::Paused) return true; if (b.second == ExerciseState::Paused) return false; if (a.second == ExerciseState::Created) return true; if (b.second == ExerciseState::Created) return false; return a.first < b.first; }); for (const auto &p : qAsConst(exerciseStates)) m_exercisesToStart.enqueue(p.first); m_startupExerciseTimer->start(); return true; } ObjectExercise *HandlerExercise::getExerciseById(qint32 id) const { return m_exeObjMap.value(id); } bool HandlerExercise::startGroupRequest(const ServiceManagement::GroupRequest &req) { return m_ddsHandler->scmFtc()->startGroupRequest(req, this); } void HandlerExercise::onStartNextExercise() { if (m_exercisesToStart.isEmpty()) { NOTICE_LOG(m_logger) << "onStartNextExercise no more exercises to start"; m_startupExerciseTimer->stop(); delete m_startupExerciseTimer; m_startupExerciseTimer = nullptr; return; } m_startupExerciseTimer->setInterval(m_startupExerciseTimer->interval() + 1000); while (true) { int exId = m_exercisesToStart.dequeue(); auto *objEx = m_exeObjMap.value(exId); if (!objEx) { WARNING_LOG(m_logger) << "Trying to start exercise with id " << exId << " but could not find a handler !?"; continue; } NOTICE_LOG(m_logger) << "Init state machine for exercise " << exId << " (" << objEx->data().name << ")"; objEx->initStateMachine(); break; } m_startupExerciseTimer->start(); } bool HandlerExercise::hasError(const Core::FunctionReplyObject &replyObject, ObjectExercise *ifs) const { if (replyObject.error()) { delete ifs; ERROR_LOG(m_logger) << replyObject(); return true; } return false; } template static Database::ExmExercise copyBaseData(const T &in) { Database::ExmExercise newData; // exerciseState defined in ObjectExercise newData.id = in.id(); newData.name = QString::fromStdString(in.name()); newData.beginTime = QDateTime::fromMSecsSinceEpoch(in.beginTime()); newData.endTime = QDateTime::fromMSecsSinceEpoch(in.endTime()); newData.startTime = QDateTime::fromMSecsSinceEpoch(in.startTime()); newData.stopTime = QDateTime::fromMSecsSinceEpoch(in.stopTime()); newData.trainingAreaId = in.trainingAreaId(); if (in.radioGroupId().is_set()) newData.radioGroupId = in.radioGroupId().get(); newData.symbolFillTypeId = in.symbolFillTypeId(); newData.linkedExerciseId = in.linkedExerciseId(); newData.defaultTimeZone = QLatin1Char(in.defaultTimeZone()); newData.sendToWms = in.sendToWMS(); newData.transferred = in.transferred(); newData.hasHlaInterface = in.hasHlaInterface(); newData.forces = QString::fromStdString(in.forces()); newData.description = QString::fromStdString(in.description()); return newData; } void HandlerExercise::onExerciseCreateRequest(const rti::core::SampleIdentity &cookie, const ExerciseManagement::ExerciseCreateRequest &request) { auto ret = exerciseCreateRequest(request); if (ret.error()) { WARNING_LOG(m_logger) << ret(); } m_ddsHandler->fts()->exerciseCreateReply(cookie, ret); } Core::FunctionReplyObject HandlerExercise::exerciseCreateRequest(const ExerciseCreateRequest &request) { DEBUG_LOG(m_logger) << "onExerciseCreateRequest: " << request; Database::ExmExercise newData(copyBaseData(request)); newData.linkedExerciseId = 0; DBTransaction trans(m_db->database()); int iCnt = 0; QSet linkedExerciseIds; for (const ObjectExercise *ex : qAsConst(m_exeObjMap)) { const auto state = ex->exerciseState(); // ignore linked exercises if (linkedExerciseIds.contains(ex->id())) continue; if (ex->linkedExerciseId() != 0) linkedExerciseIds += ex->linkedExerciseId(); if (state == ExerciseState::Created) ++iCnt; } if (iCnt >= Settings::maxPreparedExercises()) return Core::FunctionReplyObject(tr_lts("dds", "Can not create exercise - only %1 prepared exercise(s) allowed")).arg(Settings::maxPreparedExercises()); ObjectExercise *ifs(new ObjectExercise(m_ddsHandler, m_db, m_exTblGen, this)); Core::FunctionReplyObject ret = ifs->createObject(newData, true); if (hasError(ret, ifs)) { // remove failed create exercise request from DDS bus (id = 0) ERROR_LOG(m_logger) << "CreateExercise failed, removing request from dds"; m_ddsHandler->dts()->removeExerciseByKey(0); return ret; } // Create default phase for exercise ExercisePhaseCreateRequest phaseData; phaseData.name(tr("Phase 1").toStdString()); phaseData.description(tr("Default exercise phase").toStdString()); phaseData.beginTime(request.beginTime()); phaseData.endTime(request.endTime()); phaseData.exerciseId(ifs->id()); ret = m_mainHandler->exercisePhaseHandler()->exercisePhaseCreateRequest(phaseData); if (hasError(ret, ifs)) { ERROR_LOG(m_logger) << "CreateExercisePhase failed, removing requests from dds"; m_ddsHandler->dts()->removeExercisePhaseByKey(0); m_ddsHandler->dts()->removeExerciseByKey(0); return ret; } // Set default phase as active in exercise newData = ifs->data(); newData.activePhaseId = ret.id(); ret = ifs->updateObject(newData); if (hasError(ret, ifs)) { ERROR_LOG(m_logger) << "Set active phase failed, removing requests from dds"; m_ddsHandler->dts()->removeExercisePhaseByKey(0); m_ddsHandler->dts()->removeExerciseByKey(0); return ret; } ifs->initStateMachine(); m_exeObjMap.insert(ifs->id(), ifs); ret.setId(ifs->id()); // set the returned id to the new exercise id trans.commit(); return Core::FunctionReplyObject(ifs->id()); } void HandlerExercise::onExerciseModifyRequest(const rti::core::SampleIdentity &cookie, const ExerciseManagement::ExerciseModifyRequest &request) { auto ret = exerciseModifyRequest(request); if (ret.error()) WARNING_LOG(m_logger) << ret(); m_ddsHandler->fts()->exerciseModifyReply(cookie, ret); } Core::FunctionReplyObject HandlerExercise::exerciseModifyRequest(const ExerciseModifyRequest &request) { INFO_LOG(m_logger) << "onExerciseModifyRequest: " << request; ObjectExercise *ifs = m_exeObjMap.value(request.id()); if (ifs == nullptr) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Exercise with id %1 not found")).arg(request.id()); if (ifs->exerciseState() == ExerciseState::Created && request.newState() == ExerciseState::Started) { // check if the running exercise has CnrChannels, which are already included in the global CnrConfigs QStringList doubleChannelNames = doubleChannels(request.id(), 0); if (!doubleChannelNames.isEmpty()) { std::sort(doubleChannelNames.begin(), doubleChannelNames.end()); return Core::FunctionReplyObject(request.id(), tr_lts("dds", "The following channel(s) are already planned in the global CnrConfig: %1")).arg(doubleChannelNames.join(QLatin1String(", "))); } // check if another exercise is already running, only allow N active exercises in parallel // check if another running exercise has HLA interface on. int iCntRunningExercises = 0; QSet linkedExerciseIds; for (const ObjectExercise *ex : qAsConst(m_exeObjMap)) { const auto state = ex->exerciseState(); if (ex->linkedExerciseId() == request.id()) { doubleChannelNames = doubleChannels(ex->id(), 0); if (!doubleChannelNames.isEmpty()) { std::sort(doubleChannelNames.begin(), doubleChannelNames.end()); return Core::FunctionReplyObject(request.id(), tr_lts("dds", "The following channel(s) are planned in the global CnrConfig and the linked exercise %1: %2")).arg(ex->name()).arg(doubleChannelNames.join(QLatin1String(", "))); } } // ignore linked exercises if (linkedExerciseIds.contains(ex->id())) continue; if (ex->linkedExerciseId() != 0) { linkedExerciseIds += ex->linkedExerciseId(); } if (state == ExerciseState::Started || state == ExerciseState::Paused) ++iCntRunningExercises; } if (iCntRunningExercises >= Settings::maxStartedExercises()) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not start exercise - only %1 started exercise(s) allowed")).arg(Settings::maxStartedExercises()); } if (request.newState() == ExerciseState::Stopped) { int iCnt = 0; for (const ObjectExercise *ex : qAsConst(m_exeObjMap)) { const auto state = ex->exerciseState(); if (ex->id() != request.id() && state == ExerciseState::Stopped) ++iCnt; } if (Settings::maxStoppedExercises() > 0 && iCnt >= Settings::maxStoppedExercises()) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not stop exercise - there are %1 stopped exercise(s) from maximal %2 allowed")).arg(iCnt).arg(Settings::maxStoppedExercises()); } if (request.hasHlaInterface() && (request.newState() == ExerciseState::Started || request.newState() == ExerciseState::Paused)) { int iCnt = 0; for (const ObjectExercise *ex : qAsConst(m_exeObjMap)) { if (ex->id() != request.id() && ex->data().hasHlaInterface && (ex->exerciseState() == ExerciseState::Started || ex->exerciseState() == ExerciseState::Paused)) ++iCnt; } if (iCnt >= Settings::maxHlaExercises()) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not modify exercise - only %1 running exercise(s) with HLA allowed")).arg(Settings::maxHlaExercises()); } if (ifs->exerciseState() != ExerciseManagement::ExerciseState::Created) { if (request.linkedExerciseId() != ifs->linkedExerciseId()) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not change linked exercise when exercise is already started")); if (request.sendToWMS() == false && ifs->data().sendToWms == true) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Exercise is already visible in WMS - can not reset this option")); } if (request.linkedExerciseId() == 0) { if (ifs->linkedExerciseId() != 0) { ObjectExercise *linked = m_exeObjMap.value(ifs->linkedExerciseId()); if (linked != nullptr) { if (!linked->setLinkedExerciseId(0)) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not update unlinked exercise")); } } } else // request.linkedExercise() != 0 { ObjectExercise *linked = m_exeObjMap.value(request.linkedExerciseId()); if (linked == nullptr) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not find linked exercise with id %1")).arg(request.linkedExerciseId()); if (linked->linkedExerciseId() != request.id()) { if (linked->linkedExerciseId() != 0) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "The exercise to link already has another linked exercise")); // own state is checked above if (linked->exerciseState() != ExerciseManagement::ExerciseState::Created) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not link to an already running exercise")); QStringList doubleChannelNames = doubleChannels(request.id(), request.linkedExerciseId()); if (!doubleChannelNames.isEmpty()) { std::sort(doubleChannelNames.begin(), doubleChannelNames.end()); return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Linked exercise has overlapping CnrConfig(s) for channel(s): %1")).arg(doubleChannelNames.join(QLatin1String(", "))); } if (!linked->setLinkedExerciseId(request.id())) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not update linked exercise")); } if (linked->sendToWms() != request.sendToWMS()) { if (!linked->setSendToWms(request.sendToWMS())) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not update linked exercise")); } } DEBUG_LOG(m_logger) << "Linked exercise: " << request.linkedExerciseId() << ", state: " << ifs->exerciseState() << ", new: " << request.newState(); if (request.linkedExerciseId() != 0 && ifs->exerciseState() != request.newState()) { if (request.newState() == ExerciseManagement::ExerciseState::Started || request.newState() == ExerciseManagement::ExerciseState::Stopped) { ObjectExercise *ifsLinked = m_exeObjMap.value(request.linkedExerciseId()); if (!ifsLinked) WARNING_LOG(m_logger) << "Linked exercise with id " << request.linkedExerciseId() << " not found!"; else { if (ifsLinked->exerciseState() != ExerciseManagement::ExerciseState::Paused || request.newState() == ExerciseManagement::ExerciseState::Stopped) { auto retLinked = ifsLinked->switchToState(request.newState(), request.operatorId(), request.workstationId()); if (retLinked.error()) return retLinked; } } } } // state change to started requested? (case 'start' or 'resume') if ((request.newState() != ifs->exerciseState() ) && (request.newState() == ExerciseManagement::ExerciseState::Started)) { QString mesg; if (!checkEquipmentNumbersOnAllExercises(request.id(), mesg)) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "Can not start exercise - equipment numbers already in use: %1 ")).arg(mesg); } Database::ExmExercise data(copyBaseData(request)); data.exerciseStateId = request.newState(); data.activePhaseId = request.activePhaseId(); data.operatorId = request.operatorId(); data.workstationId = request.workstationId(); return ifs->updateObject(data); } //void HandlerExercise::onExerciseChangeStateRequest(const rti::core::SampleIdentity &cookie, // const ExerciseManagement::ExerciseChangeStateRequest &request) //{ // Core::FunctionReplyObject ret; // INFO_LOG(m_logger) << "onExerciseChangeStateRequest id = " << request.id(); // ObjectExercise *ifs = m_exeObjMap.value(request.id()); // if (ifs == nullptr) // { // ret = Core::FunctionReplyObject(request.id(), tr_lts("dds", "Exercise with id %1 not found")).arg(request.id()); // m_ddsHandler->fts()->exerciseChangeStateReply(cookie, ret); // return; // } // DEBUG_LOG(m_logger) << "request.newState() = " << request.newState(); // ret = ifs->switchToState(request.newState()); // if (!ret.error() && ifs->linkedExerciseId() != 0) // { // if (request.newState() == ExerciseManagement::ExerciseState::Started || // request.newState() == ExerciseManagement::ExerciseState::Stopped) // { // ObjectExercise *ifsLinked = m_exeObjMap.value(ifs->linkedExerciseId()); // if (!ifsLinked) // WARNING_LOG(m_logger) << "Linked exercise with id " << ifs->linkedExerciseId() << " not found!"; // else // { // if (ifsLinked->exerciseState() != ExerciseManagement::ExerciseState::Paused || // request.newState() == ExerciseManagement::ExerciseState::Stopped) // ret = ifsLinked->switchToState(request.newState()); // } // } // } // if (ret.error()) // ERROR_LOG(m_logger) << ret(); // m_ddsHandler->fts()->exerciseChangeStateReply(cookie, ret); //} //uint qHash(const HandlerExercise::ImageKey &key) //{ // return ::qHash(key.m_size.height()) ^ (::qHash(key.m_size.width()) >> 2) ^ ::qHash(key.m_lowBandwidth); //} void HandlerExercise::onGetExerciseImageRequest(const rti::core::SampleIdentity &cookie, const GetExerciseImageRequest &request) { Core::FunctionReplyObject fro; GetExerciseImageReply reply; int32_t exerciseId = request.exerciseId(); reply.id(exerciseId); DEBUG_LOG(m_logger) << "onGetExerciseImageRequest id = " << exerciseId; Database::ExmExerciseImage exmImg; if (!m_db->selectExmExerciseImage(exerciseId, exmImg)) fro = Core::FunctionReplyObject(exerciseId, tr_lts("dds", "Exercise image with id %1 not found")).arg(exerciseId); if (fro.error()) { reply.id(fro.id()); reply.errorString(fro.str()); reply.errorParams(fro.args()); } else { reply.imageData(LtsUtils::convert>(exmImg.image)); // TODO: reimpl optimization routines (e.g. use ImageMagick instead of GuiApplication, which won't work without an X server) // QPixmap excPx(LtsUtils::getPixmap(exmImg.image)); // if (!excPx.isNull()) // { // QSize newImgSize(request.maxWidth(), request.maxHeight()); // QRect requestedRect(QPoint(0, 0), newImgSize); // QRect pictureRect(QPoint(0, 0), excPx.size()); // if (pictureRect.contains(requestedRect)) // only shrink, don't grow (which would make the image unnecessarily bigger) // { // if (request.lowBandwidth()) // make the image even smaller on limited bandwidth (e.g. Tablet on WiFi) // newImgSize /= 2.5; // excPx = excPx.scaled(newImgSize, Qt::KeepAspectRatio); // } // reply.imageData(LtsUtils::fromPixmap(excPx)); // } // else // ERROR_LOG(m_logger) << "image isNull for exercise " << exerciseId; } /* test behaviour on bad network speeds QTimer::singleShot(5000, this, [this, cookie, reply]() {*/ m_ddsHandler->fts()->getExerciseImageReply(cookie, reply); /* });*/ } void HandlerExercise::onExerciseImageModifyRequest(const rti::core::SampleIdentity &cookie, const ExerciseImageModifyRequest &request) { qint32 exerciseId = request.id(); DEBUG_LOG(m_logger) << "onExerciseImageModifyRequest id = " << exerciseId << ", image size = " << request.image().size(); Database::ExmExerciseImage exImg; exImg.exerciseId = exerciseId; bool result = m_db->selectExmExerciseImage(exerciseId, exImg); exImg.image = LtsUtils::toQByteArray(request.image()); if (result) { // exists => update if (!m_db->update(exImg)) { m_ddsHandler->fts()->exerciseImageModifyReply(cookie, Core::FunctionReplyObject(exerciseId, tr_lts("dds", "Error updating Exercise image for exercise id %1")).arg(exerciseId)); return; } } else { // doesn't exist yet => insert if (!m_db->insert(exImg)) { m_ddsHandler->fts()->exerciseImageModifyReply(cookie, Core::FunctionReplyObject(exerciseId, tr_lts("dds", "Error inserting Exercise image for exercise id %1")).arg(exerciseId)); return; } } m_ddsHandler->fts()->exerciseImageModifyReply(cookie, Core::FunctionReplyObject(exerciseId)); } void HandlerExercise::onExerciseRemoveRequest(const rti::core::SampleIdentity &cookie, const ExerciseManagement::ExerciseRemoveRequest &request) { Core::FunctionReplyObject ret; ObjectExercise *ifs = m_exeObjMap.value(request.id()); if (ifs == nullptr) { ret = Core::FunctionReplyObject(request.id(), tr_lts("dds", "Exercise with id %1 not found")).arg(request.id()); m_ddsHandler->fts()->exerciseRemoveReply(cookie, ret); return; } ExerciseManagement::Exercise ddsData; ddsData.id(request.id()); if (m_mainHandler->dds()->topicLockMaster()->isLocked(ddsData, 0)) { ret = Core::FunctionReplyObject(request.id(), tr_lts("dds", "Exercise '%1' is used by another user!")).arg(ifs->data().name); m_mainHandler->dds()->fts()->exerciseRemoveReply(cookie, ret); return; } ret = ifs->removeObject(request.force()); if (ret.error()) { ERROR_LOG(m_logger) << ret(); } else { // delete ObjectExercise (including ExerciseStateMachine) delete ifs; m_exeObjMap.remove(request.id()); } m_ddsHandler->fts()->exerciseRemoveReply(cookie, ret); return; } void HandlerExercise::onStartGroupReply(const Core::FunctionReplyObject &replyObject) { if (replyObject.error()) WARNING_LOG(m_logger) << "Error starting group: " << replyObject(); } bool HandlerExercise::checkEquipmentNumbersOnAllExercises(int exerciseIdToBeStarted, QString &mesgOut) { INFO_LOG(m_logger) << LOGFUNCTION <<" START, exerciseIdTobeStarted: " << exerciseIdToBeStarted; // only one exercise, about to start if (m_exeObjMap.size() <= 1) return true; // first // get all eqIds of exercise to be started QSet eqNoForEx; { std::shared_ptr ddsConnectorForEx; int domainId = m_ddsHandler->ddsConnector()->getDomainId(); ddsConnectorForEx = std::make_shared(domainId, IDL::PartitionType::Exercise, exerciseIdToBeStarted); ddsConnectorForEx->init(); ddsConnectorForEx->initSubscriber(); std::shared_ptr ppmDtcForEx; ppmDtcForEx = ParticipantManagement::Interface::Dtc::create(ddsConnectorForEx); if (!ppmDtcForEx) { WARNING_LOG(m_logger) << LOGFUNCTION << " no ppmDtcForEx (" << exerciseIdToBeStarted << ")"; return true; } ppmDtcForEx->enableParticipant(); ppmDtcForEx->waitForHistoricalDataParticipant(); // <---------- waitForHistoricalDataParticipant may take a while :-( const auto cb = [&eqNoForEx](const ParticipantManagement::Participant &data) -> void { if (data.equipmentNumber().is_set()) eqNoForEx.insert(data.equipmentNumber().get()); }; ppmDtcForEx->participants(cb); } // second: // get ParticipantManagement::EquipmentExerciseMapping // all eqIds of running/started exercises std::shared_ptr ppmDtc = m_ddsHandler->ppmDtc(); if (!ppmDtc) { WARNING_LOG(m_logger) << LOGFUNCTION << " no ppmDtc"; return true; } QMap> usedEqNumbers; const auto cb = [this, &usedEqNumbers, exerciseIdToBeStarted, eqNoForEx](const ParticipantManagement::EquipmentExerciseMapping &equipmentExerciseMapping) -> void { const int exerciseId = equipmentExerciseMapping.exerciseId(); // in case of resume exercise: if (exerciseId == exerciseIdToBeStarted) return; // typically just exercises with state == Started should be published. // but to be sure: ObjectExercise *ifs = m_exeObjMap.value(exerciseId); if (!ifs) { ERROR_LOG(m_logger) << LOGFUNCTION << " Exercise not found, exId = " << exerciseId; return; } if ((ifs->exerciseState() == ExerciseState::Started) || (ifs->exerciseState() == ExerciseState::Paused)) { const std::vector &mapping = equipmentExerciseMapping.mapping(); for (const auto &eq2p : qAsConst(mapping)) if (eqNoForEx.contains(eq2p.equipmentNumber())) { DEBUG_LOG(m_logger) << LOGFUNCTION << " !!! eq = " << eq2p.equipmentNumber() << " is already used in exercise =" << exerciseId; usedEqNumbers[exerciseId].insert(eq2p.equipmentNumber()); } } }; ppmDtc->equipmentExerciseMappings(cb); if (!usedEqNumbers.isEmpty()) { // convert to msg string: QMapIterator> mapIt(usedEqNumbers); while (mapIt.hasNext()) { mapIt.next(); int exerciseId = mapIt.key(); const QSet &usedEqNumbersPerExercise = mapIt.value(); if (!usedEqNumbersPerExercise.isEmpty()) { mesgOut += QString(QStringLiteral("'%1' -> (")).arg(m_exeObjMap[exerciseId]->data().name); QSetIterator setIt(usedEqNumbersPerExercise); while (setIt.hasNext()) { int eqNo = setIt.next(); mesgOut += QString::number(eqNo); if (setIt.hasNext()) mesgOut += QStringLiteral(","); } // while mesgOut += QStringLiteral("); "); } } // while WARNING_LOG(m_logger) << LOGFUNCTION << " state change request not allowed: mesgOut:" << mesgOut; // limit length: if (mesgOut.size()>200) { mesgOut.resize(200); mesgOut += QStringLiteral("..."); } return false; } return true; } QStringList HandlerExercise::doubleChannels(int exerciseAId, int exerciseBId) const { auto cnrConfigs = m_ddsHandler->avmDtc()->cnrConfigs(); QVector exerciseAChannelIds; QVector exerciseBChannelIds; exerciseAChannelIds.reserve(cnrConfigs.size()); exerciseBChannelIds.reserve(cnrConfigs.size()); for (auto it = cnrConfigs.constBegin(); it != cnrConfigs.constEnd(); ++it) { int cnrExerciseId = it.value().exerciseId().is_set() ? it.value().exerciseId().get() : 0; if (cnrExerciseId == exerciseAId) { exerciseAChannelIds.append(it.value().cnrChannelId()); continue; } if (cnrExerciseId == exerciseBId) { exerciseBChannelIds.append(it.value().cnrChannelId()); continue; } } QStringList doubleChannelNames; doubleChannelNames.reserve(exerciseAChannelIds.size()); for (int exerciseAChannelId : qAsConst(exerciseAChannelIds)) { if (exerciseBChannelIds.contains(exerciseAChannelId)) doubleChannelNames.append(QString::fromStdString(m_ddsHandler->avmDtc()->cnrChannel(exerciseAChannelId).name())); } return doubleChannelNames; } } // Namespace ExerciseManagement