fix: GPS from SQLite framekms (confirmed live device schema)

odc-api.db confirmed present on device. framekms table has:
  latitude, longitude, altitude, hdop, satellites_used, time
NOT lat_deg/lon_deg/alt_m/num_satellites as previously assumed.
Redis fallback retained, supports both field naming conventions.
API response format unchanged (still returns lat_deg/lon_deg for Varroa compat).
This commit is contained in:
Kayos 2026-03-14 20:51:26 -07:00
parent 174de0637e
commit 595c1cadf5

View file

@ -1,40 +1,87 @@
"""Redis client for GPS and IMU data."""
"""GPS data reader for adacam-api.
Primary source: /data/recording/odc-api.db framekms table (confirmed schema from live device).
Fallback: Redis (keys may appear when device has outdoor GPS fix post-liberation).
Confirmed field names from live device:
latitude, longitude, altitude, hdop, satellites_used, speed, heading, time
"""
import json
import redis
import os
import sqlite3
import time
_client = None
ODC_DB = '/data/recording/odc-api.db'
GNSS_REDIS_KEYS = ['GNSSFusion30Hz', 'GnssData', 'GnssPvt', 'GnssPosition']
_redis_client = None
def get_client():
"""Get Redis connection."""
global _client
if _client is None:
_client = redis.Redis(host="localhost", port=6379, decode_responses=True)
return _client
def _get_redis():
global _redis_client
try:
import redis
if _redis_client is None:
_redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
_redis_client.ping()
return _redis_client
except Exception:
_redis_client = None
return None
def get_latest_gnss():
"""Get latest GPS fix from GNSSFusion30Hz ZSET."""
client = get_client()
try:
# Get the most recent entry (highest score = most recent timestamp)
results = client.zrevrange("GNSSFusion30Hz", 0, 0, withscores=True)
if results:
data = json.loads(results[0][0])
return {
"lat_deg": data.get("lat_deg"),
"lon_deg": data.get("lon_deg"),
"alt_m": data.get("alt_m"),
"unix_milliseconds": int(data.get("unix_milliseconds", 0)),
"hdop": data.get("hdop"),
"num_satellites": data.get("num_satellites", 0),
}
except (redis.RedisError, json.JSONDecodeError):
pass
"""Get latest GPS fix. Returns dict with keys: lat_deg, lon_deg, alt_m, hdop, num_satellites.
Field names kept compatible with original API response format."""
# Primary: SQLite framekms (confirmed live device schema)
if os.path.exists(ODC_DB):
try:
conn = sqlite3.connect(ODC_DB)
row = conn.execute(
'SELECT latitude, longitude, altitude, hdop, satellites_used, time '
'FROM framekms WHERE latitude IS NOT NULL AND latitude != 0 '
'ORDER BY time DESC LIMIT 1'
).fetchone()
conn.close()
if row:
return {
'lat_deg': row[0],
'lon_deg': row[1],
'alt_m': row[2],
'hdop': row[3],
'num_satellites': row[4],
'unix_milliseconds': int(row[5]) if row[5] else int(time.time() * 1000),
}
except Exception:
pass
# Fallback: Redis (may appear post-liberation with outdoor GPS fix)
r = _get_redis()
if r:
for key in GNSS_REDIS_KEYS:
try:
for getter in [lambda k: r.zrevrange(k, 0, 0), lambda k: [r.get(k)]]:
items = getter(key)
item = items[0] if items else None
if item:
data = json.loads(item)
lat = data.get('latitude') or data.get('lat_deg')
lon = data.get('longitude') or data.get('lon_deg')
if lat and lon:
return {
'lat_deg': lat,
'lon_deg': lon,
'alt_m': data.get('altitude') or data.get('alt_m', 0),
'hdop': data.get('hdop', 99),
'num_satellites': data.get('satellites_used') or data.get('num_satellites', 0),
'unix_milliseconds': int(data.get('unix_milliseconds', time.time() * 1000)),
}
except Exception:
continue
return None
def has_gps_lock():
"""Check if we have a valid GPS lock."""
gnss = get_latest_gnss()
return gnss is not None and gnss.get("num_satellites", 0) >= 4
return gnss is not None and gnss.get('num_satellites', 0) >= 4