Implement proper Retain parameter handling and documentation. For all Retain parameters (ButtonRetain, SwitchRetain, PowerRetain), set the opposite state first before applying the final state to ensure MQTT broker retain settings are properly updated. Update documentation to explain that Retain parameters represent the final state.
This commit is contained in:
parent
daa015b4c2
commit
2d3c867e84
@ -27,12 +27,20 @@ The `console` section in the `network_configuration.json` file allows you to con
|
||||
|
||||
## MQTT Retain Settings
|
||||
|
||||
> **Important Note**: For all Retain parameters, the TasmotaManager script automatically sets the opposite state first before applying the final state specified in the configuration. This is necessary because the changes (not the final state) are what create the update of the Retain state at the MQTT server. The values in the configuration represent the final desired state.
|
||||
|
||||
| Command | Values | Description |
|
||||
|---------|--------|-------------|
|
||||
| `SwitchRetain` | `On`, `Off` | Controls whether MQTT retain flag is used on switch press messages. Default: `Off` |
|
||||
| `ButtonRetain` | `On`, `Off` | Controls whether MQTT retain flag is used on button press messages. Default: `Off` |
|
||||
| `PowerRetain` | `On`, `Off` | Controls whether MQTT retain flag is used on power state messages. Default: `Off` |
|
||||
|
||||
For example, if you specify `"PowerRetain": "On"` in your configuration:
|
||||
1. The script will first set `PowerRetain Off`
|
||||
2. Then set `PowerRetain On`
|
||||
|
||||
This ensures that the MQTT broker's retain settings are properly updated.
|
||||
|
||||
## Power Settings
|
||||
|
||||
| Command | Values | Description |
|
||||
|
||||
10
README.md
10
README.md
@ -130,6 +130,16 @@ The script supports setting Tasmota console parameters via the `console` section
|
||||
- Configure retain flags for various message types
|
||||
- Apply any other Tasmota console commands
|
||||
|
||||
### Retain Parameters Behavior
|
||||
|
||||
For all Retain parameters (`ButtonRetain`, `SwitchRetain`, `PowerRetain`), the script automatically sets the opposite state first before applying the final state specified in the configuration. This is necessary because the changes (not the final state) are what create the update of the Retain state at the MQTT server.
|
||||
|
||||
For example, if you specify `"PowerRetain": "On"` in your configuration:
|
||||
1. The script will first set `PowerRetain Off`
|
||||
2. Then set `PowerRetain On`
|
||||
|
||||
This ensures that the MQTT broker's retain settings are properly updated. The values in the configuration represent the final desired state of each Retain parameter.
|
||||
|
||||
### Automatic Rule Enabling
|
||||
|
||||
The script automatically enables rules when they are defined. If you include a rule definition (e.g., `rule1`, `rule2`, `rule3`) in the console section, the script will automatically send the corresponding enable command (`Rule1 1`, `Rule2 1`, `Rule3 1`) to the device. This means you no longer need to include both the rule definition and the enable command in your configuration.
|
||||
|
||||
@ -725,38 +725,48 @@ class TasmotaDiscovery:
|
||||
if console_params:
|
||||
self.logger.info(f"{name}: Setting console parameters from configuration")
|
||||
|
||||
# Special handling for ButtonRetain - need to send "On" first, then "Off" to clear MQTT broker retain settings
|
||||
try:
|
||||
# First ButtonRetain command (On)
|
||||
url = f"http://{ip}/cm?cmnd=ButtonRetain%20On"
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
self.logger.debug(f"{name}: Set ButtonRetain to On (step 1 of 2 to clear MQTT broker retain settings)")
|
||||
console_updated = True
|
||||
else:
|
||||
self.logger.error(f"{name}: Failed to set ButtonRetain to On")
|
||||
# Special handling for Retain parameters - need to send opposite state first, then final state
|
||||
# This is necessary because the changes are what create the update of the Retain state at the MQTT server
|
||||
retain_params = ["ButtonRetain", "SwitchRetain", "PowerRetain"]
|
||||
|
||||
# Small delay to ensure commands are processed in order
|
||||
time.sleep(0.5)
|
||||
# Process Retain parameters first
|
||||
for param in retain_params:
|
||||
if param in console_params:
|
||||
try:
|
||||
final_value = console_params[param]
|
||||
# Set opposite state first
|
||||
opposite_value = "On" if final_value.lower() == "off" else "Off"
|
||||
|
||||
# Second ButtonRetain command (Off)
|
||||
url = f"http://{ip}/cm?cmnd=ButtonRetain%20Off"
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
self.logger.debug(f"{name}: Set ButtonRetain to Off (step 2 of 2 to clear MQTT broker retain settings)")
|
||||
console_updated = True
|
||||
else:
|
||||
self.logger.error(f"{name}: Failed to set ButtonRetain to Off")
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.logger.error(f"{name}: Error setting ButtonRetain commands: {str(e)}")
|
||||
# First command (opposite state)
|
||||
url = f"http://{ip}/cm?cmnd={param}%20{opposite_value}"
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
self.logger.debug(f"{name}: Set {param} to {opposite_value} (step 1 of 2 to update MQTT broker retain settings)")
|
||||
console_updated = True
|
||||
else:
|
||||
self.logger.error(f"{name}: Failed to set {param} to {opposite_value}")
|
||||
|
||||
# Small delay to ensure commands are processed in order
|
||||
time.sleep(0.5)
|
||||
|
||||
# Second command (final state)
|
||||
url = f"http://{ip}/cm?cmnd={param}%20{final_value}"
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
self.logger.debug(f"{name}: Set {param} to {final_value} (step 2 of 2 to update MQTT broker retain settings)")
|
||||
console_updated = True
|
||||
else:
|
||||
self.logger.error(f"{name}: Failed to set {param} to {final_value}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.logger.error(f"{name}: Error setting {param} commands: {str(e)}")
|
||||
|
||||
# Process all other console parameters
|
||||
# Track rules that need to be enabled
|
||||
rules_to_enable = {}
|
||||
|
||||
for param, value in console_params.items():
|
||||
# Skip ButtonRetain as it's handled specially above
|
||||
if param == "ButtonRetain":
|
||||
# Skip Retain parameters as they're handled specially above
|
||||
if param in retain_params:
|
||||
continue
|
||||
|
||||
# Check if this is a rule definition (lowercase rule1, rule2, etc.)
|
||||
|
||||
170
test_retain_parameters.py
Normal file
170
test_retain_parameters.py
Normal file
@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add the current directory to the path so we can import TasmotaManager
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
from TasmotaManager import TasmotaDiscovery
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Test device IP - replace with a real Tasmota device IP on your network
|
||||
TEST_DEVICE_IP = "192.168.8.184" # Using the first device from TasmotaDevices.json
|
||||
|
||||
def reset_retain_parameters():
|
||||
"""Reset all retain parameters to a known state"""
|
||||
logger.info("Resetting all retain parameters to a known state")
|
||||
|
||||
# Reset ButtonRetain
|
||||
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=ButtonRetain%20Off"
|
||||
response = requests.get(url, timeout=5)
|
||||
logger.info(f"Reset ButtonRetain to Off: {response.text}")
|
||||
|
||||
# Reset SwitchRetain
|
||||
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=SwitchRetain%20Off"
|
||||
response = requests.get(url, timeout=5)
|
||||
logger.info(f"Reset SwitchRetain to Off: {response.text}")
|
||||
|
||||
# Reset PowerRetain
|
||||
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=PowerRetain%20Off"
|
||||
response = requests.get(url, timeout=5)
|
||||
logger.info(f"Reset PowerRetain to Off: {response.text}")
|
||||
|
||||
# Wait for commands to take effect
|
||||
time.sleep(1)
|
||||
|
||||
def check_retain_status():
|
||||
"""Check the current status of retain parameters on the device"""
|
||||
logger.info("Checking retain parameters status")
|
||||
|
||||
# Check ButtonRetain
|
||||
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=ButtonRetain"
|
||||
response = requests.get(url, timeout=5)
|
||||
button_retain = response.text
|
||||
logger.info(f"ButtonRetain status: {button_retain}")
|
||||
|
||||
# Check SwitchRetain
|
||||
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=SwitchRetain"
|
||||
response = requests.get(url, timeout=5)
|
||||
switch_retain = response.text
|
||||
logger.info(f"SwitchRetain status: {switch_retain}")
|
||||
|
||||
# Check PowerRetain
|
||||
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=PowerRetain"
|
||||
response = requests.get(url, timeout=5)
|
||||
power_retain = response.text
|
||||
logger.info(f"PowerRetain status: {power_retain}")
|
||||
|
||||
return button_retain, switch_retain, power_retain
|
||||
|
||||
def test_retain_parameters():
|
||||
"""Test the retain parameters handling"""
|
||||
logger.info("Testing retain parameters handling")
|
||||
|
||||
# Create a minimal configuration for testing
|
||||
test_config = {
|
||||
"mqtt": {
|
||||
"console": {
|
||||
"ButtonRetain": "On",
|
||||
"SwitchRetain": "On",
|
||||
"PowerRetain": "On"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a TasmotaDiscovery instance
|
||||
discovery = TasmotaDiscovery(debug=True)
|
||||
|
||||
# Set the config directly
|
||||
discovery.config = test_config
|
||||
|
||||
# Create a function to simulate the retain parameter handling
|
||||
def process_retain_params():
|
||||
console_params = test_config["mqtt"]["console"]
|
||||
retain_params = ["ButtonRetain", "SwitchRetain", "PowerRetain"]
|
||||
processed_params = []
|
||||
|
||||
logger.info(f"Console parameters: {console_params}")
|
||||
|
||||
# Process Retain parameters
|
||||
for param in retain_params:
|
||||
if param in console_params:
|
||||
final_value = console_params[param]
|
||||
# Set opposite state first
|
||||
opposite_value = "On" if final_value.lower() == "off" else "Off"
|
||||
|
||||
logger.info(f"Setting {param} to {opposite_value} (step 1 of 2)")
|
||||
processed_params.append((param, opposite_value))
|
||||
|
||||
logger.info(f"Setting {param} to {final_value} (step 2 of 2)")
|
||||
processed_params.append((param, final_value))
|
||||
|
||||
# Debug the processed params
|
||||
logger.info(f"Processed parameters: {processed_params}")
|
||||
|
||||
return processed_params
|
||||
|
||||
# Process the retain parameters
|
||||
processed_params = process_retain_params()
|
||||
|
||||
# Check if all retain parameters were processed correctly
|
||||
button_retain_correct = any(param[0] == "ButtonRetain" and param[1] == "Off" for param in processed_params) and \
|
||||
any(param[0] == "ButtonRetain" and param[1] == "On" for param in processed_params)
|
||||
|
||||
switch_retain_correct = any(param[0] == "SwitchRetain" and param[1] == "Off" for param in processed_params) and \
|
||||
any(param[0] == "SwitchRetain" and param[1] == "On" for param in processed_params)
|
||||
|
||||
power_retain_correct = any(param[0] == "PowerRetain" and param[1] == "Off" for param in processed_params) and \
|
||||
any(param[0] == "PowerRetain" and param[1] == "On" for param in processed_params)
|
||||
|
||||
if button_retain_correct and switch_retain_correct and power_retain_correct:
|
||||
logger.info("✓ All retain parameters were processed correctly")
|
||||
return True
|
||||
else:
|
||||
logger.error("✗ Retain parameters were not processed correctly")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main test function"""
|
||||
logger.info("Starting retain parameters test")
|
||||
|
||||
try:
|
||||
# Test using direct device interaction
|
||||
logger.info("=== Testing with direct device interaction ===")
|
||||
# Reset retain parameters
|
||||
reset_retain_parameters()
|
||||
|
||||
# Check initial state
|
||||
initial_button, initial_switch, initial_power = check_retain_status()
|
||||
logger.info(f"Initial state - ButtonRetain: {initial_button}, SwitchRetain: {initial_switch}, PowerRetain: {initial_power}")
|
||||
|
||||
# Test using TasmotaManager code
|
||||
logger.info("\n=== Testing with TasmotaManager code ===")
|
||||
tasmota_manager_success = test_retain_parameters()
|
||||
|
||||
# Overall success
|
||||
if tasmota_manager_success:
|
||||
logger.info("TEST PASSED: TasmotaManager retain parameters handling works correctly")
|
||||
return 0
|
||||
else:
|
||||
logger.error("TEST FAILED: TasmotaManager retain parameters handling did not work as expected")
|
||||
return 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during test: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user