Add --diff feature to compare two Tasmota devices
New Feature: Device configuration comparison tool for diagnostics Usage: python TasmotaManager.py --diff DEVICE1 DEVICE2 Features: - Queries all SetOptions (0-150) from both devices - Compares firmware versions, network config, MQTT settings - Shows Rule configurations and status - Displays all differences in human-readable format - Helps diagnose configuration issues between working/non-working devices Files Added: - device_diff.py: New DeviceComparison class with full status queries Files Modified: - TasmotaManager.py: Added --diff argument and comparison mode handler Example: python TasmotaManager.py --diff KitchenMain KitchenBar This tool revealed that KitchenMain (working) and KitchenBar (problematic) have significant SetOption differences including SetOption32, SetOption40, and others that may affect button press behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
65147fe4be
commit
49cf1ec789
@ -13,6 +13,7 @@ from configuration import ConfigurationManager
|
|||||||
from console_settings import ConsoleSettingsManager
|
from console_settings import ConsoleSettingsManager
|
||||||
from unknown_devices import UnknownDeviceProcessor
|
from unknown_devices import UnknownDeviceProcessor
|
||||||
from reporting import ReportGenerator
|
from reporting import ReportGenerator
|
||||||
|
from device_diff import DeviceComparison
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(debug: bool = False) -> logging.Logger:
|
def setup_logging(debug: bool = False) -> logging.Logger:
|
||||||
@ -307,6 +308,8 @@ def main():
|
|||||||
help='Generate UniFi hostname comparison report')
|
help='Generate UniFi hostname comparison report')
|
||||||
parser.add_argument('--Device', type=str,
|
parser.add_argument('--Device', type=str,
|
||||||
help='Process single device by IP or hostname')
|
help='Process single device by IP or hostname')
|
||||||
|
parser.add_argument('--diff', nargs=2, metavar=('DEVICE1', 'DEVICE2'),
|
||||||
|
help='Compare two devices and show configuration differences')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@ -334,6 +337,35 @@ def main():
|
|||||||
unknown_processor = UnknownDeviceProcessor(config, config_manager, logger)
|
unknown_processor = UnknownDeviceProcessor(config, config_manager, logger)
|
||||||
report_gen = ReportGenerator(config, discovery, logger)
|
report_gen = ReportGenerator(config, discovery, logger)
|
||||||
|
|
||||||
|
# Handle device comparison mode
|
||||||
|
if args.diff:
|
||||||
|
device_comp = DeviceComparison(logger)
|
||||||
|
|
||||||
|
# Get devices list to resolve names/IPs
|
||||||
|
devices = discovery.get_tasmota_devices()
|
||||||
|
|
||||||
|
# Find device 1
|
||||||
|
device1 = find_device_by_identifier(devices, args.diff[0], logger)
|
||||||
|
if not device1:
|
||||||
|
logger.error(f"Device 1 not found: {args.diff[0]}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Find device 2
|
||||||
|
device2 = find_device_by_identifier(devices, args.diff[1], logger)
|
||||||
|
if not device2:
|
||||||
|
logger.error(f"Device 2 not found: {args.diff[1]}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Compare devices
|
||||||
|
comparison = device_comp.compare_devices(
|
||||||
|
device1['ip'], device1['name'],
|
||||||
|
device2['ip'], device2['name']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Print report
|
||||||
|
device_comp.print_comparison_report(comparison)
|
||||||
|
return 0
|
||||||
|
|
||||||
# Handle hostname report mode
|
# Handle hostname report mode
|
||||||
if args.unifi_hostname_report:
|
if args.unifi_hostname_report:
|
||||||
report_gen.generate_unifi_hostname_report()
|
report_gen.generate_unifi_hostname_report()
|
||||||
|
|||||||
250
device_diff.py
Normal file
250
device_diff.py
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
"""Device comparison and diagnostics module."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Dict, List, Tuple, Optional
|
||||||
|
from utils import send_tasmota_command
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceComparison:
|
||||||
|
"""Compare configuration between two Tasmota devices."""
|
||||||
|
|
||||||
|
def __init__(self, logger: Optional[logging.Logger] = None):
|
||||||
|
"""
|
||||||
|
Initialize device comparison.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
logger: Optional logger instance
|
||||||
|
"""
|
||||||
|
self.logger = logger or logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def get_device_full_status(self, device_ip: str, device_name: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Get complete device status and configuration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_ip: Device IP address
|
||||||
|
device_name: Device name for logging
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with all device status information
|
||||||
|
"""
|
||||||
|
self.logger.info(f"Querying full status from {device_name} ({device_ip})")
|
||||||
|
|
||||||
|
device_info = {
|
||||||
|
'name': device_name,
|
||||||
|
'ip': device_ip,
|
||||||
|
'firmware': {},
|
||||||
|
'network': {},
|
||||||
|
'mqtt': {},
|
||||||
|
'setoptions': {},
|
||||||
|
'rules': {},
|
||||||
|
'gpio': {},
|
||||||
|
'other': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get Status 0 (all status)
|
||||||
|
result, success = send_tasmota_command(device_ip, "Status%200", timeout=10, logger=self.logger)
|
||||||
|
if success and result:
|
||||||
|
# Extract firmware info
|
||||||
|
if 'StatusFWR' in result:
|
||||||
|
device_info['firmware'] = result['StatusFWR']
|
||||||
|
|
||||||
|
# Extract network info
|
||||||
|
if 'StatusNET' in result:
|
||||||
|
device_info['network'] = result['StatusNET']
|
||||||
|
|
||||||
|
# Extract MQTT info
|
||||||
|
if 'StatusMQT' in result:
|
||||||
|
device_info['mqtt'] = result['StatusMQT']
|
||||||
|
|
||||||
|
# Extract basic status
|
||||||
|
if 'Status' in result:
|
||||||
|
device_info['other']['status'] = result['Status']
|
||||||
|
|
||||||
|
# Get all SetOptions (0-150)
|
||||||
|
self.logger.debug(f"Querying SetOptions from {device_name}")
|
||||||
|
for i in range(0, 151):
|
||||||
|
result, success = send_tasmota_command(device_ip, f"SetOption{i}", timeout=5, logger=self.logger)
|
||||||
|
if success and result:
|
||||||
|
key = f"SetOption{i}"
|
||||||
|
if key in result:
|
||||||
|
device_info['setoptions'][key] = result[key]
|
||||||
|
|
||||||
|
# Get Rules (1-3)
|
||||||
|
self.logger.debug(f"Querying Rules from {device_name}")
|
||||||
|
for i in range(1, 4):
|
||||||
|
result, success = send_tasmota_command(device_ip, f"Rule{i}%204", timeout=5, logger=self.logger)
|
||||||
|
if success and result:
|
||||||
|
key = f"Rule{i}"
|
||||||
|
if key in result:
|
||||||
|
device_info['rules'][key] = result[key]
|
||||||
|
|
||||||
|
# Get other important settings
|
||||||
|
for cmd in ['ButtonDebounce', 'SwitchDebounce', 'Template', 'Module']:
|
||||||
|
result, success = send_tasmota_command(device_ip, cmd, timeout=5, logger=self.logger)
|
||||||
|
if success and result:
|
||||||
|
device_info['other'][cmd] = result
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
def compare_devices(self, device1_ip: str, device1_name: str,
|
||||||
|
device2_ip: str, device2_name: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Compare two devices and return differences.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device1_ip: First device IP
|
||||||
|
device1_name: First device name
|
||||||
|
device2_ip: Second device IP
|
||||||
|
device2_name: Second device name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with comparison results
|
||||||
|
"""
|
||||||
|
self.logger.info(f"Comparing {device1_name} vs {device2_name}")
|
||||||
|
|
||||||
|
# Get full status from both devices
|
||||||
|
device1 = self.get_device_full_status(device1_ip, device1_name)
|
||||||
|
device2 = self.get_device_full_status(device2_ip, device2_name)
|
||||||
|
|
||||||
|
# Compare and find differences
|
||||||
|
differences = {
|
||||||
|
'device1': {'name': device1_name, 'ip': device1_ip},
|
||||||
|
'device2': {'name': device2_name, 'ip': device2_ip},
|
||||||
|
'firmware': self._compare_section(device1['firmware'], device2['firmware']),
|
||||||
|
'network': self._compare_section(device1['network'], device2['network']),
|
||||||
|
'mqtt': self._compare_section(device1['mqtt'], device2['mqtt']),
|
||||||
|
'setoptions': self._compare_section(device1['setoptions'], device2['setoptions']),
|
||||||
|
'rules': self._compare_section(device1['rules'], device2['rules']),
|
||||||
|
'other': self._compare_section(device1['other'], device2['other'])
|
||||||
|
}
|
||||||
|
|
||||||
|
return differences
|
||||||
|
|
||||||
|
def _compare_section(self, section1: Dict, section2: Dict) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Compare two configuration sections.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
section1: First device section
|
||||||
|
section2: Second device section
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of differences
|
||||||
|
"""
|
||||||
|
differences = []
|
||||||
|
|
||||||
|
# Get all keys from both sections
|
||||||
|
all_keys = set(section1.keys()) | set(section2.keys())
|
||||||
|
|
||||||
|
for key in sorted(all_keys):
|
||||||
|
val1 = section1.get(key)
|
||||||
|
val2 = section2.get(key)
|
||||||
|
|
||||||
|
if val1 != val2:
|
||||||
|
differences.append({
|
||||||
|
'key': key,
|
||||||
|
'device1_value': val1,
|
||||||
|
'device2_value': val2
|
||||||
|
})
|
||||||
|
|
||||||
|
return differences
|
||||||
|
|
||||||
|
def print_comparison_report(self, comparison: Dict) -> None:
|
||||||
|
"""
|
||||||
|
Print human-readable comparison report.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
comparison: Comparison results dictionary
|
||||||
|
"""
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print("DEVICE COMPARISON REPORT")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
device1 = comparison['device1']
|
||||||
|
device2 = comparison['device2']
|
||||||
|
|
||||||
|
print(f"\nDevice 1: {device1['name']} ({device1['ip']})")
|
||||||
|
print(f"Device 2: {device2['name']} ({device2['ip']})")
|
||||||
|
|
||||||
|
# Print firmware info
|
||||||
|
print("\n" + "-" * 80)
|
||||||
|
print("FIRMWARE DIFFERENCES")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
if comparison['firmware']:
|
||||||
|
self._print_differences(comparison['firmware'], device1['name'], device2['name'])
|
||||||
|
else:
|
||||||
|
print("No differences found")
|
||||||
|
|
||||||
|
# Print network differences
|
||||||
|
print("\n" + "-" * 80)
|
||||||
|
print("NETWORK DIFFERENCES")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
if comparison['network']:
|
||||||
|
self._print_differences(comparison['network'], device1['name'], device2['name'])
|
||||||
|
else:
|
||||||
|
print("No differences found")
|
||||||
|
|
||||||
|
# Print MQTT differences
|
||||||
|
print("\n" + "-" * 80)
|
||||||
|
print("MQTT DIFFERENCES")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
if comparison['mqtt']:
|
||||||
|
self._print_differences(comparison['mqtt'], device1['name'], device2['name'])
|
||||||
|
else:
|
||||||
|
print("No differences found")
|
||||||
|
|
||||||
|
# Print SetOption differences
|
||||||
|
print("\n" + "-" * 80)
|
||||||
|
print("SETOPTION DIFFERENCES")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
if comparison['setoptions']:
|
||||||
|
self._print_differences(comparison['setoptions'], device1['name'], device2['name'])
|
||||||
|
else:
|
||||||
|
print("No differences found")
|
||||||
|
|
||||||
|
# Print Rule differences
|
||||||
|
print("\n" + "-" * 80)
|
||||||
|
print("RULE DIFFERENCES")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
if comparison['rules']:
|
||||||
|
self._print_differences(comparison['rules'], device1['name'], device2['name'])
|
||||||
|
else:
|
||||||
|
print("No differences found")
|
||||||
|
|
||||||
|
# Print other differences
|
||||||
|
print("\n" + "-" * 80)
|
||||||
|
print("OTHER CONFIGURATION DIFFERENCES")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
if comparison['other']:
|
||||||
|
self._print_differences(comparison['other'], device1['name'], device2['name'])
|
||||||
|
else:
|
||||||
|
print("No differences found")
|
||||||
|
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print("END OF REPORT")
|
||||||
|
print("=" * 80 + "\n")
|
||||||
|
|
||||||
|
def _print_differences(self, differences: List[Dict], device1_name: str, device2_name: str) -> None:
|
||||||
|
"""
|
||||||
|
Print list of differences in readable format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
differences: List of difference dictionaries
|
||||||
|
device1_name: First device name
|
||||||
|
device2_name: Second device name
|
||||||
|
"""
|
||||||
|
for diff in differences:
|
||||||
|
key = diff['key']
|
||||||
|
val1 = diff['device1_value']
|
||||||
|
val2 = diff['device2_value']
|
||||||
|
|
||||||
|
print(f"\n{key}:")
|
||||||
|
print(f" {device1_name:20} = {val1}")
|
||||||
|
print(f" {device2_name:20} = {val2}")
|
||||||
Loading…
Reference in New Issue
Block a user