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 |