/** * Communication with A/V system * $Id$ * $HeadURL$ * $Author$ */ #include "av_control.h" #include #include #include // 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(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(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(baList.at(1).toInt(&bOk)); if (!bOk) { ERROR_LOG(m_logger) << "Error parsing component: '" << line << "'"; break; } const OperatingMode mode = static_cast(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 &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(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(component)) + " " + QByteArray::number(static_cast(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(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(om) << ")"; return stream; } std::ostream &operator <<(std::ostream &stream, AVControl::Commands cmd) { stream << "Commands: " << static_cast(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(comp) << ")"; return stream; } } // namespace AudioVideoManagement