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

171 lines
6.5 KiB
Markdown

# 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
```python
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
```python
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
```python
# 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
```python
# 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
```python
# 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
```python
# 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