diff --git a/src/blackcore/afv/clients/afvclient.cpp b/src/blackcore/afv/clients/afvclient.cpp index 46b6f372d..a3e4dfc8c 100644 --- a/src/blackcore/afv/clients/afvclient.cpp +++ b/src/blackcore/afv/clients/afvclient.cpp @@ -180,6 +180,7 @@ namespace BlackCore this->retryConnectTo(cid, password, callsign, client, QStringLiteral("No connection afer 20secs")); }); } + // thread safe connect { QMutexLocker lock(&m_mutexConnection); @@ -235,6 +236,11 @@ namespace BlackCore QMutexLocker lock(&m_mutexConnection); m_connection->disconnectFrom(); } + + m_heartBeatFailures = 0; + m_retryConnectAttempt = 0; + m_fsdConnectMismatches = 0; + emit connectionStatusChanged(Disconnected); if (stop) { this->stopAudio(); } @@ -840,8 +846,8 @@ namespace BlackCore if (loopback && transmit) { IAudioDto audioData; - audioData.audio = QByteArray(args.audio.data(), args.audio.size()); - audioData.callsign = QStringLiteral("loopback"); + audioData.audio = QByteArray(args.audio.data(), args.audio.size()); + audioData.callsign = QStringLiteral("loopback"); audioData.lastPacket = false; audioData.sequenceCounter = 0; @@ -1023,6 +1029,47 @@ namespace BlackCore // for AFV sample client this->updateTransceivers(); } + + // connection check + this->checkServerHeartbeat(); + } + + void CAfvClient::checkServerHeartbeat() + { + if (!this->isStarted()) { return; } + if (!this->isConnected()) { return; } + + if (this->isVoiceServerAlive()) + { + m_heartBeatFailures = 0; + return; + } + + // Heartbeat failure + // it can happen that after connect we see an initial timeout + const int failures = ++m_heartBeatFailures; + if (failures < 2) { return; } + + QString un, pw, cs, client; + { + QMutexLocker lock(&m_mutexConnection); + un = m_connection->getUserName(); + pw = m_connection->getPassword(); + cs = m_connection->getCallsign(); + client = m_connection->getClient(); + } + if (un.isEmpty() || pw.isEmpty()) { return; } + + // make sure we are disconnected + if (this->isConnected()) { this->disconnectFrom(false); } + + QPointer myself(this); + QTimer::singleShot(5 * 1000, this, [ = ] + { + if (!myself) { return; } + const QString reason = QStringLiteral("Heartbeat failed %1 times").arg(failures); + this->retryConnectTo(un, pw, cs, client, reason); + }); } void CAfvClient::onSettingsChanged() @@ -1263,6 +1310,21 @@ namespace BlackCore return roundedFrequencyHz; } + bool CAfvClient::isVoiceServerAlive() const + { + QMutexLocker lock(&m_mutexConnection); + return m_connection && m_connection->isVoiceServerAlive(); + } + + const QString &CAfvClient::getVoiceServerUrl() const + { + QMutexLocker lock(&m_mutexConnection); + + static const QString e; + if (!m_connection) { return e; } + return m_connection->getVoiceServerUrl(); + } + bool CAfvClient::fuzzyMatchCallsign(const QString &callsign, const QString &compareTo) const { if (callsign.isEmpty() || compareTo.isEmpty()) { return false; } // empty callsigns should NOT match diff --git a/src/blackcore/afv/clients/afvclient.h b/src/blackcore/afv/clients/afvclient.h index 91617a3a2..bca674b42 100644 --- a/src/blackcore/afv/clients/afvclient.h +++ b/src/blackcore/afv/clients/afvclient.h @@ -133,7 +133,7 @@ namespace BlackCore //! \threadsafe bool isComUnitIntegrated() const { return m_integratedComUnit; } - /* + /* NOT used //! The device's volume 0..1 @{ double getDeviceInputVolume() const; bool setDeviceInputVolume(double volume); @@ -180,7 +180,7 @@ namespace BlackCore //! Get transceivers //! \threadsafe //! @{ - QVector getTransceivers() const; + QVector getTransceivers() const; QVector getTransmittingTransceivers() const; QSet getEnabledTransceivers() const; //! @} @@ -352,6 +352,14 @@ namespace BlackCore //! \threadsafe quint32 getAliasFrequencyHz(quint32 frequencyHz) const; + //! Voice server alive + //! \threadsafe + bool isVoiceServerAlive() const; + + //! Get voice server URL + //! \threadsafe + const QString &getVoiceServerUrl() const; + bool fuzzyMatchCallsign(const QString &callsign, const QString &compareTo) const; void getPrefixSuffix(const QString &callsign, QString &prefix, QString &suffix) const; @@ -385,10 +393,11 @@ namespace BlackCore static const QVector &allTransceiverIds() { static const QVector transceiverIds{0, 1}; return transceiverIds; } std::atomic_int m_fsdConnectMismatches { 0 }; //!< FSD no longer connected? - std::atomic_int m_retryConnectAttempt { 0 }; //!< Try to connect the n-th time - std::atomic_bool m_isStarted { false }; - std::atomic_bool m_loopbackOn { false }; - std::atomic_bool m_enableAliased { true }; + std::atomic_int m_retryConnectAttempt { 0 }; //!< try to connect the n-th time + std::atomic_int m_heartBeatFailures { 0 }; //!< voice server heartbeat failures + std::atomic_bool m_isStarted { false }; + std::atomic_bool m_loopbackOn { false }; + std::atomic_bool m_enableAliased { true }; std::atomic_bool m_winCoInitialized { false }; //!< Windows only CoInitializeEx std::atomic_bool m_integratedComUnit { false }; //!< is COM unit sychronized, integrated? @@ -405,6 +414,7 @@ namespace BlackCore Audio::InputVolumeStreamArgs m_inputVolumeStream; Audio::OutputVolumeStreamArgs m_outputVolumeStream; + void checkServerHeartbeat(); void deferredInit(); void initTransceivers(); void connectWithContexts(); diff --git a/src/blackcore/afv/connection/apiserverconnection.h b/src/blackcore/afv/connection/apiserverconnection.h index f1c4f442c..7a47836b1 100644 --- a/src/blackcore/afv/connection/apiserverconnection.h +++ b/src/blackcore/afv/connection/apiserverconnection.h @@ -80,6 +80,16 @@ namespace BlackCore //! Set the URL bool setUrl(const QString &url); + //! Get the URL + const QString &getUrl() const { return m_addressUrl; } + + //! User data @{ + const QString &getUserName() const { return m_username; } + const QString &getPassword() const { return m_password; } + const QString &getClient() const { return m_client; } + const QUuid &getNetworkVersion() const { return m_networkVersion; } + //! @} + private: //! Post to resource template diff --git a/src/blackcore/afv/connection/clientconnection.cpp b/src/blackcore/afv/connection/clientconnection.cpp index de16b2c5c..9c418e816 100644 --- a/src/blackcore/afv/connection/clientconnection.cpp +++ b/src/blackcore/afv/connection/clientconnection.cpp @@ -65,6 +65,7 @@ namespace BlackCore m_connection.setTokens(m_apiServerConnection->addCallsign(callsign)); m_connection.setTsAuthenticatedToNow(); m_connection.createCryptoChannels(); + m_connection.setTsHeartbeatToNow(); this->connectToVoiceServer(); // taskServerConnectionCheck.Start(); @@ -125,6 +126,13 @@ namespace BlackCore return m_apiServerConnection->setUrl(url); } + const QString &CClientConnection::getVoiceServerUrl() const + { + static const QString e; + if (!m_apiServerConnection) { return e; } + return m_apiServerConnection->getUrl(); + } + void CClientConnection::connectToVoiceServer() { const QHostAddress localAddress(QHostAddress::AnyIPv4); diff --git a/src/blackcore/afv/connection/clientconnection.h b/src/blackcore/afv/connection/clientconnection.h index 6f715efa6..40dd6e34a 100644 --- a/src/blackcore/afv/connection/clientconnection.h +++ b/src/blackcore/afv/connection/clientconnection.h @@ -52,9 +52,11 @@ namespace BlackCore //! Disconnect void disconnectFrom(const QString &reason = {}); - //! Is connected + //! Is connected? bool isConnected() const { return m_connection.isConnected(); } - //! @} + + //! Is alive? + bool isVoiceServerAlive() const { return m_connection.isVoiceServerAlive(); } //! Receiving audio? @{ void setReceiveAudio(bool value) { m_connection.setReceiveAudio(value); } @@ -89,9 +91,20 @@ namespace BlackCore //! Update the voice server URL bool updateVoiceServerUrl(const QString &url); + //! Get the voice server URL + const QString &getVoiceServerUrl() const; + //! Authenticated since when qint64 secondsSinceAuthentication() const { return m_connection.secondsSinceAuthentication(); } + //! User data @{ + const QString &getUserName() const { return m_connection.getUserName(); } + const QString &getCallsign() const { return m_connection.getCallsign(); } + const QString &getPassword() const { static const QString e; return m_apiServerConnection ? m_apiServerConnection->getPassword() : e; } + const QString &getClient() const { static const QString e; return m_apiServerConnection ? m_apiServerConnection->getClient() : e; } + const QUuid &getNetworkVersion() const { return m_networkVersion; } + //! @} + signals: //! Audio has been received void audioReceived(const AudioRxOnTransceiversDto &dto); diff --git a/src/blackcore/afv/connection/clientconnectiondata.cpp b/src/blackcore/afv/connection/clientconnectiondata.cpp index 0fce66e8e..2dc1ffd57 100644 --- a/src/blackcore/afv/connection/clientconnectiondata.cpp +++ b/src/blackcore/afv/connection/clientconnectiondata.cpp @@ -32,14 +32,16 @@ namespace BlackCore bool CClientConnectionData::isVoiceServerAlive() const { - return m_lastVoiceServerHeartbeatAckUtc.isValid() && - m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > ServerTimeoutSecs; + if (!m_lastVoiceServerHeartbeatAckUtc.isValid()) { return false; } + const qint64 d = qAbs(m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc())); + return d < ServerTimeoutSecs; } bool CClientConnectionData::isDataServerAlive() const { - return m_lastDataServerHeartbeatAckUtc.isValid() && - m_lastDataServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > ServerTimeoutSecs; + if (!m_lastDataServerHeartbeatAckUtc.isValid()) { return false; } + const qint64 d = qAbs(m_lastDataServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc())); + return d < ServerTimeoutSecs; } void CClientConnectionData::createCryptoChannels() diff --git a/src/blackcore/afv/connection/clientconnectiondata.h b/src/blackcore/afv/connection/clientconnectiondata.h index e963b5513..47638c93f 100644 --- a/src/blackcore/afv/connection/clientconnectiondata.h +++ b/src/blackcore/afv/connection/clientconnectiondata.h @@ -42,7 +42,7 @@ namespace BlackCore //! Servers alive @{ bool isVoiceServerAlive() const; - bool isDataServerAlive() const; + bool isDataServerAlive() const; //! @} //! Is connected? @{