Files
2026-08-13 18:33:10 +02:00

311 lines
9.8 KiB
C++
Executable File

/**
* 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 <LtsUtilsCore/json_object.h>
#include <chrono>
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