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

627 lines
21 KiB
C++
Executable File

/**
* @RDE_HEADER@
* AvaComm
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Fröbel, SLSE, 2017-06-16
*/
#include "ava_comm.h"
#include "av_helper.h"
#include <AudioVideoManagement/logger.h>
#include <QString>
#include <QStringList>
#include <QByteArray>
#include <QList>
#include <QTextStream>
#include <QRect>
#include <cmath>
#include <ngltools/ngltools_dtg.h>
namespace AudioVideoManagement
{
AvaComm::AvaComm(const QString &avaHost, quint16 avaPort)
: m_avSocket(avaHost, avaPort, getLogger("AvaComm"), getLogger("AvaComm.IO"))
, m_logger(getLogger("AvaComm"))
, m_loggerIO(getLogger("AvaComm.IO"))
{
connect(&m_avSocket, &AVSocketWrapper::readyRead, this, &AvaComm::onReadyRead);
connect(&m_avSocket, &AVSocketWrapper::disconnected, this, &AvaComm::disconnected); // signal -> signal
}
void AvaComm::start()
{
m_avSocket.connectToAVS();
}
void AvaComm::forceDisconnect()
{
m_avSocket.disconnectFromHost();
}
void AvaComm::switchContext(int exerciseId)
{
m_avSocket.send(QByteArray("switchContext ex_").append(QByteArray::number(exerciseId)));
}
void AvaComm::setUserProfile(const QString &userName, const QString &langCode, AvaAudioPrio audioPrio, AvaGuiStyle style)
{
static const QString cmd = QStringLiteral(R"(setUserProfile "%1" %2 %3 %3 "" "" %4)");
m_avSocket.send(cmd.arg(userName, langCode).arg(static_cast<int>(audioPrio)).arg(static_cast<int>(style)).toUtf8());
}
void AvaComm::setTimezone(QChar tz)
{
char timezone = tz.toLatin1();
if (!DTG::isTimezoneValid(timezone))
{
timezone = 'Z';
Q_ASSERT(false);
}
const int ofs = DTG::timezone2Secs(timezone);
static const QString cmd = QStringLiteral("setTimezone %1 %2");
m_avSocket.send(cmd.arg(tz).arg(ofs).toUtf8());
}
void AvaComm::setVideoDialogProfile(const QString &dialogId, AvaVisibility visibility)
{
static const QString cmd = QStringLiteral(R"(setVideoDialogProfile "%1" * * * * * * * * * * * * * * * %2 * * * *)");
m_avSocket.send(cmd.arg(dialogId).arg(static_cast<int>(visibility)).toUtf8());
}
void AvaComm::setVideoDialogGeometry(const QString &dialogId, const QRect &geometry)
{
static const QString cmd = QStringLiteral(R"(setVideoDialogProfile "%1" * %2 %3 %4 %5 * * * * * * * * * * %6 * * * *)");
m_avSocket.send(cmd.arg(dialogId).arg(geometry.x()).arg(geometry.y()).arg(geometry.width()).arg(geometry.height()).arg(static_cast<int>(AvaVisibility::Visible)).toUtf8());
}
void AvaComm::setVideoProfile(const QString &dialogId, const QString &menuDef)
{
static const QString cmd = QStringLiteral(R"(setVideoProfile "%1" "%2")");
m_avSocket.send(cmd.arg(dialogId, menuDef).toUtf8());
}
void AvaComm::setAnnouncementProfile(AvaSumChannel sumChannel, const QString &menuDef, const QString &identityList, const QString &stateList)
{
static const QString cmd = QStringLiteral(R"(SetAnnouncementProfile "%1" "%2" "%3" %4)");
m_avSocket.send(cmd.arg(menuDef, identityList, stateList, QString::number(static_cast<int>(sumChannel))).toUtf8());
}
void AvaComm::getStreamList(AvaStreamType streamType, AvaStreamSource streamSource)
{
static const QString cmd = QStringLiteral("getStreamList %1 %2");
m_avSocket.send(cmd.arg(static_cast<int>(streamType)).arg(static_cast<int>(streamSource)).toUtf8());
}
void AvaComm::chooseStream(const QString &dialogId, const QString &streamName, bool muted)
{
static const QString cmd = QStringLiteral(R"(chooseStream "%1" %2 "%3")");
m_avSocket.send(cmd.arg(streamName).arg(muted).arg(dialogId).toUtf8());
}
void AvaComm::chooseStreamOverview(const QString &dialogId, const QStringList &streamNames, bool muted)
{
static const QString cmd = QStringLiteral(R"(chooseStreamOverview "%1" %2 "%3")");
Q_ASSERT(!streamNames.empty());
QString streamArgs;
QTextStream s(&streamArgs, QIODevice::WriteOnly);
s << QLatin1Char('{') << streamNames.first() << QLatin1Char('}');
for (int i = 1; i < streamNames.size(); ++i)
s << QStringLiteral(" {") << streamNames.at(i) << QLatin1Char('}');
m_avSocket.send(cmd.arg(streamArgs).arg(muted).arg(dialogId).toUtf8());
}
void AvaComm::releaseStream(const QString &dialogId, const QString &streamName)
{
static const QString cmd = QStringLiteral(R"(releaseStream "%1" "%2")");
m_avSocket.send(cmd.arg(streamName, dialogId).toUtf8());
}
//void AvaComm::setAbsPosition(const QString &dialogId, const QString &streamName, AvaTimePoint pos)
//{
// static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" posa %3)");
// m_avSocket.send(cmd.arg(streamName, dialogId, toString(pos)).toUtf8());
//}
void AvaComm::queryTimeline(const QString &streamName, const boost::optional<AvaTimePoint> &start, const boost::optional<AvaTimePoint> &end)
{
static const QString cmd = QStringLiteral(R"(queryTimeline "%1" 1)"); // 1 = get list
QString finalCmd(cmd.arg(streamName));
if (start && end)
finalCmd += QStringLiteral(" %1 %2").arg(toString(start.get()), toString(end.get()));
m_avSocket.send(finalCmd.toUtf8());
}
void AvaComm::setVolume(AvaVolume db)
{
static const QString cmd = QStringLiteral("setVolume 1 %1");
m_avSocket.send(cmd.arg(db).toUtf8());
}
void AvaComm::startStream(const QString &dialogId, const QString &streamName, AvaSpeed speed)
{
static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" start %3)");
m_avSocket.send(cmd.arg(streamName, dialogId, QString::number(speed)).toUtf8());
}
void AvaComm::stopStream(const QString &dialogId, const QString &streamName)
{
static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" stop)");
m_avSocket.send(cmd.arg(streamName, dialogId).toUtf8());
}
void AvaComm::pauseStream(const QString &dialogId, const QString &streamName)
{
static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" pause)");
m_avSocket.send(cmd.arg(streamName, dialogId).toUtf8());
}
void AvaComm::setStreamSpeed(const QString &dialogId, const QString &streamName, AvaSpeed speed)
{
static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" sped %3)");
m_avSocket.send(cmd.arg(streamName, dialogId, QString::number(speed)).toUtf8());
}
void AvaComm::setAbsPosition(const QString &dialogId, const QString &streamName, AvaTimePoint pos)
{
static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" posa %3)");
m_avSocket.send(cmd.arg(streamName, dialogId, toString(pos)).toUtf8());
}
void AvaComm::positionUpdate(const QString &dialogId, const QString &streamName, std::chrono::milliseconds updateInterval)
{
Q_ASSERT(updateInterval <= std::chrono::seconds(1));
static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" posu %3)");
m_avSocket.send(cmd.arg(streamName, dialogId, QString::number(updateInterval.count())).toUtf8());
}
void AvaComm::setPint(const QString &dialogId, const QString &streamName, bool doSkip)
{
static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" pint %3)");
m_avSocket.send(cmd.arg(streamName, dialogId).arg(!doSkip).toUtf8());
}
void AvaComm::getElokaList(const QString &streamName)
{
static const QString cmd = QStringLiteral(R"(getElokaList "%1")");
m_avSocket.send(cmd.arg(streamName).toUtf8());
}
void AvaComm::chooseEloka(const QString &streamName, const QString &signalName, AvaElokaMode mode)
{
static const QString cmd = QStringLiteral(R"(chooseEloka "%1" "%2" %3)");
m_avSocket.send(cmd.arg(signalName, streamName).arg(static_cast<int>(mode)).toUtf8());
}
void AvaComm::releaseEloka(const QString &streamName)
{
static const QString cmd = QStringLiteral(R"(releaseEloka "%1")");
m_avSocket.send(cmd.arg(streamName).toUtf8());
}
void AvaComm::queryEloka(AvaQueryElokaMode mode)
{
static const QString cmd = QStringLiteral(R"(queryEloka "%1")");
m_avSocket.send(cmd.arg(static_cast<int>(mode)).toUtf8());
}
void AvaComm::defineStreamNameMapping(const QString &internalName, const QString &displayName)
{
static const QString cmd = QStringLiteral(R"(defineStreamNameMapping AUDIO_VIDEO "{%1} {%2}")");
m_avSocket.send(cmd.arg(internalName, displayName).toUtf8());
}
void AvaComm::resetStreamNameMapping()
{
static const QString cmd = QStringLiteral("resetStreamNameMapping AUDIO_VIDEO");
m_avSocket.send(cmd.toUtf8());
}
void AvaComm::keepAlive()
{
static const QString cmd = QStringLiteral("KeepAlive");
m_avSocket.send(cmd.toUtf8());
}
void AvaComm::chooseImport(const QString &dialogId, const QString &path, int mode)
{
static const QString cmd = QStringLiteral(R"(chooseImport "%1" "%2" %3)");
m_avSocket.send(cmd.arg(dialogId, path, QString::number(mode)).toUtf8());
}
void AvaComm::releaseImport(const QString &dialogId)
{
static const QString cmd = QStringLiteral(R"(releaseImport "%1")");
m_avSocket.send(cmd.arg(dialogId).toUtf8());
}
void AvaComm::setScript(const QString &dialogId, const QString &scriptName, AvaScriptSource source, const QVector<AvaScriptEntry> &streams)
{
QStringList streamList;
streamList.reserve(streams.size());
for (const auto &stream : streams)
{
/*/
* NOTE:
* Clemens Fricke on 11 Dec 2020:
* Subject: AvaScriptSource
*
* In lokalen Skripten muss der vollständige Name angegeben werden, der beim Befehl GetStreamList zurückgegeben wird.
* Wenn der StreamName nicht mit einer Extension endet, wird “(S)“ als Default angenommen.
* Nehmen wir den Fall für den Download:
* Die Situation an der RCP_AVA-Schnittstelle ist wie folgt:
*
* >GetStreamList 2 2
* <293 STREAMLIST
* <2 2 "2610 Video Test(S)”
* <299 ENDOFPARAMETERLIST
*
* Im Download Skript sollte dann der Name „2610 Video Test(S)“ verwendet werden:
* SetScript "Download" 2 "LOCAL_RECORD" “{2610 Video Test(S)} 100 0 1603700404.00 1603700415.00 1 0.1\n{2610 Video Test(S)} 100 0 1603700418.00 1603700431.00 1 1\n{2610 Video Test(S)} 100 0 1603700412.00 1603700418.00 1 1”
* Wenn sie nach dem Download die globale und lokale Stream-Liste abfragen ergibt sich folgende Situation:
*
* >GetStreamList 2 2
* <293 STREAMLIST
* <2 2 "2610 Video Test(S)”
* <299 ENDOFPARAMETERLIST
*
* >GetStreamList 2 4
* <293 STREAMLIST
* <2 2 "2610 Video Test(L)”
* <299 ENDOFPARAMETERLIST
*
* Im System gibt es einen Stream mit der Extension (S) vom globalen Server und einen Stream mit der Extension (L) vom privaten lokalen Server.
* Wenn Sie jetzt ein Skript ausführen wollen, das ausschließlich die lokale Kopie verwenden soll, dann müssen sie im Skript entsprechend für alle
* Einträge die Extension (L) angeben.
* z.B. SetScript "Download" 4 "Videodialog 1" "{2610 Video Test(L)} 100 0 1607011182.20 1607011334.73 0\n{2610 Video Test(L)} 100 0 1607013012.64 1607013184.24 0"
* Ich habe im Source-Code nachgesehen, der zweite Parameter ist ein Relikt aus der vorherigen System-Inkarnation. Er wird nicht mehr ausgewertet.
* Die Auswahl zwischen lokal und global wird ausschließlich über die Extension der Stream-Namen innerhalb des Skriptes vorgenommen.
*
* tl;dr AvaScriptSource deprecated for setScript, add (L) for local or (S) for Server to stream name
*
* AvaScriptSource will be used to determine the script source to append to the stream name.
* For compatibility issues, AvaScriptSource is kept as parameter for the method signature, but internally AvaScriptSource will be converted
* to the needed parameters for the RPC call.
/*/
QString sourceString;
switch (source)
{
case AvaScriptSource::LocalAudio:
case AvaScriptSource::LocalVideo:
{
sourceString = QStringLiteral("L");
break;
}
case AvaScriptSource::ServerAudio:
case AvaScriptSource::ServerVideo:
{
sourceString = QStringLiteral("S");
break;
}
}
// not in db here!
const double vol = pow(10.0, double(stream.db) / 20.0);
streamList += QStringLiteral("{%1(%8)} %2 %3 %4 %5 %6 %7")
.arg(stream.name,
QString::number(stream.speed),
QString::number(stream.mute),
toString(stream.start),
toString(stream.stop),
QString::number(!stream.pint),
QString::number(vol),
sourceString);
}
static const QString cmd = QStringLiteral(R"(setScript "%1" %2 "%3" "%4")");
m_avSocket.send(cmd.arg(scriptName).arg((source == AvaScriptSource::LocalAudio || source == AvaScriptSource::ServerAudio) ? 1 : 2) /* ignore source, see comment above for the why */
.arg(dialogId, streamList.join(QLatin1Char('\n'))).toUtf8());
}
void AvaComm::releaseScript(const QString &dialogId, const QString &scriptName)
{
static const QString cmd = QStringLiteral(R"(releaseScript "%1" "%2")");
m_avSocket.send(cmd.arg(scriptName, dialogId).toUtf8());
}
void AvaComm::sendPreparedCommand(const QString &command)
{
m_avSocket.send(command.toUtf8());
}
void AvaComm::startScript(const QString &dialogId, const QString &scriptName)
{
static const QString cmd = QStringLiteral(R"(scriptControl "%1" "%2" start 1000)");
m_avSocket.send(cmd.arg(scriptName, dialogId).toUtf8());
}
void AvaComm::stopScript(const QString &dialogId, const QString &scriptName)
{
static const QString cmd = QStringLiteral(R"(scriptControl "%1" "%2" stop)");
m_avSocket.send(cmd.arg(scriptName, dialogId).toUtf8());
}
void AvaComm::pauseScript(const QString &dialogId, const QString &scriptName)
{
static const QString cmd = QStringLiteral(R"(scriptControl "%1" "%2" pause)");
m_avSocket.send(cmd.arg(scriptName, dialogId).toUtf8());
}
void AvaComm::setAbsPositionScript(const QString &dialogId, const QString &scriptName, AvaTimePoint pos)
{
static const QString cmd = QStringLiteral(R"(scriptControl "%1" "%2" posa %3)");
m_avSocket.send(cmd.arg(scriptName, dialogId, toString(pos)).toUtf8());
}
void AvaComm::onReadyRead(QTcpSocket *socket)
{
Q_ASSERT(socket);
while (socket->canReadLine())
parseResponse(socket->readLine().trimmed());
}
void AvaComm::parseResponse(const QByteArray &line)
{
static const QByteArray GREETER("RCP_AVA/1.0");
if (line.isEmpty())
return;
DEBUG_LOG(m_loggerIO) << "From A/V: '" << line << "'";
if (line == GREETER)
{
emit connected();
return;
}
const auto parts = AVSocket::parseLine(line);
if (m_pendingList)
{
parseListValue(parts);
return;
}
bool couldConvert = false;
Response response = static_cast<Response>(parts.at(0).toInt(&couldConvert));
Q_ASSERT(couldConvert);
switch (response)
{
case Response::Ok:
Q_ASSERT(parts.size() == 2);
emit ok();
break;
case Response::RtChoiceUnitState:
{
Q_ASSERT(parts.size() == 5);
AvaRtPos pos = parts.at(1).toInt(&couldConvert);
Q_ASSERT(couldConvert);
QString streamName = QString::fromUtf8(parts.at(2));
AvaRtButtonState buttonState = static_cast<AvaRtButtonState>(parts.at(3).toInt(&couldConvert));
Q_ASSERT(couldConvert);
emit rtChoiceUnitStateReceived(pos, streamName, buttonState);
break;
}
case Response::SumChannelState:
{
Q_ASSERT(parts.size() == 3);
AvaSumChannel sumChannel = static_cast<AvaSumChannel>(parts.at(1).toInt(&couldConvert));
Q_ASSERT(couldConvert);
emit sumChannelStateReceived(sumChannel);
break;
}
case Response::Volume:
{
Q_ASSERT(parts.size() == 4);
int device = parts.at(1).toInt(&couldConvert);
Q_ASSERT(couldConvert);
Q_ASSERT(device == 1);
AvaVolume db = parts.at(2).toInt(&couldConvert);
Q_ASSERT(couldConvert);
emit volumeReceived(db);
break;
}
case Response::VideoDialogState:
Q_ASSERT(parts.size() == 4);
break;
case Response::KeepAlive:
emit keepAliveReceived();
break;
case Response::StreamInfo:
{
Q_ASSERT(parts.size() == 7);
QString streamName = QString::fromUtf8(parts.at(1));
QString dialogId = QString::fromUtf8(parts.at(2));
AvaTimePoint start = createTimePoint(parts.at(3));
AvaTimePoint end = createTimePoint(parts.at(4));
AvaDuration duration = createDuration(parts.at(5));
emit streamInfoReceived(dialogId, streamName, AvaStreamInfo{start, end, duration});
break;
}
case Response::StreamPos:
{
Q_ASSERT(parts.size() == 5);
QString streamName = QString::fromUtf8(parts.at(1));
QString dialogId = QString::fromUtf8(parts.at(2));
AvaTimePoint pos = createTimePoint(parts.at(3));
emit streamPositionReceived(dialogId, streamName, pos);
break;
}
case Response::StreamSpeed:
{
Q_ASSERT(parts.size() == 5);
QString streamName = QString::fromUtf8(parts.at(1));
QString dialogId = QString::fromUtf8(parts.at(2));
AvaSpeed speed(parts.at(3).toInt(&couldConvert));
Q_ASSERT(couldConvert);
emit streamSpeedReceived(dialogId, streamName, speed);
break;
}
case Response::StreamState:
{
Q_ASSERT(parts.size() == 5);
QString streamName = QString::fromUtf8(parts.at(1));
QString dialogId = QString::fromUtf8(parts.at(2));
AvaStreamState streamState = static_cast<AvaStreamState>(parts.at(3).toInt(&couldConvert));
Q_ASSERT(couldConvert);
emit streamStateReceived(dialogId, streamName, streamState);
break;
}
case Response::StreamMode:
{
Q_ASSERT(parts.size() == 6);
QString streamName = QString::fromUtf8(parts.at(1));
QString dialogId = QString::fromUtf8(parts.at(2));
bool pint = parts.at(3).toInt(&couldConvert);
Q_ASSERT(couldConvert);
bool autoStop = parts.at(4).toInt(&couldConvert);
Q_ASSERT(couldConvert);
emit streamModeReceived(dialogId, streamName, pint, autoStop);
break;
}
case Response::StreamAudioMute:
Q_ASSERT(parts.size() == 5);
break;
case Response::StreamAudioState:
Q_ASSERT(parts.size() == 5);
break;
case Response::ElokaState:
Q_ASSERT(parts.size() == 5);
break;
case Response::ElokaList:
Q_ASSERT(parts.size() == 2);
Q_ASSERT(!m_pendingList);
m_pendingList = Response::ElokaList;
emit elokaListBeginReceived();
break;
case Response::StreamVolumeChanged:
Q_ASSERT(parts.size() == 5);
break;
case Response::RtVolumeUser:
{
Q_ASSERT(parts.size() == 4);
QString streamName = QString::fromUtf8(parts.at(1));
AvaVolume db = parts.at(2).toInt(&couldConvert);
Q_ASSERT(couldConvert);
emit rtVolumeUserReceived(streamName, db);
break;
}
case Response::InvalidNameList:
Q_ASSERT(parts.size() == 2);
Q_ASSERT(!m_pendingList);
m_pendingList = Response::InvalidNameList;
emit invalidNameListBeginReceived();
break;
case Response::StreamList:
Q_ASSERT(parts.size() == 2);
Q_ASSERT(!m_pendingList);
m_pendingList = Response::StreamList;
emit streamListBeginReceived();
break;
case Response::QueryElokaList:
Q_ASSERT(parts.size() == 2);
Q_ASSERT(!m_pendingList);
m_pendingList = Response::QueryElokaList;
emit queryElokaListBeginReceived();
break;
case Response::TimelineList:
{
Q_ASSERT(parts.size() == 3);
Q_ASSERT(!m_pendingList);
m_pendingList = Response::TimelineList;
QString streamName = QString::fromUtf8(parts.at(1));
emit timelineListBeginReceived(streamName);
break;
}
case Response::ScriptInfo:
{
Q_ASSERT(parts.size() == 7);
QString scriptName = QString::fromUtf8(parts.at(1));
QString dialogId = QString::fromUtf8(parts.at(2));
AvaTimePoint start = createTimePoint(parts.at(3));
AvaTimePoint end = createTimePoint(parts.at(4));
AvaDuration duration = createDuration(parts.at(5));
emit scriptInfoReceived(dialogId, scriptName, AvaStreamInfo{start, end, duration});
break;
}
case Response::ScriptState:
{
Q_ASSERT(parts.size() == 5);
QString scriptName = QString::fromUtf8(parts.at(1));
QString dialogId = QString::fromUtf8(parts.at(2));
AvaStreamState scriptState = static_cast<AvaStreamState>(parts.at(3).toInt(&couldConvert));
Q_ASSERT(couldConvert);
emit scriptStateReceived(dialogId, scriptName, scriptState);
break;
}
case Response::ScriptPos:
{
Q_ASSERT(parts.size() == 7);
QString scriptName = QString::fromUtf8(parts.at(1));
QString dialogId = QString::fromUtf8(parts.at(2));
QString streamName = QString::fromUtf8(parts.at(3));
AvaTimePoint pos = createTimePoint(parts.at(4));
AvaTimePoint relPos = createTimePoint(parts.at(5));
NOTICE_LOG(m_logger) << "Script pos received: " << scriptName << ", " << dialogId;
emit scriptPositionReceived(dialogId, scriptName, streamName, pos, relPos);
break;
}
default:
DEBUG_LOG(m_logger) << "Response not implemented: " << line;
}
}
void AvaComm::parseListValue(const QVector<QByteArray> &parts)
{
Q_ASSERT(m_pendingList);
bool couldConvert = false;
Response response = static_cast<Response>(parts.at(0).toInt(&couldConvert));
if (couldConvert && response == Response::EndOfParameterList)
{
m_pendingList.reset();
emit endOfListReceived();
return;
}
emit listValueReceived(parts);
}
} // namespace AudioVideoManagement