diff --git a/lib/PsychicMqttClient/src/PsychicMqttClient.cpp b/lib/PsychicMqttClient/src/PsychicMqttClient.cpp index c91f3a50..2ae395c8 100644 --- a/lib/PsychicMqttClient/src/PsychicMqttClient.cpp +++ b/lib/PsychicMqttClient/src/PsychicMqttClient.cpp @@ -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 diff --git a/lib/PsychicMqttClient/src/PsychicMqttClient.h b/lib/PsychicMqttClient/src/PsychicMqttClient.h index 80961d22..c45cf272 100644 --- a/lib/PsychicMqttClient/src/PsychicMqttClient.h +++ b/lib/PsychicMqttClient/src/PsychicMqttClient.h @@ -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. * diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 76eccfe3..a007ce71 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -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