Files
reterminal-dm4/backup-from-device/gnss-guard/tm-gnss-guard/sources/tm_ais_gps.py
nearxos 808fbf5c7c Refactor golden image handling in backup upload process</message>
<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.
2026-02-24 00:19:40 +02:00

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