mirror of
https://github.com/OMGeeky/homecontrol.esp-sensors.git
synced 2025-12-26 17:02:29 +01:00
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.
This commit is contained in:
committed by
OMGeeky
parent
25f2d9e342
commit
19c0aec290
@@ -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:
|
||||
|
||||
@@ -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": "<your ssid>",
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user