/** * Exercises Handler - Handle all information which is needed for AVS to create the proper AV-Streams for a specific context * $Id$ * $HeadURL$ * $Author$ * Created by Christian Ehrlicher, SLSE, 2016-10-20 */ #include "exercise_handler.h" #include "exercises_handler.h" #include "dds_handler.h" #include "main_handler.h" #include #include using LtsUtils::JsonObject; namespace { QString avRootName() { return QStringLiteral("ECRS"); } QString avRecordReplayDefintion() { return QStringLiteral("mode=1;rootfilepath=/data/AVS"); } QString avController() { return QStringLiteral("EXCAVS01"); } bool isActive(ExerciseManagement::ExerciseState state) { return (state == ExerciseManagement::ExerciseState::Started || state == ExerciseManagement::ExerciseState::Paused); } } // anonymous namespace namespace AudioVideoManagement { ExerciseHandler::ExerciseHandler(MainHandler *mainHandler, int exerciseId) : QObject() , m_exContext(exerciseId) , m_curExerciseState(ExerciseManagement::ExerciseState::Unknown) , m_curAVOperatingMode(AVControl::OperatingMode::Unknown) , m_mainHandler(mainHandler) , m_updateTimer(new QTimer(this)) , m_lastSeenTimer(new QTimer(this)) , m_bWarnedOnce(false) , m_logger(getLogger("ExerciseHandler")) { m_updateTimer->setSingleShot(true); connect(m_updateTimer, &QTimer::timeout, this, &ExerciseHandler::onUpdateTimerTimeout); m_lastSeenTimer->setSingleShot(false); m_lastSeenTimer->setInterval(std::chrono::milliseconds(std::chrono::minutes(10)).count()); connect(m_lastSeenTimer, &QTimer::timeout, this, &ExerciseHandler::onLastSeenTimerTimeout); m_lastSeenTimer->start(); connect(avControl(), &AVControl::operatingModeChanged, this, &ExerciseHandler::onOperatingModeChanged); connect(avControl(), &AVControl::successfullyLoggedIn, this, &ExerciseHandler::onAvSuccessfullyLoggedIn); connect(fmControl(), &FMControl::fileDeletionCompleted, this, &ExerciseHandler::onContextFilesDeleted); } /** * Called whenever the exercise state changed unless the new state is Terminated. * We make sure that for this exercise the Record- and PlaybackSession-entries * exist in avm.device_config. */ void ExerciseHandler::checkForRecordPlaybackSession() { if (m_recordSession.id <= 0) // new { m_recordSession.context = m_exContext.toString(); m_recordSession.controller = avController(); m_recordSession.definition = avRecordReplayDefintion(); m_recordSession.name = avRootName(); m_recordSession.type = avTypeRecordSession(); if (!db()->insert(m_recordSession)) { ERROR_LOG(m_logger) << m_exContext << " Error adding AvmDeviceConfig - aborting"; return; } emit avDbReloadNeeded(); } if (m_replaySession.id <= 0) { m_replaySession.context = m_exContext.toString(); m_replaySession.controller = avController(); m_replaySession.definition = avRecordReplayDefintion(); m_replaySession.name = avRootName(); m_replaySession.type = avTypePlaybackSession(); if (!db()->insert(m_replaySession)) { ERROR_LOG(m_logger) << m_exContext << " Error adding AvmDeviceConfig - aborting"; return; } emit avDbReloadNeeded(); } } void ExerciseHandler::setNewExerciseState(ExerciseManagement::ExerciseState newState) { Q_ASSERT(newState != ExerciseManagement::ExerciseState::Unknown); INFO_LOG(m_logger) << m_exContext << ": state change: " << m_curExerciseState << " -> " << newState; if (m_curExerciseState == newState) return; Q_ASSERT(m_curExerciseState != ExerciseManagement::ExerciseState::Terminated); // No transition from Terminated to some other state allowed. if (newState != ExerciseManagement::ExerciseState::Terminated) checkForRecordPlaybackSession(); else { // We are about to terminate the session. OperatingMode will be switched to Cleanup. // Once the AVS has removed all relevant files it will signal onContextFilesDelete(). // Then, Record- and PlaybackSession will be removed and the deletion of this is triggered. } bool currentIsActive = isActive(m_curExerciseState); bool newIsActive = isActive(newState); if (newIsActive != currentIsActive) { if (newIsActive) { DEBUG_LOG(m_logger) << m_exContext << " activated"; emit contextActivated(); } else { DEBUG_LOG(m_logger) << m_exContext << " deactivated"; emit contextDeactivated(); } } m_curExerciseState = newState; m_updateTimer->start(0); } void ExerciseHandler::setLastSeen(const Database::AvmExerciseLastSeen &lastSeen) { Q_ASSERT(lastSeen.id > 0); m_lastSeen = lastSeen; } void ExerciseHandler::addDeviceConfig(const Database::AvmDeviceConfig &config) { if (config.type == avTypeRecordSession()) m_recordSession = config; else if (config.type == avTypePlaybackSession()) m_replaySession = config; else Q_ASSERT(false); } bool ExerciseHandler::isTerminating() const { return m_curExerciseState == ExerciseManagement::ExerciseState::Terminated; } void ExerciseHandler::onUpdateTimerTimeout() { // check current state and make sure AVS has the same state const AVControl::OperatingMode desiredOperatingMode = avModeFromExerciseState(m_curExerciseState); if (m_curAVOperatingMode == desiredOperatingMode) return; DEBUG_LOG(m_logger) << "Context: " << m_exContext << " Try switching to " << desiredOperatingMode; if (!avControl()->setOperatingModeForContext(m_exContext, desiredOperatingMode)) { if (!m_bWarnedOnce) { DEBUG_LOG(m_logger) << "Context: " << m_exContext << " - could not change operating mode, maybe not yet connected to AVS - trying again in 5 seconds"; m_bWarnedOnce = true; } m_updateTimer->start(5000); // retry in 5sec } else m_bWarnedOnce = false; } void ExerciseHandler::onLastSeenTimerTimeout() { if (isTerminating()) return; // we check if the exercise is still available on dds // if this is not the case, we first switch to AVControl::OperatingMode::Cleanup // and remove all device_config entries except the record/replay session // when the cleanup is finished (signal from A/V) we remove also the last two sessions and delete our self auto ex = exmDtc()->exercise(m_exContext.id()); if (ex.id() == m_exContext.id()) { // found -> update last seen timestamp m_lastSeen.context = m_exContext.toString(); m_lastSeen.lastSeen = QDateTime::currentDateTimeUtc(); if (m_lastSeen.id > 0) db()->update(m_lastSeen); else db()->insert(m_lastSeen); return; } // not found, check for last seen information if (m_lastSeen.id == 0 || m_lastSeen.lastSeen.isNull() || m_lastSeen.lastSeen.addDays(1) < QDateTime::currentDateTimeUtc()) { INFO_LOG(m_logger) << "Exercise with id " << m_exContext.id() << " not seen since more than 1 day -> removing"; // not seen at all or not since more than 1 day -> remove setNewExerciseState(ExerciseManagement::ExerciseState::Terminated); // now wait for finished signal from AVS ... } } void ExerciseHandler::onOperatingModeChanged(const ExerciseContext &context, AVControl::Component comp, AVControl::OperatingMode om) { if (context != m_exContext) return; const AVControl::OperatingMode desiredOperatingMode = avModeFromExerciseState(m_curExerciseState); if (desiredOperatingMode != om) { DEBUG_LOG(m_logger) << "Context: " << context << " Got " << om << " for " << comp << " but expected " << desiredOperatingMode; m_updateTimer->start(100); } else { DEBUG_LOG(m_logger) << "Context: " << context << " Got " << om << " for " << comp << " which is the expected value"; m_updateTimer->stop(); } m_curAVOperatingMode = om; } void ExerciseHandler::onContextFilesDeleted(const ExerciseContext &context) { if (context != m_exContext) return; if (!isTerminating()) return; // all files were deleted, so we are ready with cleaning up - the only thing which is left now // is to remove the record/replay session from db and delete ourself if (m_recordSession.id > 0) db()->removeAvmDeviceConfig(m_recordSession.id); if (m_replaySession.id > 0) db()->removeAvmDeviceConfig(m_replaySession.id); m_recordSession = Database::AvmDeviceConfig(); m_replaySession = Database::AvmDeviceConfig(); if (m_lastSeen.id > 0) db()->removeAvmExerciseLastSeen(m_lastSeen.id); emit removeMeImFinished(m_exContext.id()); } AVControl::OperatingMode ExerciseHandler::avModeFromExerciseState(ExerciseManagement::ExerciseState state) { switch (state.underlying()) { case ExerciseManagement::ExerciseState::Unknown: case ExerciseManagement::ExerciseState::Created: return AVControl::OperatingMode::Initialization; case ExerciseManagement::ExerciseState::Started: return AVControl::OperatingMode::Recording; case ExerciseManagement::ExerciseState::Paused: return AVControl::OperatingMode::Pause; case ExerciseManagement::ExerciseState::Stopped: return AVControl::OperatingMode::SaveData; case ExerciseManagement::ExerciseState::Terminated: return AVControl::OperatingMode::Cleanup; } return AVControl::OperatingMode::Unknown; } AVControl *ExerciseHandler::avControl() const { return m_mainHandler->avControl(); } FMControl *ExerciseHandler::fmControl() const { return m_mainHandler->fmControl(); } DatabaseInterface *ExerciseHandler::db() const { return m_mainHandler->db(); } ExerciseManagement::Interface::Dtc *ExerciseHandler::exmDtc() const { return m_mainHandler->ddsHandler()->exmDtc(); } void ExerciseHandler::onAvSuccessfullyLoggedIn() { m_updateTimer->start(); m_curAVOperatingMode = AVControl::OperatingMode::Unknown; } } // namespace AudioVideoManagement