Fix MQTT FullTopic with URL-encoded leading space

- Devices had FullTopic starting with %20 (URL-encoded space)
- This breaks MQTT topic publishing with invalid leading space
- Now detects %20 prefix and forces update even if normalized values match
- Properly URL-encodes all MQTT setting values when sending
- FullTopic %prefix%/%topic%/ now encoded as %25prefix%25%2F%25topic%25%2F
- Fixes MQTT topics showing as '%20stat/device/...' instead of 'stat/device/...'
This commit is contained in:
Mike Geppert 2026-01-07 21:05:20 -06:00
parent 12ebdbf3e9
commit be95930cd1

View File

@ -225,18 +225,24 @@ class ConfigurationManager:
current_full_topic = result.get('FullTopic', '') if success and result else ''
self.logger.debug(f"{device_name}: Raw FullTopic from device: '{current_full_topic}'")
# Check if device has URL-encoded spaces at the beginning (broken state)
has_leading_encoded_space = current_full_topic.startswith('%20')
# Normalize: remove any URL-encoded spaces from the beginning of current value
# This handles the case where the device returns '%20%prefix%' instead of '%prefix%'
while current_full_topic.startswith('%20'):
current_full_topic = current_full_topic[3:]
# Also normalize expected value in case config has leading spaces
mqtt_full_topic_normalized = mqtt_full_topic.lstrip()
self.logger.debug(f"{device_name}: Comparing FullTopic: current='{current_full_topic}' vs expected='{mqtt_full_topic_normalized}'")
if current_full_topic != mqtt_full_topic_normalized:
# Update if values don't match OR if device had leading encoded space (needs fixing)
if current_full_topic != mqtt_full_topic_normalized or has_leading_encoded_space:
if has_leading_encoded_space:
self.logger.info(f"{device_name}: FullTopic has invalid leading space, will fix")
updates_needed.append(('FullTopic', mqtt_full_topic_normalized))
# Handle NoRetain (SetOption62)
@ -255,8 +261,14 @@ class ConfigurationManager:
failed_updates = []
for setting_name, setting_value in updates_needed:
command = f"{setting_name}%20{setting_value}"
# URL encode the value, especially important for FullTopic which contains % and /
from urllib.parse import quote
# Convert value to string and URL encode it
encoded_value = quote(str(setting_value), safe='')
command = f"{setting_name}%20{encoded_value}"
self.logger.debug(f"{device_name}: Sending command: {command}")
result, success = retry_command(
lambda: send_tasmota_command(device_ip, command, timeout=5, logger=self.logger),
max_attempts=3,