From cd7673f78a20f3c7a4bc3c0468eea8575f4ed47f Mon Sep 17 00:00:00 2001 From: "jetbrains-junie[bot]" Date: Sat, 7 Jun 2025 10:50:08 +0000 Subject: [PATCH 1/6] feat: implement daily reconnection strategy with persistence The reconnection strategy was implemented to ensure at least four connection attempts per day while conserving battery life through state persistence in a configuration file. --- docs/mqtt_implementation.md | 60 +++++++++++++++++++- src/esp_sensors/config.py | 9 +++ src/esp_sensors/mqtt.py | 110 ++++++++++++++++++++++++++++++++++-- src/main.py | 8 +++ 4 files changed, 182 insertions(+), 5 deletions(-) diff --git a/docs/mqtt_implementation.md b/docs/mqtt_implementation.md index 0f68b6f..8cb5a9b 100644 --- a/docs/mqtt_implementation.md +++ b/docs/mqtt_implementation.md @@ -20,6 +20,8 @@ The implementation provides the following features: - Socket-based communication with MQTT brokers - Quality of Service (QoS) support (levels 0 and 1) - Ping/keepalive mechanism to maintain connections +- Smart reconnection strategy with exponential backoff for unreachable brokers +- Battery-efficient operation when the broker is unavailable - Simulation mode for development on non-ESP hardware ## Classes @@ -166,7 +168,16 @@ mqtt_config = { "topic_data_prefix": "/homecontrol/device/data", # Prefix for data topics "topic_config": "/homecontrol/device/config", # Topic for configuration "load_config_from_mqtt": True, # Whether to load config from MQTT - "config_wait_time": 1.0 # Wait time for config updates in seconds + "config_wait_time": 1.0, # Wait time for config updates in seconds + "reconnect": { # Reconnection strategy configuration + "enabled": True, # Enable/disable reconnection strategy + "max_attempts": 3, # Maximum consecutive connection attempts + "attempt_count": 0, # Current number of consecutive failed attempts + "last_attempt_time": 0, # Timestamp of the last connection attempt + "backoff_factor": 2, # Exponential backoff factor + "min_interval": 3600, # Minimum interval between reconnection attempts (1 hour) + "max_interval": 21600, # Maximum interval between reconnection attempts (6 hours) + } } ``` @@ -205,6 +216,53 @@ When running on non-ESP hardware, the implementation automatically switches to s This is useful for development and testing without actual hardware. +## Reconnection Strategy + +The MQTT implementation includes a smart reconnection strategy designed to balance connectivity needs with battery conservation, especially when the MQTT broker is unreachable. This is particularly important for ESP32 devices that use deep sleep to conserve power. + +### How It Works + +1. **Initial Connection Attempts**: The system will make up to `max_attempts` consecutive connection attempts (default: 3) without any delay between them. + +2. **Exponential Backoff**: After reaching the maximum number of consecutive attempts, the system implements an exponential backoff strategy: + - The wait time between attempts increases exponentially based on the `backoff_factor` (default: 2) + - The formula is: `min_interval * (backoff_factor ^ (attempt_count - max_attempts))` + - This wait time is capped at `max_interval` to ensure reconnection attempts happen regularly + +3. **Guaranteed Reconnection Attempts**: With default settings (min_interval: 1 hour, max_interval: 6 hours), the system will attempt to reconnect at least 4 times per day even if the broker remains unreachable. + +4. **State Persistence**: The reconnection state (attempt count, last attempt time) is persisted across deep sleep cycles by saving it to the configuration file. + +5. **Automatic Reset**: When a connection is successful, the attempt counter is reset to 0, returning to normal operation. + +### Configuration + +The reconnection strategy can be configured through the `reconnect` section of the MQTT configuration: + +```python +mqtt_config = { + # ... other MQTT settings ... + "reconnect": { + "enabled": True, # Enable/disable reconnection strategy + "max_attempts": 3, # Maximum consecutive connection attempts + "attempt_count": 0, # Current number of consecutive failed attempts + "last_attempt_time": 0, # Timestamp of the last connection attempt + "backoff_factor": 2, # Exponential backoff factor + "min_interval": 3600, # Minimum interval (1 hour in seconds) + "max_interval": 21600, # Maximum interval (6 hours in seconds) + } +} +``` + +### Battery Efficiency + +This strategy significantly reduces battery consumption when the MQTT broker is unreachable for extended periods: + +- Instead of attempting to connect on every wake cycle (which would drain the battery quickly) +- The device will skip connection attempts based on the exponential backoff algorithm +- This allows the device to spend more time in deep sleep, conserving power +- While still ensuring regular reconnection attempts to restore functionality when the broker becomes available again + ## Integration with Sensor Data The `publish_sensor_data` function provides a convenient way to publish sensor data to MQTT topics: diff --git a/src/esp_sensors/config.py b/src/esp_sensors/config.py index f3eaecf..7a198d0 100644 --- a/src/esp_sensors/config.py +++ b/src/esp_sensors/config.py @@ -53,6 +53,15 @@ DEFAULT_CONFIG = { "topic_data_prefix": "/homecontrol/{device_id}/data", "ssl": False, "keepalive": 60, + "reconnect": { + "enabled": True, + "max_attempts": 3, + "attempt_count": 0, + "last_attempt_time": 0, + "backoff_factor": 2, + "min_interval": 3600, # 1 hour in seconds + "max_interval": 21600, # 6 hours in seconds + }, }, "network": { "ssid": "", diff --git a/src/esp_sensors/mqtt.py b/src/esp_sensors/mqtt.py index 8777b7f..d12bfc4 100644 --- a/src/esp_sensors/mqtt.py +++ b/src/esp_sensors/mqtt.py @@ -9,6 +9,7 @@ This module uses the MQTTClient class from mqtt_client.py for the core MQTT impl import time import json +import os from .mqtt_client import ( MQTTClient, MQTTException, @@ -278,6 +279,23 @@ def setup_mqtt(mqtt_config: dict) -> ESP32MQTTClient | MQTTClient | None: ssl = mqtt_config.get("ssl", False) use_esp32_client = mqtt_config.get("use_esp32_client", True) + # Get reconnection configuration + reconnect_config = mqtt_config.get("reconnect", {}) + reconnect_enabled = reconnect_config.get("enabled", True) + + # Check if we should attempt to connect based on reconnection strategy + if reconnect_enabled and not should_attempt_connection(reconnect_config): + print("Skipping MQTT connection attempt based on reconnection strategy") + # Return a client instance but don't connect + if use_esp32_client: + return ESP32MQTTClient( + client_id, broker, port, username, password, keepalive, ssl + ) + else: + return MQTTClient( + client_id, broker, port, username, password, keepalive, ssl + ) + print(f"Setting up MQTT client: {client_id} -> {broker}:{port}") if use_esp32_client: @@ -289,8 +307,14 @@ def setup_mqtt(mqtt_config: dict) -> ESP32MQTTClient | MQTTClient | None: # Try to connect if client.connect(): print("MQTT connected successfully using ESP32MQTTClient") + # Reset reconnection attempt counter on successful connection + if reconnect_enabled: + update_reconnection_state(reconnect_config, True) else: print("Failed to connect using ESP32MQTTClient") + # Update reconnection attempt counter + if reconnect_enabled: + update_reconnection_state(reconnect_config, False) return client # print("Failed to connect using ESP32MQTTClient, falling back to basic MQTTClient") @@ -303,10 +327,20 @@ def setup_mqtt(mqtt_config: dict) -> ESP32MQTTClient | MQTTClient | None: client_id, broker, port, username, password, keepalive, ssl ) - # Try to connect - client.connect() - print("MQTT connected successfully using basic MQTTClient") - return client + try: + # Try to connect + client.connect() + print("MQTT connected successfully using basic MQTTClient") + # Reset reconnection attempt counter on successful connection + if reconnect_enabled: + update_reconnection_state(reconnect_config, True) + return client + except Exception as e: + print(f"Failed to connect using basic MQTTClient: {e}") + # Update reconnection attempt counter + if reconnect_enabled: + update_reconnection_state(reconnect_config, False) + return client except Exception as e: print(f"Failed to connect to MQTT broker: {e}") @@ -410,6 +444,74 @@ def subscribe_to_config( return False +def should_attempt_connection(reconnect_config: dict) -> bool: + """ + Determine if a connection attempt should be made based on the reconnection strategy. + + Args: + reconnect_config: Reconnection configuration dictionary + + Returns: + True if a connection attempt should be made, False otherwise + """ + # If reconnection is disabled, always attempt to connect + if not reconnect_config.get("enabled", True): + return True + + # Get reconnection parameters + attempt_count = reconnect_config.get("attempt_count", 0) + max_attempts = reconnect_config.get("max_attempts", 3) + last_attempt_time = reconnect_config.get("last_attempt_time", 0) + backoff_factor = reconnect_config.get("backoff_factor", 2) + min_interval = reconnect_config.get("min_interval", 3600) # 1 hour default + max_interval = reconnect_config.get("max_interval", 21600) # 6 hours default + + # If we haven't reached max attempts, always try to connect + if attempt_count < max_attempts: + return True + + # Calculate the backoff interval based on attempt count + # Use exponential backoff with a maximum interval + interval = min(min_interval * (backoff_factor ** (attempt_count - max_attempts)), max_interval) + + # Check if enough time has passed since the last attempt + current_time = time.time() + time_since_last_attempt = current_time - last_attempt_time + + # If we've waited long enough, allow another attempt + if time_since_last_attempt >= interval: + print(f"Allowing reconnection attempt after {time_since_last_attempt:.1f}s (interval: {interval:.1f}s)") + return True + else: + print(f"Skipping reconnection attempt, next attempt in {interval - time_since_last_attempt:.1f}s") + return False + +def update_reconnection_state(reconnect_config: dict, success: bool) -> None: + """ + Update the reconnection state based on the connection attempt result. + + Args: + reconnect_config: Reconnection configuration dictionary + success: Whether the connection attempt was successful + """ + current_time = time.time() + + if success: + # Reset attempt counter on successful connection + reconnect_config["attempt_count"] = 0 + print("Connection successful, reset reconnection attempt counter") + else: + # Increment attempt counter on failed connection + attempt_count = reconnect_config.get("attempt_count", 0) + 1 + reconnect_config["attempt_count"] = attempt_count + print(f"Connection failed, reconnection attempt count: {attempt_count}") + + # Update last attempt time + reconnect_config["last_attempt_time"] = current_time + + # Update the configuration in the parent dictionary + # This will be saved to the config file in the main application + def check_config_update( client: ESP32MQTTClient | MQTTClient | None, mqtt_config: dict, current_config: dict ) -> dict: diff --git a/src/main.py b/src/main.py index 23f7890..45d316e 100644 --- a/src/main.py +++ b/src/main.py @@ -195,6 +195,14 @@ def main(): print("MQTT client disconnected") except Exception as e: print(f"Error disconnecting MQTT client: {e}") + + # Save the updated reconnection state to the configuration + reconnect_config = config.mqtt_config.get("reconnect", {}) + if reconnect_config: + # Save the updated configuration + updated_config = config.config.copy() + updated_config["mqtt"]["reconnect"] = reconnect_config + config.save_config(updated_config) else: print("MQTT is disabled, not publishing data") From 3c72b4f39d822455d8c06429dbb72a28c5e7a183 Mon Sep 17 00:00:00 2001 From: OMGeeky <39029799+OMGeeky@users.noreply.github.com> Date: Sat, 7 Jun 2025 14:05:05 +0200 Subject: [PATCH 2/6] Update src/esp_sensors/mqtt.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/esp_sensors/mqtt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/esp_sensors/mqtt.py b/src/esp_sensors/mqtt.py index d12bfc4..98b27e7 100644 --- a/src/esp_sensors/mqtt.py +++ b/src/esp_sensors/mqtt.py @@ -9,7 +9,6 @@ This module uses the MQTTClient class from mqtt_client.py for the core MQTT impl import time import json -import os from .mqtt_client import ( MQTTClient, MQTTException, From 2ad51bad06ff42452cc82b650a6d293546c2bd6d Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Sat, 7 Jun 2025 14:15:19 +0200 Subject: [PATCH 3/6] refactor: simplify MQTT client connection logic and remove unused configuration --- src/esp_sensors/mqtt.py | 69 ++++++++++++----------------------------- src/main.py | 7 +---- 2 files changed, 20 insertions(+), 56 deletions(-) diff --git a/src/esp_sensors/mqtt.py b/src/esp_sensors/mqtt.py index 98b27e7..94fe871 100644 --- a/src/esp_sensors/mqtt.py +++ b/src/esp_sensors/mqtt.py @@ -276,7 +276,6 @@ def setup_mqtt(mqtt_config: dict) -> ESP32MQTTClient | MQTTClient | None: password = mqtt_config.get("password", "") keepalive = mqtt_config.get("keepalive", 60) ssl = mqtt_config.get("ssl", False) - use_esp32_client = mqtt_config.get("use_esp32_client", True) # Get reconnection configuration reconnect_config = mqtt_config.get("reconnect", {}) @@ -286,60 +285,30 @@ def setup_mqtt(mqtt_config: dict) -> ESP32MQTTClient | MQTTClient | None: if reconnect_enabled and not should_attempt_connection(reconnect_config): print("Skipping MQTT connection attempt based on reconnection strategy") # Return a client instance but don't connect - if use_esp32_client: - return ESP32MQTTClient( - client_id, broker, port, username, password, keepalive, ssl - ) - else: - return MQTTClient( - client_id, broker, port, username, password, keepalive, ssl - ) + return ESP32MQTTClient( + client_id, broker, port, username, password, keepalive, ssl + ) print(f"Setting up MQTT client: {client_id} -> {broker}:{port}") - if use_esp32_client: - # Use the new ESP32MQTTClient - client = ESP32MQTTClient( - client_id, broker, port, username, password, keepalive, ssl - ) + # Use the new ESP32MQTTClient + client = ESP32MQTTClient( + client_id, broker, port, username, password, keepalive, ssl + ) - # Try to connect - if client.connect(): - print("MQTT connected successfully using ESP32MQTTClient") - # Reset reconnection attempt counter on successful connection - if reconnect_enabled: - update_reconnection_state(reconnect_config, True) - else: - print("Failed to connect using ESP32MQTTClient") - # Update reconnection attempt counter - if reconnect_enabled: - update_reconnection_state(reconnect_config, False) + # Try to connect + if client.connect(): + print("MQTT connected successfully using ESP32MQTTClient") + # Reset reconnection attempt counter on successful connection + if reconnect_enabled: + update_reconnection_state(reconnect_config, True) + else: + print("Failed to connect using ESP32MQTTClient") + # Update reconnection attempt counter + if reconnect_enabled: + update_reconnection_state(reconnect_config, False) - return client - # print("Failed to connect using ESP32MQTTClient, falling back to basic MQTTClient") - # # Fall back to basic client - # use_esp32_client = False - - if not use_esp32_client: - # Use the basic MQTTClient for backward compatibility - client = MQTTClient( - client_id, broker, port, username, password, keepalive, ssl - ) - - try: - # Try to connect - client.connect() - print("MQTT connected successfully using basic MQTTClient") - # Reset reconnection attempt counter on successful connection - if reconnect_enabled: - update_reconnection_state(reconnect_config, True) - return client - except Exception as e: - print(f"Failed to connect using basic MQTTClient: {e}") - # Update reconnection attempt counter - if reconnect_enabled: - update_reconnection_state(reconnect_config, False) - return client + return client except Exception as e: print(f"Failed to connect to MQTT broker: {e}") diff --git a/src/main.py b/src/main.py index 45d316e..e5810c0 100644 --- a/src/main.py +++ b/src/main.py @@ -197,12 +197,7 @@ def main(): print(f"Error disconnecting MQTT client: {e}") # Save the updated reconnection state to the configuration - reconnect_config = config.mqtt_config.get("reconnect", {}) - if reconnect_config: - # Save the updated configuration - updated_config = config.config.copy() - updated_config["mqtt"]["reconnect"] = reconnect_config - config.save_config(updated_config) + config.save_config() else: print("MQTT is disabled, not publishing data") From 19c0aec290e00fc512a93766ada2d04993f0536d Mon Sep 17 00:00:00 2001 From: "jetbrains-junie[bot]" Date: Sat, 7 Jun 2025 10:50:08 +0000 Subject: [PATCH 4/6] feat: implement daily reconnection strategy with persistence The reconnection strategy was implemented to ensure at least four connection attempts per day while conserving battery life through state persistence in a configuration file. --- docs/mqtt_implementation.md | 60 +++++++++++++++++++++++++- src/esp_sensors/config.py | 9 ++++ src/esp_sensors/mqtt.py | 84 +++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) diff --git a/docs/mqtt_implementation.md b/docs/mqtt_implementation.md index 0f68b6f..8cb5a9b 100644 --- a/docs/mqtt_implementation.md +++ b/docs/mqtt_implementation.md @@ -20,6 +20,8 @@ The implementation provides the following features: - Socket-based communication with MQTT brokers - Quality of Service (QoS) support (levels 0 and 1) - Ping/keepalive mechanism to maintain connections +- Smart reconnection strategy with exponential backoff for unreachable brokers +- Battery-efficient operation when the broker is unavailable - Simulation mode for development on non-ESP hardware ## Classes @@ -166,7 +168,16 @@ mqtt_config = { "topic_data_prefix": "/homecontrol/device/data", # Prefix for data topics "topic_config": "/homecontrol/device/config", # Topic for configuration "load_config_from_mqtt": True, # Whether to load config from MQTT - "config_wait_time": 1.0 # Wait time for config updates in seconds + "config_wait_time": 1.0, # Wait time for config updates in seconds + "reconnect": { # Reconnection strategy configuration + "enabled": True, # Enable/disable reconnection strategy + "max_attempts": 3, # Maximum consecutive connection attempts + "attempt_count": 0, # Current number of consecutive failed attempts + "last_attempt_time": 0, # Timestamp of the last connection attempt + "backoff_factor": 2, # Exponential backoff factor + "min_interval": 3600, # Minimum interval between reconnection attempts (1 hour) + "max_interval": 21600, # Maximum interval between reconnection attempts (6 hours) + } } ``` @@ -205,6 +216,53 @@ When running on non-ESP hardware, the implementation automatically switches to s This is useful for development and testing without actual hardware. +## Reconnection Strategy + +The MQTT implementation includes a smart reconnection strategy designed to balance connectivity needs with battery conservation, especially when the MQTT broker is unreachable. This is particularly important for ESP32 devices that use deep sleep to conserve power. + +### How It Works + +1. **Initial Connection Attempts**: The system will make up to `max_attempts` consecutive connection attempts (default: 3) without any delay between them. + +2. **Exponential Backoff**: After reaching the maximum number of consecutive attempts, the system implements an exponential backoff strategy: + - The wait time between attempts increases exponentially based on the `backoff_factor` (default: 2) + - The formula is: `min_interval * (backoff_factor ^ (attempt_count - max_attempts))` + - This wait time is capped at `max_interval` to ensure reconnection attempts happen regularly + +3. **Guaranteed Reconnection Attempts**: With default settings (min_interval: 1 hour, max_interval: 6 hours), the system will attempt to reconnect at least 4 times per day even if the broker remains unreachable. + +4. **State Persistence**: The reconnection state (attempt count, last attempt time) is persisted across deep sleep cycles by saving it to the configuration file. + +5. **Automatic Reset**: When a connection is successful, the attempt counter is reset to 0, returning to normal operation. + +### Configuration + +The reconnection strategy can be configured through the `reconnect` section of the MQTT configuration: + +```python +mqtt_config = { + # ... other MQTT settings ... + "reconnect": { + "enabled": True, # Enable/disable reconnection strategy + "max_attempts": 3, # Maximum consecutive connection attempts + "attempt_count": 0, # Current number of consecutive failed attempts + "last_attempt_time": 0, # Timestamp of the last connection attempt + "backoff_factor": 2, # Exponential backoff factor + "min_interval": 3600, # Minimum interval (1 hour in seconds) + "max_interval": 21600, # Maximum interval (6 hours in seconds) + } +} +``` + +### Battery Efficiency + +This strategy significantly reduces battery consumption when the MQTT broker is unreachable for extended periods: + +- Instead of attempting to connect on every wake cycle (which would drain the battery quickly) +- The device will skip connection attempts based on the exponential backoff algorithm +- This allows the device to spend more time in deep sleep, conserving power +- While still ensuring regular reconnection attempts to restore functionality when the broker becomes available again + ## Integration with Sensor Data The `publish_sensor_data` function provides a convenient way to publish sensor data to MQTT topics: diff --git a/src/esp_sensors/config.py b/src/esp_sensors/config.py index 91a08c5..7404e8d 100644 --- a/src/esp_sensors/config.py +++ b/src/esp_sensors/config.py @@ -53,6 +53,15 @@ DEFAULT_CONFIG = { "topic_data_prefix": "/homecontrol/{device_id}/data", "ssl": False, "keepalive": 60, + "reconnect": { + "enabled": True, + "max_attempts": 3, + "attempt_count": 0, + "last_attempt_time": 0, + "backoff_factor": 2, + "min_interval": 3600, # 1 hour in seconds + "max_interval": 21600, # 6 hours in seconds + }, }, "network": { "ssid": "", diff --git a/src/esp_sensors/mqtt.py b/src/esp_sensors/mqtt.py index 54ab81b..58ad0ef 100644 --- a/src/esp_sensors/mqtt.py +++ b/src/esp_sensors/mqtt.py @@ -260,6 +260,11 @@ def setup_mqtt(mqtt_config: dict) -> ESP32MQTTClient | MQTTClient | None: keepalive = mqtt_config.get("keepalive", 60) ssl = mqtt_config.get("ssl", False) + # Get reconnection configuration + reconnect_config = mqtt_config.get("reconnect", {}) + reconnect_enabled = reconnect_config.get("enabled", True) + + print(f"Setting up MQTT client: {client_id} -> {broker}:{port}") # Use the new ESP32MQTTClient @@ -267,11 +272,22 @@ def setup_mqtt(mqtt_config: dict) -> ESP32MQTTClient | MQTTClient | None: client_id, broker, port, username, password, keepalive, ssl ) + # Check if we should attempt to connect based on reconnection strategy + if reconnect_enabled and not should_attempt_connection(reconnect_config): + print("Skipping MQTT connection attempt based on reconnection strategy") + return client + # Try to connect if client.connect(): print("MQTT connected successfully using ESP32MQTTClient") + # Reset reconnection attempt counter on successful connection + if reconnect_enabled: + update_reconnection_state(reconnect_config, True) else: print("Failed to connect using ESP32MQTTClient") + # Update reconnection attempt counter + if reconnect_enabled: + update_reconnection_state(reconnect_config, False) return client @@ -370,6 +386,74 @@ def subscribe_to_config( return False +def should_attempt_connection(reconnect_config: dict) -> bool: + """ + Determine if a connection attempt should be made based on the reconnection strategy. + + Args: + reconnect_config: Reconnection configuration dictionary + + Returns: + True if a connection attempt should be made, False otherwise + """ + # If reconnection is disabled, always attempt to connect + if not reconnect_config.get("enabled", True): + return True + + # Get reconnection parameters + attempt_count = reconnect_config.get("attempt_count", 0) + max_attempts = reconnect_config.get("max_attempts", 3) + last_attempt_time = reconnect_config.get("last_attempt_time", 0) + backoff_factor = reconnect_config.get("backoff_factor", 2) + min_interval = reconnect_config.get("min_interval", 3600) # 1 hour default + max_interval = reconnect_config.get("max_interval", 21600) # 6 hours default + + # If we haven't reached max attempts, always try to connect + if attempt_count < max_attempts: + return True + + # Calculate the backoff interval based on attempt count + # Use exponential backoff with a maximum interval + interval = min(min_interval * (backoff_factor ** (attempt_count - max_attempts)), max_interval) + + # Check if enough time has passed since the last attempt + current_time = time.time() + time_since_last_attempt = current_time - last_attempt_time + + # If we've waited long enough, allow another attempt + if time_since_last_attempt >= interval: + print(f"Allowing reconnection attempt after {time_since_last_attempt:.1f}s (interval: {interval:.1f}s)") + return True + else: + print(f"Skipping reconnection attempt, next attempt in {interval - time_since_last_attempt:.1f}s") + return False + +def update_reconnection_state(reconnect_config: dict, success: bool) -> None: + """ + Update the reconnection state based on the connection attempt result. + + Args: + reconnect_config: Reconnection configuration dictionary + success: Whether the connection attempt was successful + """ + current_time = time.time() + + if success: + # Reset attempt counter on successful connection + reconnect_config["attempt_count"] = 0 + print("Connection successful, reset reconnection attempt counter") + else: + # Increment attempt counter on failed connection + attempt_count = reconnect_config.get("attempt_count", 0) + 1 + reconnect_config["attempt_count"] = attempt_count + print(f"Connection failed, reconnection attempt count: {attempt_count}") + + # Update last attempt time + reconnect_config["last_attempt_time"] = current_time + + # Update the configuration in the parent dictionary + # This will be saved to the config file in the main application + def check_config_update( client: ESP32MQTTClient | MQTTClient | None, mqtt_config: dict, current_config: dict ) -> dict: From 4c5e664c77e6192690814185e68c9fc95f11b15b Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Sat, 7 Jun 2025 14:15:19 +0200 Subject: [PATCH 5/6] refactor: simplify MQTT client connection logic and remove unused configuration --- src/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.py b/src/main.py index 0cdd51b..6f5a400 100644 --- a/src/main.py +++ b/src/main.py @@ -207,6 +207,9 @@ def main(): else: print("MQTT client not connected, skipping publish") display.set_status("MQTT not connected") + + # Save the updated reconnection state to the configuration + config.save_config() else: print("MQTT is disabled, not publishing data") From dda437e1ebdf2785c9c202761355c51bdce5529b Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Sat, 7 Jun 2025 14:27:34 +0200 Subject: [PATCH 6/6] formatting --- src/esp_sensors/mqtt.py | 19 +++++++++----- src/main.py | 58 +++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/esp_sensors/mqtt.py b/src/esp_sensors/mqtt.py index 58ad0ef..4901037 100644 --- a/src/esp_sensors/mqtt.py +++ b/src/esp_sensors/mqtt.py @@ -264,7 +264,6 @@ def setup_mqtt(mqtt_config: dict) -> ESP32MQTTClient | MQTTClient | None: reconnect_config = mqtt_config.get("reconnect", {}) reconnect_enabled = reconnect_config.get("enabled", True) - print(f"Setting up MQTT client: {client_id} -> {broker}:{port}") # Use the new ESP32MQTTClient @@ -337,9 +336,7 @@ def publish_sensor_data( # Publish the data and check the result publish_success = client.publish(data_topic, data_payload) if publish_success: - print( - f"Published sensor data to MQTT: '{data_topic}'" - ) + print(f"Published sensor data to MQTT: '{data_topic}'") return True else: print("Failed to publish sensor data to MQTT") @@ -414,7 +411,9 @@ def should_attempt_connection(reconnect_config: dict) -> bool: # Calculate the backoff interval based on attempt count # Use exponential backoff with a maximum interval - interval = min(min_interval * (backoff_factor ** (attempt_count - max_attempts)), max_interval) + interval = min( + min_interval * (backoff_factor ** (attempt_count - max_attempts)), max_interval + ) # Check if enough time has passed since the last attempt current_time = time.time() @@ -422,12 +421,17 @@ def should_attempt_connection(reconnect_config: dict) -> bool: # If we've waited long enough, allow another attempt if time_since_last_attempt >= interval: - print(f"Allowing reconnection attempt after {time_since_last_attempt:.1f}s (interval: {interval:.1f}s)") + print( + f"Allowing reconnection attempt after {time_since_last_attempt:.1f}s (interval: {interval:.1f}s)" + ) return True else: - print(f"Skipping reconnection attempt, next attempt in {interval - time_since_last_attempt:.1f}s") + print( + f"Skipping reconnection attempt, next attempt in {interval - time_since_last_attempt:.1f}s" + ) return False + def update_reconnection_state(reconnect_config: dict, success: bool) -> None: """ Update the reconnection state based on the connection attempt result. @@ -454,6 +458,7 @@ def update_reconnection_state(reconnect_config: dict, success: bool) -> None: # Update the configuration in the parent dictionary # This will be saved to the config file in the main application + def check_config_update( client: ESP32MQTTClient | MQTTClient | None, mqtt_config: dict, current_config: dict ) -> dict: diff --git a/src/main.py b/src/main.py index 9c8452b..129c6c0 100644 --- a/src/main.py +++ b/src/main.py @@ -149,36 +149,38 @@ def main(): mqtt_client = None if mqtt_client and mqtt_client.connected and load_config_from_mqtt: - display.set_status("Checking MQTT config...") - print("Checking for configuration updates before publishing...") - updated_config = check_config_update( - mqtt_client, config.mqtt_config, config.config - ) + display.set_status("Checking MQTT config...") + print("Checking for configuration updates before publishing...") + updated_config = check_config_update( + mqtt_client, config.mqtt_config, config.config + ) - # If we got an updated configuration with a newer version, save it - if ( - updated_config != config.config - and updated_config.get("version", 0) > config.current_version - ): - display.set_status("Updating config...") - print( - f"Found newer configuration (version {updated_config.get('version')}), updating..." - ) - config.save_config(updated_config) - publish_success = mqtt_client.publish( - get_data_topic(config.mqtt_config) + "/config_status", - "Configuration updated", - ) - if not publish_success: - print("Failed to publish configuration update status") - # Note: We continue with the current config for this cycle - # The updated config will be used after the next reboot - else: - print( - f"No configuration updates found or no newer version available (local version: {config.current_version})" - ) + # If we got an updated configuration with a newer version, save it + if ( + updated_config != config.config + and updated_config.get("version", 0) > config.current_version + ): + display.set_status("Updating config...") + print( + f"Found newer configuration (version {updated_config.get('version')}), updating..." + ) + config.save_config(updated_config) + publish_success = mqtt_client.publish( + get_data_topic(config.mqtt_config) + "/config_status", + "Configuration updated", + ) + if not publish_success: + print("Failed to publish configuration update status") + # Note: We continue with the current config for this cycle + # The updated config will be used after the next reboot + else: + print( + f"No configuration updates found or no newer version available (local version: {config.current_version})" + ) else: - print("MQTT client not connected or not configured to load config from broker, skipping config check") + print( + "MQTT client not connected or not configured to load config from broker, skipping config check" + ) display.set_status("MQTT not loading config") if mqtt_client and mqtt_client.connected: