fix(mqtt): raise QoS1 retransmit timeout to stop duplicate /status storms
esp-mqtt's default message_retransmit_timeout is 1000 ms: any unacked QoS 1 PUBLISH is resent (byte-identical, DUP=1) every second until the PUBACK arrives or the outbox entry expires (30 s). Status messages are the only QoS 1 publishes; on a congested or recovering uplink where broker acks take several seconds, each 5-minute /status was delivered ~6 times, ~1 s apart, as exact copies (same timestamp and stats). Downstream observers flagged excessive_packet_copies and at least one broker treats it as abuse. Expose message_retransmit_timeout via PsychicMqttClient and set it to 15 s in optimizeMqttClientConfig: one retry still fits inside the 30 s outbox expiry, preserving at-least-once delivery while capping duplicates at one. /packets paths are QoS 0 and were never affected.
This commit is contained in:
3 files changed
+31
No files matched your search
@@ -59,6 +59,17 @@ PsychicMqttClient &PsychicMqttClient::setAutoReconnect(bool reconnect)
|
||||
return *this;
|
||||
}
|
||||
|
||||
PsychicMqttClient &PsychicMqttClient::setMessageRetransmitTimeout(int timeoutMs)
|
||||
{
|
||||
#if ESP_IDF_VERSION_MAJOR == 5
|
||||
_mqtt_cfg.session.message_retransmit_timeout = timeoutMs;
|
||||
#else
|
||||
_mqtt_cfg.message_retransmit_timeout = timeoutMs;
|
||||
#endif
|
||||
_config_dirty = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PsychicMqttClient &PsychicMqttClient::setClientId(const char *clientId)
|
||||
{
|
||||
#if ESP_IDF_VERSION_MAJOR == 5
|
||||
|
||||
@@ -126,6 +126,17 @@ public:
|
||||
*/
|
||||
PsychicMqttClient &setAutoReconnect(bool reconnect = true);
|
||||
|
||||
/**
|
||||
* @brief Sets the retransmit timeout for unacknowledged QoS 1/2 messages.
|
||||
* esp-mqtt resends an unacked PUBLISH (DUP flag set) every time this
|
||||
* timeout elapses, so a value shorter than the broker's ack latency
|
||||
* produces byte-identical duplicates on the wire.
|
||||
*
|
||||
* @param timeoutMs Retransmit timeout in milliseconds. esp-mqtt's default is 1000.
|
||||
* @return A reference to the PsychicMqttClient instance.
|
||||
*/
|
||||
PsychicMqttClient &setMessageRetransmitTimeout(int timeoutMs);
|
||||
|
||||
/**
|
||||
* @brief Sets the client ID for the MQTT connection.
|
||||
*
|
||||
|
||||
@@ -3323,6 +3323,15 @@ void MQTTBridge::optimizeMqttClientConfig(PsychicMqttClient* client, bool needs_
|
||||
client->setKeepAlive(75);
|
||||
#endif
|
||||
|
||||
// QoS 1 retransmit timeout for unacked PUBLISHes (status messages). esp-mqtt's
|
||||
// 1000 ms default resends a byte-identical duplicate every second whenever the
|
||||
// broker's PUBACK takes >1s — on a congested or recovering uplink this floods
|
||||
// subscribers with exact copies of one /status message (observed 6 copies ~1s
|
||||
// apart after an ISP outage; brokers may drop the session as spam). 15s allows
|
||||
// one retry before the outbox entry expires (esp-mqtt outbox expiry is 30s),
|
||||
// preserving at-least-once delivery while capping duplicates at one.
|
||||
client->setMessageRetransmitTimeout(15000);
|
||||
|
||||
// Buffer sizing: 896 is the minimum safe size for JWT clients (CONNECT + 768-byte JWT).
|
||||
// On PSRAM boards, use a uniform size to reduce fragmentation from mixed allocations.
|
||||
// On non-PSRAM boards, use smaller buffers for non-JWT slots to reduce heap usage and
|
||||
|
||||
Reference in new issue
Block a user