Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion src/MqttManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include <QFile>
#include <QTime>
#include <QTimer>

/* ************************************************************************** */

Expand Down Expand Up @@ -92,6 +93,10 @@ bool MqttManager::connect()
{
//qDebug() << "MqttManager::connect()";

// We intend to stay connected from now on; an unexpected drop should
// arm the auto-reconnect backoff (see scheduleReconnect()).
m_reconnectWanted = true;

SettingsManager *sm = SettingsManager::getInstance();
m_mqttclient->setHostname(sm->getMqttHost());
m_mqttclient->setPort(sm->getMqttPort());
Expand Down Expand Up @@ -139,6 +144,14 @@ void MqttManager::disconnect()
{
#if defined(ENABLE_MQTT)

// Explicit disconnect (user toggled MQTT off, or a forced reconnect is
// about to re-dial): stop wanting a connection and cancel any pending
// backoff so the timer doesn't re-dial behind the user's back. Reset the
// interval so the next session starts from the base delay.
m_reconnectWanted = false;
m_reconnectInterval = kReconnectBaseMs;
if (m_reconnectTimer) m_reconnectTimer->stop();

if (m_mqttclient)
{
//qDebug() << "MqttManager::disconnect()";
Expand Down Expand Up @@ -183,6 +196,63 @@ void MqttManager::reconnect()
#endif
}

void MqttManager::scheduleReconnect()
{
#if defined(ENABLE_MQTT)

// Only re-dial if we still want to be connected and MQTT is enabled.
if (!m_reconnectWanted) return;

SettingsManager *sm = SettingsManager::getInstance();
if (!sm || !sm->getMQTT()) return;

if (!m_reconnectTimer)
{
m_reconnectTimer = new QTimer(this);
m_reconnectTimer->setSingleShot(true);
QObject::connect(m_reconnectTimer, &QTimer::timeout,
this, &MqttManager::reconnectTimerFired);
}

// A burst of Disconnected state-changes must not stack timers or grow the
// backoff multiple times — one armed attempt at a time.
if (m_reconnectTimer->isActive()) return;

m_reconnectTimer->start(m_reconnectInterval);
logLine(QString("auto-reconnect in %1s").arg(m_reconnectInterval / 1000));

// Exponential backoff for the *next* attempt, capped. Reset to the base
// interval happens on a successful connect (brokerConnected) or an
// explicit disconnect().
m_reconnectInterval = qMin(m_reconnectInterval * 2, kReconnectMaxMs);

#endif
}

void MqttManager::reconnectTimerFired()
{
#if defined(ENABLE_MQTT)

if (!m_reconnectWanted) return;

// Something already (re)connected us in the meantime — nothing to do.
if (m_mqttclient &&
(m_mqttclient->state() == QMqttClient::Connected ||
m_mqttclient->state() == QMqttClient::Connecting))
{
return;
}

logLine("auto-reconnect: attempting");
connect();

// If this attempt fails, the client transitions back to Disconnected,
// updateStateChange() fires scheduleReconnect() again, and the next
// (longer) backoff interval is armed.

#endif
}

/* ************************************************************************** */
/* ************************************************************************** */

Expand Down Expand Up @@ -292,7 +362,16 @@ void MqttManager::updateStateChange()
//qDebug() << "MqttManager::updateStateChange()" << m_mqttclient->state();
Q_EMIT statusChanged();

if (m_mqttclient->state() == QMqttClient::Disconnected) logLine("status: disconnected");
if (m_mqttclient->state() == QMqttClient::Disconnected)
{
logLine("status: disconnected");

// Both unexpected drops (broker restart) and failed reconnect
// attempts (broker still down) land here as Disconnected. Arm the
// backoff; scheduleReconnect() no-ops unless we still want to be
// connected, so an explicit disconnect() won't re-dial.
scheduleReconnect();
}
else if (m_mqttclient->state() == QMqttClient::Connecting) logLine("status: connecting");
else if (m_mqttclient->state() == QMqttClient::Connected) logLine("status: connected");
}
Expand All @@ -308,6 +387,11 @@ void MqttManager::brokerConnected()

if (m_mqttclient)
{
// Connection re-established: cancel any pending auto-reconnect and
// reset the backoff so the next drop starts from the base interval.
m_reconnectInterval = kReconnectBaseMs;
if (m_reconnectTimer) m_reconnectTimer->stop();

// Clear drop bookkeeping: the banner/notification hides automatically
// once droppedSinceDisconnect == 0 and disconnectedSince is invalid.
if (m_droppedSinceDisconnect != 0)
Expand Down
19 changes: 19 additions & 0 deletions src/MqttManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include <QtMqtt/QtMqtt>
#endif

class QTimer;

/* ************************************************************************** */

class Broker: public QObject
Expand Down Expand Up @@ -116,6 +118,18 @@ class MqttManager: public QObject
qint64 m_droppedSinceDisconnect = 0;
QDateTime m_disconnectedSince;

// Auto-reconnect with exponential backoff. QtMqtt does NOT reconnect on
// its own, so an unexpected drop (e.g. broker restart) would otherwise
// strand the app on the disconnect banner until the user taps retry.
// m_reconnectWanted tracks intent: true while we want to stay connected,
// false after an explicit disconnect()/MQTT-off, so the timer never
// fights the user.
static constexpr int kReconnectBaseMs = 2000;
static constexpr int kReconnectMaxMs = 60000;
QTimer *m_reconnectTimer = nullptr;
int m_reconnectInterval = kReconnectBaseMs;
bool m_reconnectWanted = false;

QList <Broker *> m_brokersAvailable;
QVariant getBrokersAvailable() const { return QVariant::fromValue(m_brokersAvailable); }

Expand All @@ -136,13 +150,18 @@ private slots:
void updateStateChange();
void brokerConnected();
void brokerDisconnected();
void reconnectTimerFired();

private:
// Prepend a timestamped line to m_mqttLog (newest on top) and emit
// logChanged so the QML broker panel re-renders. Called from the
// state-change / error / TLS handlers.
void logLine(const QString &msg);

// Arm the single-shot backoff timer for the next reconnect attempt.
// No-op unless m_reconnectWanted (and MQTT is still enabled in settings).
void scheduleReconnect();

public:
static MqttManager *getInstance();

Expand Down
Loading