From 466a9a24ef9a4f923f0e9537238a0ccd573866d2 Mon Sep 17 00:00:00 2001 From: Roland Winklmeier Date: Mon, 19 Jan 2015 18:14:33 +0100 Subject: [PATCH] refs #321 Methods to read/write aircraft config packets from/to network --- src/blackcore/network.h | 9 ++ src/blackcore/network_vatlib.cpp | 117 ++++++++++++++++++++++- src/blackcore/network_vatlib.h | 22 +++++ src/blackmisc/blackmiscfreefunctions.cpp | 39 ++++++++ src/blackmisc/blackmiscfreefunctions.h | 8 ++ 5 files changed, 194 insertions(+), 1 deletion(-) diff --git a/src/blackcore/network.h b/src/blackcore/network.h index 0bd6e2a08..f846b1c70 100644 --- a/src/blackcore/network.h +++ b/src/blackcore/network.h @@ -259,6 +259,12 @@ namespace BlackCore const QString &aircraftDesignator, const QString &combinedType, const QString &modelString) = 0; //! @} + //! Broadcast an incremental aircraft config + virtual void broadcastAircraftConfig(const QJsonObject &config) = 0; + + //! Query callsign for its current full aircraft config + virtual void sendAircraftConfigQuery(const BlackMisc::Aviation::CCallsign &callsign) = 0; + //! @} //////////////////////////////////////////////////////////////// //! \name ATC slots @@ -499,6 +505,9 @@ namespace BlackCore void fsipirCustomPacketReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &airlineDesignator, const QString &aircraftDesignator, const QString &combinedType, const QString &modelString); + //! We received a aircraft config packet + void aircraftConfigPacketReceived(const BlackMisc::Aviation::CCallsign &callsign, const QJsonObject &incremental, bool isFull); + //! @} //////////////////////////////////////////////////////////////// //! \name Weather signals diff --git a/src/blackcore/network_vatlib.cpp b/src/blackcore/network_vatlib.cpp index 9c47d52e2..b743338c1 100644 --- a/src/blackcore/network_vatlib.cpp +++ b/src/blackcore/network_vatlib.cpp @@ -10,6 +10,7 @@ #include "network_vatlib.h" #include "blackmisc/project.h" #include "blackmisc/logmessage.h" +#include #include #include @@ -37,7 +38,8 @@ namespace BlackCore : INetwork(parent), COwnAircraftProviderSupport(ownAircraft), m_loginMode(LoginNormal), m_status(vatStatusIdle), - m_fsdTextCodec(QTextCodec::codecForName("latin1")) + m_fsdTextCodec(QTextCodec::codecForName("latin1")), + m_tokenBucket(10, CTime(5, CTimeUnit::s()), 1) { connect(this, &CNetworkVatlib::terminate, this, &INetwork::terminateConnection, Qt::QueuedConnection); connect(this, &INetwork::customPacketReceived, this, &CNetworkVatlib::customPacketDispatcher); @@ -50,6 +52,9 @@ namespace BlackCore connect(&m_processingTimer, SIGNAL(timeout()), this, SLOT(process())); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(update())); m_processingTimer.start(c_processingIntervalMsec); + + this->connect(&this->m_scheduledConfigUpdate, &QTimer::timeout, this, &CNetworkVatlib::sendIncrementalAircraftConfig); + m_scheduledConfigUpdate.setSingleShot(true); } void CNetworkVatlib::initializeSession() @@ -90,6 +95,7 @@ namespace BlackCore Vat_SetAircraftInfoRequestHandler(m_net.data(), onPilotInfoRequestReceived, this); Vat_SetAircraftInfoHandler(m_net.data(), onPilotInfoReceived, this); Vat_SetCustomPilotPacketHandler(m_net.data(), onCustomPacketReceived, this); + Vat_SetAircraftConfigHandler(m_net.data(), onAircraftConfigReceived, this); } CNetworkVatlib::~CNetworkVatlib() @@ -100,6 +106,7 @@ namespace BlackCore void CNetworkVatlib::process() { if (!m_net) { return; } + sendIncrementalAircraftConfig(); Vat_ExecuteNetworkTasks(m_net.data()); } @@ -228,6 +235,25 @@ namespace BlackCore return qstrList; } + QString CNetworkVatlib::convertToUnicodeEscaped(const QString &str) + { + QString escaped; + for (const auto &ch : str) + { + ushort code = ch.unicode(); + if (code < 0x80) + { + escaped += ch; + } + else + { + escaped += "\\u"; + escaped += QString::number(code, 16).rightJustified(4, '0'); + } + } + return escaped; + } + /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ /********************************** INetwork slots ************************************/ /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ @@ -454,6 +480,19 @@ namespace BlackCore Vat_SendInformation(m_net.data(), vatInfoQueryTypeName, toFSD(callsign), toFSD(m_server.getUser().getRealName())); } + void CNetworkVatlib::replyToConfigQuery(const CCallsign &callsign) + { + QJsonObject currentConfig = ownAircraft().getParts().toJson(); + // Fixme: Use QJsonObject with std::initializer_list once 5.4 is baseline + currentConfig.insert("is_full_data", true); + QJsonObject packet; + packet.insert("config", currentConfig); + QJsonDocument doc(packet); + QString data { doc.toJson(QJsonDocument::Compact) }; + data = convertToUnicodeEscaped(data); + Vat_SendAircraftConfig(m_net.data(), toFSD(callsign), toFSD(data)); + } + void CNetworkVatlib::sendIcaoCodesQuery(const BlackMisc::Aviation::CCallsign &callsign) { Q_ASSERT_X(isConnected(), "CNetworkVatlib", "Can't send to server when disconnected"); @@ -470,6 +509,32 @@ namespace BlackCore Vat_SendModernPlaneInfo(m_net.data(), toFSD(callsign), &aircraftInfo); } + void CNetworkVatlib::sendIncrementalAircraftConfig() + { + if (!isConnected()) return; + + CAircraftParts currentParts = ownAircraft().getParts(); + + // If it hasn't changed, return + if (m_sentAircraftConfig == currentParts) return; + + if (!m_tokenBucket.tryConsume()) + { + // If timer is not yet active, start it + if (!m_scheduledConfigUpdate.isActive()) m_scheduledConfigUpdate.start(1000); + return; + } + + // Method could have been triggered by another change in aircraft config + // so a previous update might still be scheduled. Stop it. + if (m_scheduledConfigUpdate.isActive()) m_scheduledConfigUpdate.stop(); + QJsonObject previousConfig = m_sentAircraftConfig.toJson(); + QJsonObject currentConfig = currentParts.toJson(); + QJsonObject incrementalConfig = getIncrementalObject(previousConfig, currentConfig); + broadcastAircraftConfig(incrementalConfig); + m_sentAircraftConfig = currentParts; + } + void CNetworkVatlib::sendPing(const BlackMisc::Aviation::CCallsign &callsign) { Q_ASSERT_X(isConnected(), "CNetworkVatlib", "Can't send to server when disconnected"); @@ -502,6 +567,24 @@ namespace BlackCore sendCustomPacket(callsign, "FSIPIR", data); } + void CNetworkVatlib::broadcastAircraftConfig(const QJsonObject &config) + { + // Fixme: Use QJsonObject with std::initializer_list once 5.4 is baseline + QJsonObject packet; + packet.insert("config", config); + QJsonDocument doc(packet); + QString data { doc.toJson(QJsonDocument::Compact) }; + data = convertToUnicodeEscaped(data); + Vat_SendAircraftConfigBroadcast(m_net.data(), toFSD(data)); + } + + void CNetworkVatlib::sendAircraftConfigQuery(const CCallsign &callsign) + { + QJsonDocument doc(JsonPackets::aircraftConfigRequest()); + QString data { doc.toJson(QJsonDocument::Compact) }; + Vat_SendAircraftConfig(m_net.data(), toFSD(callsign), toFSD(data)); + } + /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ /********************************** shimlib callbacks ************************************/ /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ @@ -597,6 +680,30 @@ namespace BlackCore emit cbvar_cast(cbvar)->aircraftPositionUpdate(callsign, situation, transponder); } + void CNetworkVatlib::onAircraftConfigReceived(VatSessionID, const char *callsign, const char *aircraftConfig, void *cbvar) + { + QByteArray json = cbvar_cast(cbvar)->fromFSD(aircraftConfig).toUtf8(); + QJsonParseError parserError; + QJsonDocument doc = QJsonDocument::fromJson(json, &parserError); + + if (parserError.error != QJsonParseError::NoError) + CLogMessage(static_cast(nullptr)).warning("Failed to parse aircraft config packet: %1") << parserError.errorString(); + + QJsonObject packet = doc.object(); + + if (packet == JsonPackets::aircraftConfigRequest() ) + { + cbvar_cast(cbvar)->replyToConfigQuery(cbvar_cast(cbvar)->fromFSD(callsign)); + return; + } + + QJsonObject config = doc.object().value("config").toObject(); + if (config.empty()) return; + + bool isFull = config.take("is_full_data").toBool(false); + emit cbvar_cast(cbvar)->aircraftConfigPacketReceived(cbvar_cast(cbvar)->fromFSD(callsign), config, isFull); + } + void CNetworkVatlib::onInterimPilotPositionUpdate(VatSessionID, const char * /** callsign **/, const VatPilotPosition * /** position **/, void * /** cbvar **/) { //TODO @@ -839,4 +946,12 @@ namespace BlackCore CLogMessage(static_cast(nullptr)).error(message); } + QJsonObject CNetworkVatlib::JsonPackets::aircraftConfigRequest() + { + // Fixme: Use static QJsonObject with std::initializer_list once 5.4 is baseline + QJsonObject request; + request.insert("request", "full"); + return request; + } + } // namespace diff --git a/src/blackcore/network_vatlib.h b/src/blackcore/network_vatlib.h index 587a09678..de2f80a7a 100644 --- a/src/blackcore/network_vatlib.h +++ b/src/blackcore/network_vatlib.h @@ -14,6 +14,7 @@ #include "network.h" #include "blackmisc/simulation/simdirectaccessownaircraft.h" +#include "token_bucket.h" #include #include #include @@ -61,6 +62,13 @@ namespace BlackCore virtual void sendFsipirCustomPacket(const BlackMisc::Aviation::CCallsign &callsign, const QString &airlineDesignator, const QString &aircraftDesignator, const QString &combinedType, const QString &modelString) override; + //! \copydoc INetwork::broadcastAircraftConfig + virtual void broadcastAircraftConfig(const QJsonObject &config) override; + + //! \copydoc INetwork::sendAircraftConfigQuery + virtual void sendAircraftConfigQuery(const BlackMisc::Aviation::CCallsign &callsign) override; + + // Text message slots virtual void sendTextMessages(const BlackMisc::Network::CTextMessageList &messages) override; @@ -83,7 +91,9 @@ namespace BlackCore private slots: void replyToFrequencyQuery(const BlackMisc::Aviation::CCallsign &callsign); void replyToNameQuery(const BlackMisc::Aviation::CCallsign &callsign); + void replyToConfigQuery(const BlackMisc::Aviation::CCallsign &callsign); void sendAircraftInfo(const BlackMisc::Aviation::CCallsign &callsign); + void sendIncrementalAircraftConfig(); private: //shimlib callbacks static void onConnectionStatusChanged(VatSessionID, VatConnectionStatus oldStatus, VatConnectionStatus newStatus, void *cbvar); @@ -110,6 +120,7 @@ namespace BlackCore static void onPilotInfoRequestReceived(VatSessionID, const char *callsign, void *cbvar); static void onPilotInfoReceived(VatSessionID, const char *callsign, const VatAircraftInfo *aircraftInfo, void *cbvar); static void onPilotPositionUpdate(VatSessionID, const char *callsign, const VatPilotPosition *position, void *cbvar); + static void onAircraftConfigReceived(VatSessionID, const char *callsign, const char *aircraftConfig, void *cbvar); static void onCustomPacketReceived(VatSessionID, const char *callsign, const char *packetId, const char **data, int dataSize, void *cbvar); private: @@ -122,8 +133,14 @@ namespace BlackCore void initializeSession(); void changeConnectionStatus(VatConnectionStatus newStatus); bool isDisconnected() const { return m_status != vatStatusConnecting && m_status != vatStatusConnected; } + QString convertToUnicodeEscaped(const QString &str); static void networkErrorHandler(const char *message); + struct JsonPackets + { + static QJsonObject aircraftConfigRequest(); + }; + private slots: void process(); void update(); @@ -154,6 +171,11 @@ namespace BlackCore static int const c_updateIntervalMsec = 5000; static int const c_logoffTimeoutSec = 5; QTextCodec *m_fsdTextCodec; + + BlackMisc::Aviation::CAircraftParts m_sentAircraftConfig; + QTimer m_scheduledConfigUpdate; + CTokenBucket m_tokenBucket; + }; } //namespace BlackCore diff --git a/src/blackmisc/blackmiscfreefunctions.cpp b/src/blackmisc/blackmiscfreefunctions.cpp index 88fbe4cc8..98e83848e 100644 --- a/src/blackmisc/blackmiscfreefunctions.cpp +++ b/src/blackmisc/blackmiscfreefunctions.cpp @@ -372,3 +372,42 @@ bool BlackMisc::Audio::startWindowsMixer() QStringList parameterlist; return QProcess::startDetached("SndVol.exe", parameterlist); } + +QJsonObject BlackMisc::getIncrementalObject(const QJsonObject &previousObject, const QJsonObject ¤tObject) +{ + QJsonObject incrementalObject = currentObject; + for (const auto &key : previousObject.keys()) + { + if (previousObject.value(key).isObject()) + { + auto child = getIncrementalObject(previousObject.value(key).toObject(), currentObject.value(key).toObject()); + if (child.isEmpty()) incrementalObject.remove(key); + else incrementalObject.insert(key, child); + } + else + { + if (currentObject.value(key) == previousObject.value(key)) + incrementalObject.remove(key); + } + } + return incrementalObject; +} + +QJsonObject BlackMisc::applyIncrementalObject(const QJsonObject &previousObject, const QJsonObject &incrementalObject) +{ + QJsonObject currentObject = previousObject; + for(const auto &key : incrementalObject.keys()) + { + // If it is not an object, just insert the value + if (!incrementalObject.value(key).isObject()) + { + currentObject.insert(key,incrementalObject.value(key)); + } + else + { + auto child = applyIncrementalObject(currentObject.value(key).toObject(), incrementalObject.value(key).toObject()); + currentObject.insert(key, child); + } + } + return currentObject; +} diff --git a/src/blackmisc/blackmiscfreefunctions.h b/src/blackmisc/blackmiscfreefunctions.h index 06fc93a94..3e6f0748f 100644 --- a/src/blackmisc/blackmiscfreefunctions.h +++ b/src/blackmisc/blackmiscfreefunctions.h @@ -184,6 +184,14 @@ namespace BlackMisc return std::unique_ptr(new T(std::forward(args)...)); } + //! Creates an incremental json object from two existing objects + QJsonObject getIncrementalObject(const QJsonObject &previousObject, const QJsonObject ¤tObject); + + //! Merges an incremental json object into an existing one + QJsonObject applyIncrementalObject(const QJsonObject &previousObject, const QJsonObject &incrementalObject); + + + } // BlackMisc #endif // guard