<message>Update the _set_golden_from_path function to improve the handling of existing golden image files. Replace the existing unlink logic with a more robust method that safely removes files or broken symlinks using the missing_ok parameter. This change enhances the reliability of the backup upload process by ensuring that stale references are properly cleared before setting a new golden image path.
157 lines
6.6 KiB
Python
157 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
TM AIS GPS data fetcher
|
|
Fetches GPS coordinates from TM AIS GPS antenna via HTTP API
|
|
"""
|
|
|
|
import logging
|
|
import requests
|
|
from datetime import datetime, timezone
|
|
from typing import Dict, Any, Optional
|
|
from config import Config
|
|
|
|
# Suppress SSL warnings for self-signed certificates
|
|
import urllib3
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
|
|
logger = logging.getLogger("gnss_guard.tm_ais_gps")
|
|
|
|
|
|
class TMAISGPSFetcher:
|
|
"""Fetcher for TM AIS GPS coordinates"""
|
|
|
|
def __init__(self, config: Config):
|
|
self.config = config
|
|
self.url = config.tm_ais_url
|
|
self.token = config.tm_ais_token # Already trimmed in Config
|
|
self.last_fetch_failed = False
|
|
|
|
# Warn if token is empty
|
|
if not self.token:
|
|
logger.warning("TM AIS GPS token is empty - authentication will fail")
|
|
|
|
def fetch(self) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Fetch GPS coordinates from TM AIS GPS antenna
|
|
|
|
Returns:
|
|
Dictionary with position data or None if fetch fails
|
|
"""
|
|
if not self.config.tm_ais_enabled:
|
|
return None
|
|
|
|
headers = {"Authorization": f"Bearer {self.token}"}
|
|
max_retries = self.config.tm_ais_max_retries
|
|
last_error = None
|
|
|
|
# Log request details (mask token for security)
|
|
token_preview = f"{self.token[:4]}..." if len(self.token) > 4 else "***"
|
|
logger.debug(f"TM AIS GPS request: URL={self.url}, Token={token_preview}")
|
|
|
|
# Try up to max_retries times
|
|
for attempt_number in range(1, max_retries + 1):
|
|
logger.info(f"TM AIS GPS fetch attempt {attempt_number}/{max_retries}")
|
|
|
|
try:
|
|
# Disable SSL verification for self-signed certificates (equivalent to curl -k)
|
|
response = requests.get(
|
|
self.url,
|
|
headers=headers,
|
|
verify=False, # Equivalent to curl -k flag
|
|
timeout=5.0
|
|
)
|
|
|
|
# Log response status for debugging
|
|
logger.debug(f"TM AIS GPS response status: {response.status_code}")
|
|
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
|
|
# Extract coordinates
|
|
latitude = data.get("latitude")
|
|
longitude = data.get("longitude")
|
|
gps_timestamp = data.get("gps_timestamp")
|
|
response_timestamp = data.get("response_timestamp")
|
|
|
|
if latitude is None or longitude is None:
|
|
logger.warning("TM AIS GPS response missing latitude or longitude")
|
|
self.last_fetch_failed = True
|
|
return None
|
|
|
|
# Parse timestamps and convert to UTC
|
|
gps_ts = None
|
|
if gps_timestamp:
|
|
try:
|
|
# Parse timestamp (handles both Z and timezone offsets)
|
|
parsed_ts = datetime.fromisoformat(gps_timestamp.replace("Z", "+00:00"))
|
|
# Convert to UTC if timezone-aware, otherwise assume UTC
|
|
if parsed_ts.tzinfo is not None:
|
|
gps_ts = parsed_ts.astimezone(timezone.utc)
|
|
else:
|
|
gps_ts = parsed_ts.replace(tzinfo=timezone.utc)
|
|
except Exception as e:
|
|
logger.debug(f"Failed to parse GPS timestamp: {e}")
|
|
|
|
response_ts = datetime.now(timezone.utc)
|
|
if response_timestamp:
|
|
try:
|
|
# Parse timestamp (handles both Z and timezone offsets)
|
|
parsed_ts = datetime.fromisoformat(response_timestamp.replace("Z", "+00:00"))
|
|
# Convert to UTC if timezone-aware, otherwise assume UTC
|
|
if parsed_ts.tzinfo is not None:
|
|
response_ts = parsed_ts.astimezone(timezone.utc)
|
|
else:
|
|
response_ts = parsed_ts.replace(tzinfo=timezone.utc)
|
|
except Exception as e:
|
|
logger.debug(f"Failed to parse response timestamp: {e}")
|
|
|
|
# Success - reset failure flag
|
|
if self.last_fetch_failed:
|
|
logger.info("TM AIS GPS connection restored")
|
|
self.last_fetch_failed = False
|
|
|
|
return {
|
|
"source": "tm_ais",
|
|
"latitude": float(latitude),
|
|
"longitude": float(longitude),
|
|
"altitude": None,
|
|
"timestamp": gps_ts.isoformat() if gps_ts else response_ts.isoformat(),
|
|
"timestamp_unix": (gps_ts or response_ts).timestamp(),
|
|
"supplementary_data": {
|
|
"gps_timestamp": gps_timestamp,
|
|
"response_timestamp": response_timestamp,
|
|
}
|
|
}
|
|
|
|
except requests.exceptions.HTTPError as e:
|
|
# Log response body for 401 errors to help debug authentication issues
|
|
if hasattr(e.response, 'status_code') and e.response.status_code == 401:
|
|
try:
|
|
error_body = e.response.text[:200] # Limit to first 200 chars
|
|
logger.debug(f"TM AIS GPS 401 response body: {error_body}")
|
|
except Exception:
|
|
pass
|
|
last_error = str(e)
|
|
logger.info(f"TM AIS GPS attempt {attempt_number}/{max_retries} failed: {e}")
|
|
# Continue to next attempt
|
|
except requests.exceptions.RequestException as e:
|
|
last_error = str(e)
|
|
logger.info(f"TM AIS GPS attempt {attempt_number}/{max_retries} failed: {e}")
|
|
# Continue to next attempt
|
|
|
|
except Exception as e:
|
|
last_error = str(e)
|
|
logger.info(f"TM AIS GPS attempt {attempt_number}/{max_retries} unexpected error: {e}")
|
|
# Continue to next attempt
|
|
|
|
# All attempts failed
|
|
if not self.last_fetch_failed:
|
|
logger.error(f"Failed to fetch TM AIS GPS data after {max_retries} attempts. Last error: {last_error}")
|
|
else:
|
|
logger.debug(f"TM AIS GPS still unavailable after {max_retries} attempts")
|
|
|
|
self.last_fetch_failed = True
|
|
return None
|
|
|