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
171 lines
6.5 KiB
Markdown
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 |