TasmotaManager/get_device_hostname_function_design.md
Mike Geppert 126cd39555 Major code improvements and bug fixes:
1. Restructured configuration: Moved config_other and console to top level
2. Added common _match_pattern function for regex pattern matching
3. Implemented Unifi Hostname bug fix in is_hostname_unknown
4. Created common get_device_hostname function to eliminate code duplication
5. Added comprehensive test scripts for all new functionality
6. Added detailed documentation for all changes
2025-08-08 19:04:33 -05:00

6.5 KiB

Common Function Design: get_device_hostname

Purpose

Create a common function to retrieve a device's hostname from a Tasmota device, eliminating code duplication and ensuring consistent error handling and logging across the codebase.

Function Signature

def get_device_hostname(self, ip: str, device_name: str = None, timeout: int = 5, log_level: str = 'debug') -> tuple:
    """Retrieve the hostname from a Tasmota device.
    
    This function makes an HTTP request to a Tasmota device to retrieve its self-reported
    hostname using the Status 5 command. It handles error conditions and provides
    consistent logging.
    
    Args:
        ip: The IP address of the device
        device_name: Optional name of the device for logging purposes
        timeout: Timeout for the HTTP request in seconds (default: 5)
        log_level: The logging level to use ('debug', 'info', 'warning', 'error'). Default is 'debug'.
        
    Returns:
        tuple: (hostname, success)
            - hostname: The device's self-reported hostname, or empty string if not found
            - success: Boolean indicating whether the hostname was successfully retrieved
            
    Examples:
        # Basic usage
        hostname, success = manager.get_device_hostname("192.168.1.100")
        if success:
            print(f"Device hostname: {hostname}")
            
        # With device name for better logging
        hostname, success = manager.get_device_hostname("192.168.1.100", "Living Room Light")
        
        # With custom timeout and log level
        hostname, success = manager.get_device_hostname("192.168.1.100", timeout=10, log_level='info')
    """

Implementation Details

Parameters

  1. ip (required): The IP address of the device to query
  2. device_name (optional): Name of the device for logging purposes
  3. timeout (optional): Timeout for the HTTP request in seconds (default: 5)
  4. log_level (optional): The logging level to use (default: 'debug')

Return Value

A tuple containing:

  1. hostname: The device's self-reported hostname, or empty string if not found
  2. success: Boolean indicating whether the hostname was successfully retrieved

Error Handling

The function should handle:

  1. Network errors (connection failures, timeouts)
  2. Invalid responses (non-200 status codes)
  3. JSON parsing errors
  4. Missing or invalid data in the response

Logging

The function should log:

  1. Debug/Info: Attempt to retrieve hostname
  2. Debug/Info: Successfully retrieved hostname
  3. Debug/Warning: Failed to retrieve hostname (with reason)
  4. Debug: Raw response data for troubleshooting

Code Structure

def get_device_hostname(self, ip: str, device_name: str = None, timeout: int = 5, log_level: str = 'debug') -> tuple:
    # Set up logging based on the specified level
    log_func = getattr(self.logger, log_level)
    
    # Use device_name in logs if provided, otherwise use IP
    device_id = device_name if device_name else ip
    
    hostname = ""
    success = False
    
    try:
        # Log attempt to retrieve hostname
        log_func(f"Retrieving hostname for {device_id} at {ip}")
        
        # Make HTTP request to the device
        url = f"http://{ip}/cm?cmnd=Status%205"
        response = requests.get(url, timeout=timeout)
        
        # Check if response is successful
        if response.status_code == 200:
            try:
                # Parse JSON response
                status_data = response.json()
                
                # Extract hostname from response
                hostname = status_data.get('StatusNET', {}).get('Hostname', '')
                
                if hostname:
                    log_func(f"Successfully retrieved hostname for {device_id}: {hostname}")
                    success = True
                else:
                    log_func(f"No hostname found in response for {device_id}")
            except ValueError:
                log_func(f"Failed to parse JSON response from {device_id}")
        else:
            log_func(f"Failed to get hostname for {device_id}: HTTP {response.status_code}")
    except requests.exceptions.RequestException as e:
        log_func(f"Error retrieving hostname for {device_id}: {str(e)}")
    
    return hostname, success

Usage in Existing Code

In is_hostname_unknown function

# Get the device's self-reported hostname
hostname, success = self.get_device_hostname(ip, hostname, log_level='debug')
if success:
    # Check if the self-reported hostname matches unknown patterns
    device_hostname_matches_unknown = False
    for pattern in patterns:
        if self._match_pattern(hostname.lower(), pattern, match_entire_string=False):
            device_hostname_matches_unknown = True
            self.logger.debug(f"Device's self-reported hostname '{hostname}' matches unknown pattern: {pattern}")
            break

In get_tasmota_devices method

# Get the device's self-reported hostname
device_reported_hostname, success = self.get_device_hostname(device_ip, device_name, log_level='debug')
if success:
    # Check if the self-reported hostname also matches unknown patterns
    device_hostname_matches_unknown = False
    for pattern in unknown_patterns:
        # ... pattern matching code ...

In process_single_device method

# Get the device's self-reported hostname
device_reported_hostname, success = self.get_device_hostname(device_ip, device_name, log_level='info')
if success:
    # Check if the self-reported hostname also matches unknown patterns
    # ... pattern matching code ...
else:
    # No self-reported hostname found or error occurred, fall back to UniFi-reported name
    is_unknown = unifi_name_matches_unknown
    self.logger.info("Failed to get device's self-reported hostname, using UniFi-reported name")

In Device Details Collection

# Get Status 5 for network info
hostname, success = self.get_device_hostname(ip, name, log_level='info')
if not success:
    hostname = "Unknown"

device_detail = {
    # ... other fields ...
    "hostname": hostname,
    # ... other fields ...
}

Benefits

  1. Code Reuse: Eliminates duplicated code for retrieving a device's hostname
  2. Consistency: Ensures consistent error handling and logging across the codebase
  3. Maintainability: Makes it easier to update the hostname retrieval logic in one place
  4. Readability: Makes the code more concise and easier to understand
  5. Flexibility: Provides options for customizing timeout and logging level