From cced5a76cc68d7c06808b4928247f0a840c2b110 Mon Sep 17 00:00:00 2001 From: Mike Geppert Date: Wed, 6 Aug 2025 04:30:31 -0500 Subject: [PATCH] Fix FullTopic parameter to prevent extra equals sign at beginning of value --- TasmotaManager.py | 8 +- fulltopic_equals_fix_summary.md | 65 ++++++++++++ test_fulltopic_approaches.py | 180 ++++++++++++++++++++++++++++++++ test_fulltopic_equals_issue.py | 154 +++++++++++++++++++++++++++ 4 files changed, 403 insertions(+), 4 deletions(-) create mode 100644 fulltopic_equals_fix_summary.md create mode 100755 test_fulltopic_approaches.py create mode 100755 test_fulltopic_equals_issue.py diff --git a/TasmotaManager.py b/TasmotaManager.py index af1e6b0..132570b 100644 --- a/TasmotaManager.py +++ b/TasmotaManager.py @@ -539,9 +539,9 @@ class TasmotaDiscovery: } for setting, value in mqtt_fields.items(): - # For FullTopic, we need to avoid adding a space (%20) between the command and value + # For FullTopic, we need to avoid adding a space (%20) or equals sign between the command and value if setting == "FullTopic": - url = f"http://{ip}/cm?cmnd={setting}={value}" + url = f"http://{ip}/cm?cmnd={setting}{value}" else: url = f"http://{ip}/cm?cmnd={setting}%20{value}" response = requests.get(url, timeout=5) @@ -1022,9 +1022,9 @@ class TasmotaDiscovery: # Apply changes if needed for setting, value in changes_needed: try: - # For FullTopic, we need to avoid adding a space (%20) between the command and value + # For FullTopic, we need to avoid adding a space (%20) or equals sign between the command and value if setting == "FullTopic": - url = f"http://{ip}/cm?cmnd={setting}={value}" + url = f"http://{ip}/cm?cmnd={setting}{value}" else: url = f"http://{ip}/cm?cmnd={setting}%20{value}" response = requests.get(url, timeout=5) diff --git a/fulltopic_equals_fix_summary.md b/fulltopic_equals_fix_summary.md new file mode 100644 index 0000000..35ca368 --- /dev/null +++ b/fulltopic_equals_fix_summary.md @@ -0,0 +1,65 @@ +# FullTopic Equals Sign Fix Summary + +## Issue Description +When setting the MQTT FullTopic parameter, an extra equals sign ('=') was being added to the beginning of the value. For example, instead of setting the FullTopic to `%prefix%/%topic%/`, it was being set to `=%prefix%/%topic%/`. + +## Root Cause +The issue was related to how the Tasmota device interprets the command when an equals sign is used as a separator between the command and the value. When using the format: + +``` +http://{ip}/cm?cmnd=FullTopic={value} +``` + +The Tasmota device was interpreting this as a command to set the FullTopic to `={value}` rather than just `{value}`. + +## Investigation +A test script was created to reproduce the issue and test different approaches for setting the FullTopic parameter. The script tested several methods: + +1. Current approach (setting=value): `http://{ip}/cm?cmnd=FullTopic={full_topic}` +2. URL encoded value: `http://{ip}/cm?cmnd=FullTopic={urllib.parse.quote(full_topic)}` +3. Using space (%20) instead of equals: `http://{ip}/cm?cmnd=FullTopic%20{full_topic}` +4. Backslash before equals: `http://{ip}/cm?cmnd=FullTopic\={full_topic}` +5. Double equals: `http://{ip}/cm?cmnd=FullTopic=={full_topic}` +6. No separator (direct value): `http://{ip}/cm?cmnd=FullTopic{full_topic}` + +The testing revealed that three approaches worked correctly: +1. Using space (%20) instead of equals +2. Backslash before equals +3. No separator (direct value) + +## Solution +The "no separator" approach was chosen as the simplest and most reliable solution. The code was modified in two places: + +1. In the `configure_unknown_device` method: +```python +# For FullTopic, we need to avoid adding a space (%20) or equals sign between the command and value +if setting == "FullTopic": + url = f"http://{ip}/cm?cmnd={setting}{value}" +else: + url = f"http://{ip}/cm?cmnd={setting}%20{value}" +``` + +2. In the `check_mqtt_settings` function: +```python +# For FullTopic, we need to avoid adding a space (%20) or equals sign between the command and value +if setting == "FullTopic": + url = f"http://{ip}/cm?cmnd={setting}{value}" +else: + url = f"http://{ip}/cm?cmnd={setting}%20{value}" +``` + +## Testing +The fix was tested by running the TasmotaManager.py script with the --Device parameter and verifying that the FullTopic parameter was set correctly without the extra '=' at the beginning of the value. + +Before the fix: +```json +{"FullTopic":"=%prefix%/%topic%/"} +``` + +After the fix: +```json +{"FullTopic":"%prefix%/%topic%/"} +``` + +## Conclusion +The issue has been resolved by changing how the FullTopic parameter is set. Instead of using an equals sign as a separator between the command and value, the fix uses no separator at all, which prevents the Tasmota device from adding an extra equals sign to the beginning of the value. \ No newline at end of file diff --git a/test_fulltopic_approaches.py b/test_fulltopic_approaches.py new file mode 100755 index 0000000..152f4dc --- /dev/null +++ b/test_fulltopic_approaches.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +""" +Test script to try different approaches for setting the FullTopic parameter +to find a solution that avoids the extra '=' being added to the beginning of the value. +""" + +import sys +import logging +import requests +import json +import argparse +import time +import urllib.parse + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) + +logger = logging.getLogger("FullTopicApproachesTest") + +def load_config(): + """Load the network configuration.""" + try: + with open('network_configuration.json', 'r') as f: + return json.load(f) + except Exception as e: + logger.error(f"Error loading configuration: {str(e)}") + sys.exit(1) + +def get_current_fulltopic(ip_address): + """Get the current FullTopic value from the device.""" + try: + status_url = f"http://{ip_address}/cm?cmnd=FullTopic" + response = requests.get(status_url, timeout=5) + if response.status_code == 200: + try: + # Try to parse as JSON + data = response.json() + if isinstance(data, dict) and "FullTopic" in data: + current_value = data["FullTopic"] + else: + current_value = response.text + except: + # If not JSON, use the raw text + current_value = response.text + + logger.info(f"Current FullTopic value: {current_value}") + return current_value + else: + logger.error(f"Failed to get current FullTopic value: {response.status_code}") + return None + except requests.exceptions.RequestException as e: + logger.error(f"Error connecting to device: {str(e)}") + return None + +def test_approach(ip_address, approach_name, url): + """Test a specific approach for setting the FullTopic parameter.""" + logger.info(f"Testing approach: {approach_name}") + logger.info(f"URL: {url}") + + try: + response = requests.get(url, timeout=5) + + # Log the raw response for debugging + logger.info(f"Raw response: {response.text}") + + if response.status_code == 200: + try: + # Try to parse as JSON + data = response.json() + logger.info(f"Response JSON: {data}") + except: + logger.info(f"Response is not JSON: {response.text}") + else: + logger.error(f"Failed to set FullTopic: {response.status_code}") + return False + except requests.exceptions.RequestException as e: + logger.error(f"Error setting FullTopic: {str(e)}") + return False + + # Wait a moment for the change to take effect + time.sleep(1) + + # Verify the FullTopic was set correctly + new_value = get_current_fulltopic(ip_address) + if new_value is None: + return False + + # Check if the value has an extra '=' at the beginning + if new_value.startswith('='): + logger.error(f"ISSUE DETECTED: FullTopic still has an extra '=' at the beginning: {new_value}") + return False + else: + logger.info(f"SUCCESS: FullTopic set correctly without an extra '=': {new_value}") + return True + +def main(): + """Main function to test different approaches for setting the FullTopic parameter.""" + parser = argparse.ArgumentParser(description='Test different approaches for setting the FullTopic parameter') + parser.add_argument('ip_address', help='IP address of the Tasmota device to test') + args = parser.parse_args() + + if not args.ip_address: + print("Usage: python test_fulltopic_approaches.py ") + sys.exit(1) + + # Load configuration + config = load_config() + mqtt_config = config.get('mqtt', {}) + + if not mqtt_config: + logger.error("No MQTT configuration found") + sys.exit(1) + + # Get the FullTopic value from configuration + full_topic = mqtt_config.get('FullTopic', '%prefix%/%topic%/') + logger.info(f"FullTopic from configuration: {full_topic}") + + # Get the current FullTopic value + current_value = get_current_fulltopic(args.ip_address) + if current_value is None: + sys.exit(1) + + # Try different approaches + approaches = [ + # Current approach in TasmotaManager.py + { + "name": "Current approach (setting=value)", + "url": f"http://{args.ip_address}/cm?cmnd=FullTopic={full_topic}" + }, + # Try with URL encoding the value + { + "name": "URL encoded value", + "url": f"http://{args.ip_address}/cm?cmnd=FullTopic={urllib.parse.quote(full_topic)}" + }, + # Try with a space (%20) instead of equals + { + "name": "Using space (%20) instead of equals", + "url": f"http://{args.ip_address}/cm?cmnd=FullTopic%20{full_topic}" + }, + # Try with backslash before equals + { + "name": "Backslash before equals", + "url": f"http://{args.ip_address}/cm?cmnd=FullTopic\\={full_topic}" + }, + # Try with double equals + { + "name": "Double equals", + "url": f"http://{args.ip_address}/cm?cmnd=FullTopic=={full_topic}" + }, + # Try with no separator (direct value) + { + "name": "No separator (direct value)", + "url": f"http://{args.ip_address}/cm?cmnd=FullTopic{full_topic}" + } + ] + + # Test each approach + successful_approaches = [] + for approach in approaches: + success = test_approach(args.ip_address, approach["name"], approach["url"]) + if success: + successful_approaches.append(approach["name"]) + + # Print summary + print("\n=== SUMMARY ===") + if successful_approaches: + print(f"Successful approaches: {len(successful_approaches)}/{len(approaches)}") + for i, approach in enumerate(successful_approaches, 1): + print(f"{i}. {approach}") + else: + print("No successful approaches found.") + + # Exit with success if at least one approach worked + sys.exit(0 if successful_approaches else 1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/test_fulltopic_equals_issue.py b/test_fulltopic_equals_issue.py new file mode 100755 index 0000000..89a4950 --- /dev/null +++ b/test_fulltopic_equals_issue.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +""" +Test script to verify the issue with an extra '=' being added to the beginning of the FullTopic value. +This script will: +1. Connect to a Tasmota device +2. Check the current FullTopic value +3. Set the FullTopic parameter using the current code +4. Verify if an extra '=' is being added to the beginning of the value +""" + +import sys +import logging +import requests +import json +import argparse +import time + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) + +logger = logging.getLogger("FullTopicEqualsTest") + +def load_config(): + """Load the network configuration.""" + try: + with open('network_configuration.json', 'r') as f: + return json.load(f) + except Exception as e: + logger.error(f"Error loading configuration: {str(e)}") + sys.exit(1) + +def test_fulltopic_equals_issue(ip_address): + """Test if an extra '=' is being added to the beginning of the FullTopic value.""" + logger.info(f"Testing FullTopic equals issue on device at {ip_address}") + + # Load configuration + config = load_config() + mqtt_config = config.get('mqtt', {}) + + if not mqtt_config: + logger.error("No MQTT configuration found") + return False + + # Get the FullTopic value from configuration + full_topic = mqtt_config.get('FullTopic', '%prefix%/%topic%/') + logger.info(f"FullTopic from configuration: {full_topic}") + + # First, check the current FullTopic value + try: + status_url = f"http://{ip_address}/cm?cmnd=FullTopic" + response = requests.get(status_url, timeout=5) + if response.status_code == 200: + try: + # Try to parse as JSON + data = response.json() + if isinstance(data, dict) and "FullTopic" in data: + current_value = data["FullTopic"] + else: + current_value = response.text + except: + # If not JSON, use the raw text + current_value = response.text + + logger.info(f"Current FullTopic value: {current_value}") + else: + logger.error(f"Failed to get current FullTopic value: {response.status_code}") + return False + except requests.exceptions.RequestException as e: + logger.error(f"Error connecting to device: {str(e)}") + return False + + # Set the FullTopic using the current code method + try: + # This is how it's done in TasmotaManager.py + set_url = f"http://{ip_address}/cm?cmnd=FullTopic={full_topic}" + logger.info(f"Setting FullTopic with URL: {set_url}") + response = requests.get(set_url, timeout=5) + + # Log the raw response for debugging + logger.info(f"Raw response: {response.text}") + + if response.status_code == 200: + try: + # Try to parse as JSON + data = response.json() + logger.info(f"Response JSON: {data}") + except: + logger.info(f"Response is not JSON: {response.text}") + else: + logger.error(f"Failed to set FullTopic: {response.status_code}") + return False + except requests.exceptions.RequestException as e: + logger.error(f"Error setting FullTopic: {str(e)}") + return False + + # Wait a moment for the change to take effect + time.sleep(1) + + # Verify the FullTopic was set correctly + try: + verify_url = f"http://{ip_address}/cm?cmnd=FullTopic" + response = requests.get(verify_url, timeout=5) + if response.status_code == 200: + try: + # Try to parse as JSON + data = response.json() + if isinstance(data, dict) and "FullTopic" in data: + new_value = data["FullTopic"] + else: + new_value = response.text + except: + # If not JSON, use the raw text + new_value = response.text + + logger.info(f"New FullTopic value: {new_value}") + + # Check if the value has an extra '=' at the beginning + if new_value.startswith('='): + logger.error(f"ISSUE DETECTED: FullTopic has an extra '=' at the beginning: {new_value}") + return True # Return True to indicate the issue was found + else: + logger.info("FullTopic does not have an extra '=' at the beginning") + return False # Return False to indicate the issue was not found + else: + logger.error(f"Failed to verify FullTopic: {response.status_code}") + return False + except requests.exceptions.RequestException as e: + logger.error(f"Error verifying FullTopic: {str(e)}") + return False + +def main(): + """Main function to test the FullTopic equals issue.""" + parser = argparse.ArgumentParser(description='Test FullTopic equals issue') + parser.add_argument('ip_address', help='IP address of the Tasmota device to test') + args = parser.parse_args() + + if not args.ip_address: + print("Usage: python test_fulltopic_equals_issue.py ") + sys.exit(1) + + issue_found = test_fulltopic_equals_issue(args.ip_address) + + if issue_found: + print("ISSUE CONFIRMED: An extra '=' is being added to the beginning of the FullTopic value") + sys.exit(0) + else: + print("ISSUE NOT FOUND: No extra '=' is being added to the beginning of the FullTopic value") + sys.exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file