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

360 lines
10 KiB
C++
Executable File

/**
* Communication with A/V system
* $Id$
* $HeadURL$
* $Author$
*/
#include "av_control.h"
#include <QTimer>
#include <QList>
#include <time.h>
// TODO: on reconnect operatingmode abgleichen!
namespace AudioVideoManagement
{
AVControl::AVControl(const QString &avsServerHost, quint16 avsServerPort, QObject *parent)
: AVSocket(avsServerHost, avsServerPort, getLogger("AVControl"), getLogger("AVControl.IO"), parent)
, m_basicOperatingMode(BasicOperatingMode::PowerOn)
, m_bWaitingForSwitchContext(false)
, m_keepAliveTime(-1)
, m_keepAliveTimer(new QTimer(this))
{
m_keepAliveTimer->setSingleShot(false);
m_keepAliveTimer->setInterval(2000);
connect(m_keepAliveTimer, &QTimer::timeout,
this, &AVControl::onCheckKeepAlive);
}
AVControl::~AVControl()
{
delete m_keepAliveTimer;
}
bool AVControl::triggerAVUpdate()
{
DEBUG_LOG(m_logger) << "triggerAVUpdate ...";
// switching to init triggers a reload of the database
return setOperatingMode(ExerciseContext::defaultContext(), Component::AudioAndVideo, OperatingMode::Initialization) &&
setOperatingMode(ExerciseContext::defaultContext(), Component::AudioAndVideo, OperatingMode::PowerOn);
}
bool AVControl::setOperatingModeForContext(const ExerciseContext &context, OperatingMode om)
{
if (!m_bSuccessfullyLoggedIn)
return false;
return setOperatingMode(context, Component::AudioAndVideo, om);
}
void AVControl::onReadyRead()
{
static const QByteArray baGreeter("RCP/AVE 1.0");
while (m_avsSocket->canReadLine())
{
const QByteArray line = m_avsSocket->readLine().trimmed();
if (line.isEmpty())
continue;
DEBUG_LOG(m_loggerIO) << "From A/V: '" << line << "'";
if (line == baGreeter)
onRcpLogin();
else
{
const auto baList = parseLine(line);
bool bOk;
const Commands cmdId = static_cast<Commands>(baList.at(0).toInt(&bOk));
if (!bOk)
{
ERROR_LOG(m_logger) << "Error parsing command: '" << line << "'";
continue;
}
switch (cmdId)
{
case Commands::WrongSessionPriority:
// Try relogin
m_bSuccessfullyLoggedIn = false;
m_rcpLoginTimer->start();
break;
case Commands::Ok:
sendNextPendingCommand();
break;
case Commands::SessionDisabled:
// Try relogin
m_bSuccessfullyLoggedIn = false;
m_rcpLoginTimer->start();
break;
case Commands::SessionEnabled:
m_bSuccessfullyLoggedIn = true;
onSendKeepAlive();
trySendCommand(boost::none, "EnableFMServer");
emit successfullyLoggedIn();
break;
case Commands::Context:
if (baList.size() >= 2)
{
m_currentContext = ExerciseContext::fromByteArray(baList.at(1));
m_bWaitingForSwitchContext = false;
sendNextPendingCommand();
}
break;
case Commands::BasicOperatingMode:
if (baList.size() >= 2)
{
const BasicOperatingMode mode = static_cast<BasicOperatingMode>(baList.at(1).toInt(&bOk));
if (!bOk)
{
ERROR_LOG(m_logger) << "Error parsing mode: '" << line << "'";
break;
}
DEBUG_LOG(m_logger) << "Current " << mode;
checkBasicOperatingMode(mode);
}
else
ERROR_LOG(m_logger) << "Invalid arguments to BasicOperatingMode: '" << line << "'";
break;
case Commands::ComponentBasicOperatingMode:
break; // nothing to do
case Commands::OperatingMode:
if (baList.size() >= 3)
{
const Component comp = static_cast<Component>(baList.at(1).toInt(&bOk));
if (!bOk)
{
ERROR_LOG(m_logger) << "Error parsing component: '" << line << "'";
break;
}
const OperatingMode mode = static_cast<OperatingMode>(baList.at(2).toInt(&bOk));
if (!bOk)
{
ERROR_LOG(m_logger) << "Error parsing mode: '" << line << "'";
break;
}
emit operatingModeChanged(*m_currentContext, comp, mode);
}
else
ERROR_LOG(m_logger) << "Invalid arguments to OperatingMode: '" << line << "'";
break;
case Commands::FMServerState:
// TODO!
break;
case Commands::KeppAlive:
m_keepAliveTime = time(nullptr);
break;
case Commands::WrongCommand:
case Commands::BadArguments:
case Commands::CommandTooLong:
case Commands::CommandNotAllowed:
case Commands::CommandNotImplemented:
case Commands::CommandNotStateValid:
{
ERROR_LOG(m_logger) << "Sent wrong command to A/V";
QByteArray commands;
while (!m_commandQueueForErrorHandling.empty())
{
commands += m_commandQueueForErrorHandling.front() + "\n";
m_commandQueueForErrorHandling.pop();
}
if (!commands.isEmpty())
{
ERROR_LOG(m_logger) << "Last " << m_commandQueueForErrorHandling.size()
<< " commands: " << commands;
}
break;
}
case Commands::InternalError:
ERROR_LOG(m_logger) << "Internal A/V error: '" << line << "'";
break;
case Commands::InvalidRCPVersion:
ERROR_LOG(m_logger) << "Invalid RCP-Version: '" << line << "'";
break;
}
}
}
}
void AVControl::onSendKeepAlive()
{
if (!m_bSuccessfullyLoggedIn)
return;
if (!sendInternal("KEEPALIVE"))
return;
m_keepAliveTime = time(nullptr);
m_keepAliveTimer->start();
}
void AVControl::onCheckKeepAlive()
{
if (!m_bSuccessfullyLoggedIn)
return;
// 3 Sekunden
if (m_keepAliveTime + 3 < time(nullptr))
{
WARNING_LOG(m_logger) << "No KeepAlive from A/V since more than 3 seconds - reconnecting";
m_avsSocket->disconnectFromHost();
return;
}
onSendKeepAlive();
}
bool AVControl::trySendCommand(const boost::optional<ExerciseContext> &ctx, const QByteArray &cmd)
{
if (!m_avsSocket || !m_avsSocket->isWritable())
{
WARNING_LOG(m_logger) << "Trying to send command but not connected to AVS";
return false;
}
const bool pendingCommandsInQueue = m_commandsToSend.size() != 0; // do not use empty() here!
//DEBUG_LOG(m_logger) << "Trying to send command for context " << ctx
// << ": '" << cmd
// << "' - waiting for other commands: "
// << m_commandsToSend.size()
// << ", " << !pendingCommandsInQueue;
m_commandsToSend.push({ctx, cmd});
if (!pendingCommandsInQueue)
sendNextPendingCommand();
return true;
}
void AVControl::sendNextPendingCommand()
{
if (!m_bSuccessfullyLoggedIn ||
m_bWaitingForSwitchContext ||
m_commandsToSend.empty())
{
//DEBUG_LOG(m_logger) << "Nothing to send or not connected: "
// << m_bSuccessfullyLoggedIn << ", "
// << m_bWaitingForSwitchContext << ", "
// << m_commandsToSend.empty() << ", "
// ;
return;
}
const CommandToSend &cts = m_commandsToSend.front();
// if a context is required and does not match -> switch context first
if (cts.ctx && cts.ctx != m_currentContext)
{
if (sendInternal("SwitchContext " + cts.ctx->toByteArray()))
m_bWaitingForSwitchContext = true;
return;
}
if (sendInternal(cts.cmd))
m_commandsToSend.pop();
}
void AVControl::checkBasicOperatingMode(BasicOperatingMode mode)
{
if (!m_bSuccessfullyLoggedIn)
return;
INFO_LOG(m_logger) << " Is: " << mode
<< " - should be: " << m_basicOperatingMode;
if (mode != m_basicOperatingMode)
trySendCommand(boost::none, "SetBasicOperatingMode " + QByteArray::number(static_cast<int>(m_basicOperatingMode)));
}
bool AVControl::setOperatingMode(const ExerciseContext &context, Component component, OperatingMode mode)
{
if (!m_bSuccessfullyLoggedIn)
return false;
INFO_LOG(m_logger) << "Setting OperatingMode for " << component
<< " in context " << context
<< " to: " << mode;
trySendCommand(context, "SetOperatingMode " + QByteArray::number(static_cast<int>(component)) +
" " + QByteArray::number(static_cast<int>(mode)));
return true;
}
std::ostream &operator <<(std::ostream &stream, AVControl::BasicOperatingMode bom)
{
stream << "BasicOperatingMode ";
switch (bom)
{
case AVControl::BasicOperatingMode::Unknown:
stream << "Unknown";
break;
case AVControl::BasicOperatingMode::Reset:
stream << "Reset";
break;
case AVControl::BasicOperatingMode::Shutdown:
stream << "Shutdown";
break;
case AVControl::BasicOperatingMode::Service:
stream << "Service";
break;
case AVControl::BasicOperatingMode::PowerOn:
stream << " PowerOn";
break;
}
stream << " (" << static_cast<int>(bom) << ")";
return stream;
}
std::ostream &operator <<(std::ostream &stream, AVControl::OperatingMode om)
{
stream << "OperatingMode ";
switch (om)
{
case AVControl::OperatingMode::Unknown:
stream << "Unknown operating mode";
break;
case AVControl::OperatingMode::PowerOn:
stream << "PowerOn";
break;
case AVControl::OperatingMode::Initialization:
stream << "Initialization";
break;
case AVControl::OperatingMode::Recording:
stream << "Recording";
break;
case AVControl::OperatingMode::Pause:
stream << "Pause";
break;
case AVControl::OperatingMode::SaveData:
stream << "SaveData";
break;
case AVControl::OperatingMode::Cleanup:
stream << "Cleanup";
break;
}
stream << " (" << static_cast<int>(om) << ")";
return stream;
}
std::ostream &operator <<(std::ostream &stream, AVControl::Commands cmd)
{
stream << "Commands: " << static_cast<int>(cmd);
return stream;
}
std::ostream &operator <<(std::ostream &stream, AVControl::Component comp)
{
stream << "Component ";
switch (comp)
{
case AVControl::Component::Audio:
stream << "Audio";
break;
case AVControl::Component::Video:
stream << "Video";
break;
case AVControl::Component::AudioAndVideo:
stream << "Audio+Video";
break;
case AVControl::Component::Unknown:
break;
}
stream << " (" << static_cast<int>(comp) << ")";
return stream;
}
} // namespace AudioVideoManagement