Console updates: add idempotent pre-checks before sending commands; skip when values already match. Retain params toggled only if needed; rule definitions compared via RuleN 5; RuleN auto-enable skipped if already ON. Add helper _get_console_param_value. Confirm config_other already conditional.
This commit is contained in:
parent
7d1755b44a
commit
c31bcbdf85
@ -1362,6 +1362,17 @@ class TasmotaDiscovery:
|
||||
if param in console_params:
|
||||
try:
|
||||
final_value = console_params[param]
|
||||
# Pre-check current value; skip if already at desired state
|
||||
current_val, ok = self._get_console_param_value(ip, name, param)
|
||||
if ok:
|
||||
desired_cmp = str(final_value).strip().lower()
|
||||
current_cmp = str(current_val or "").strip().lower()
|
||||
# Map 1/0 to on/off for retain-like responses
|
||||
if current_cmp in ("1", "0") and desired_cmp in ("on", "off"):
|
||||
current_cmp = "on" if current_cmp == "1" else "off"
|
||||
if current_cmp == desired_cmp:
|
||||
self.logger.debug(f"{name}: {param} already {final_value}, skipping retain toggle")
|
||||
continue
|
||||
# Set opposite state first
|
||||
opposite_value = "On" if final_value.lower() == "off" else "Off"
|
||||
|
||||
@ -1508,6 +1519,41 @@ class TasmotaDiscovery:
|
||||
# If this is in the config, we'll respect it, but log that it's not needed
|
||||
self.logger.debug(f"{name}: Note: {param} is not needed with auto-enable feature")
|
||||
|
||||
# Determine if update is needed before sending
|
||||
should_send = True
|
||||
try:
|
||||
current_val, ok = self._get_console_param_value(ip, name, param)
|
||||
if ok:
|
||||
if param.lower().startswith('rule') and param.lower() == param and param[-1].isdigit():
|
||||
# Compare rule definitions (whitespace-insensitive, case-insensitive)
|
||||
import re as _re
|
||||
def _norm_rule(s):
|
||||
s = str(s or "")
|
||||
s = s.strip().lower()
|
||||
s = _re.sub(r"\s+", " ", s)
|
||||
return s
|
||||
if _norm_rule(current_val) == _norm_rule(value):
|
||||
self.logger.debug(f"{name}: {param} rule already matches definition, skipping")
|
||||
should_send = False
|
||||
else:
|
||||
# Generic comparison
|
||||
desired_cmp = str(value).strip().lower()
|
||||
current_cmp = str(current_val or "").strip().lower()
|
||||
# Normalize common ON/OFF vs 1/0 forms
|
||||
if current_cmp in ("on", "off") and desired_cmp in ("1", "0"):
|
||||
current_cmp = "1" if current_cmp == "on" else "0"
|
||||
if current_cmp in ("1", "0") and desired_cmp in ("on", "off"):
|
||||
current_cmp = "on" if current_cmp == "1" else "off"
|
||||
if current_cmp == desired_cmp:
|
||||
self.logger.debug(f"{name}: {param} already set to {value}, skipping")
|
||||
should_send = False
|
||||
except Exception:
|
||||
# If pre-check fails, fall back to sending command
|
||||
pass
|
||||
|
||||
if not should_send:
|
||||
continue
|
||||
|
||||
# Regular console parameter
|
||||
# Special handling for rule parameters to properly encode the URL
|
||||
if param.lower().startswith('rule') and param.lower() == param and param[-1].isdigit():
|
||||
@ -1601,6 +1647,16 @@ class TasmotaDiscovery:
|
||||
continue
|
||||
|
||||
# Rule auto-enabling
|
||||
# Pre-check if rule already enabled
|
||||
try:
|
||||
current_val, ok = self._get_console_param_value(ip, name, rule_enable_param)
|
||||
if ok:
|
||||
state = str(current_val or "").strip().lower()
|
||||
if state in ("on", "1", "enabled", "active"):
|
||||
self.logger.debug(f"{name}: {rule_enable_param} already enabled, skipping")
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
url = f"http://{ip}/cm?cmnd={rule_enable_param}%201"
|
||||
|
||||
if with_retry:
|
||||
@ -1655,6 +1711,81 @@ class TasmotaDiscovery:
|
||||
|
||||
return console_updated
|
||||
|
||||
def _get_console_param_value(self, ip, name, param):
|
||||
"""Query the device for the current value of a console parameter.
|
||||
Returns (value, True) on success, (None, False) on failure.
|
||||
Special handling:
|
||||
- Lowercase ruleN: returns the current rule definition text using RuleN 5
|
||||
- Uppercase RuleN: returns the current enable state (ON/OFF or 1/0)
|
||||
"""
|
||||
try:
|
||||
# Rules handling
|
||||
if param.lower().startswith('rule') and param[-1].isdigit():
|
||||
rule_num = param[-1]
|
||||
# If lowercase 'ruleN', fetch rule definition using 'RuleN 5'
|
||||
if param.islower():
|
||||
url = f"http://{ip}/cm?cmnd=Rule{rule_num}%205"
|
||||
response = requests.get(url, timeout=5)
|
||||
# Try to parse JSON first
|
||||
try:
|
||||
data = response.json()
|
||||
# Common keys: 'Rules' may contain the definition
|
||||
if isinstance(data, dict):
|
||||
for k, v in data.items():
|
||||
if str(k).lower() == 'rules':
|
||||
return v, True
|
||||
# Fallback: first string value
|
||||
for v in data.values():
|
||||
if isinstance(v, str):
|
||||
return v, True
|
||||
return str(data), True
|
||||
except Exception:
|
||||
# Fallback to text parsing
|
||||
text = response.text or ''
|
||||
return text, True
|
||||
else:
|
||||
# Uppercase 'RuleN' - fetch enable state
|
||||
url = f"http://{ip}/cm?cmnd=Rule{rule_num}"
|
||||
response = requests.get(url, timeout=5)
|
||||
try:
|
||||
data = response.json()
|
||||
# Expect something like {"Rule1":"ON"}
|
||||
if isinstance(data, dict):
|
||||
# Try exact key first
|
||||
key = f"Rule{rule_num}"
|
||||
if key in data:
|
||||
return data[key], True
|
||||
# Fallback: any ON/OFF value
|
||||
for v in data.values():
|
||||
if isinstance(v, (str, int)):
|
||||
return v, True
|
||||
return str(data), True
|
||||
except Exception:
|
||||
text = response.text or ''
|
||||
return text, True
|
||||
# Generic parameter query - send the command name without a value
|
||||
url = f"http://{ip}/cm?cmnd={param}"
|
||||
response = requests.get(url, timeout=5)
|
||||
try:
|
||||
data = response.json()
|
||||
if isinstance(data, dict) and data:
|
||||
# Prefer an exact key match (case-insensitive)
|
||||
param_lower = param.lower()
|
||||
for k, v in data.items():
|
||||
if str(k).lower() == param_lower:
|
||||
return v, True
|
||||
# Fallback: first value
|
||||
first_val = next(iter(data.values()))
|
||||
return first_val, True
|
||||
# If not a dict, return as string
|
||||
return str(data), True
|
||||
except Exception:
|
||||
# Fallback to raw text
|
||||
return response.text, True
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.logger.debug(f"{name}: Failed to query current value for {param}: {e}")
|
||||
return None, False
|
||||
|
||||
def apply_config_other(self, ip, name):
|
||||
"""Wrapper for applying config_other (template) settings."""
|
||||
return self.check_and_update_template(ip, name)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user