adacam-api/adacam_api/db.py
kayos 37aefb84c8 Initial commit: adacam-api v1.0.0
Clean Python Flask replacement for odc-api (434k lines Node.js → ~350 lines Python)
- GET /api/1/landmarks/last/{N} - last N detections from SQLite
- POST /api/1/landmarks - ingest detections + forward to AdaMaps
- GET /api/1/gnssConcise/latestValid - GPS fix from Redis
- GET /api/1/status - device status
- GET /api/1/deviceinfo - device identity
- GET /api/1/recording/frames/latest - latest frame path

No /api/1/cmd - that's the CVE, it's gone.

Includes:
- SQLite for local storage + offline queue
- Background thread for AdaMaps retry
- systemd service unit
- install.sh for device deployment
2026-03-14 08:13:04 -07:00

88 lines
2.9 KiB
Python

"""SQLite database for landmarks and offline queue."""
import sqlite3
import threading
import json
DB_PATH = "/data/adacam/adacam.db"
_local = threading.local()
def get_conn():
"""Get thread-local database connection."""
if not hasattr(_local, "conn"):
_local.conn = sqlite3.connect(DB_PATH, check_same_thread=False)
_local.conn.row_factory = sqlite3.Row
return _local.conn
def init():
"""Initialize database schema."""
conn = get_conn()
conn.executescript("""
CREATE TABLE IF NOT EXISTS landmarks (
id INTEGER PRIMARY KEY,
class_label TEXT,
class_label_confidence REAL,
overall_confidence REAL,
lat REAL, lon REAL, alt REAL,
azimuth REAL,
width INTEGER, height INTEGER,
ts INTEGER,
forwarded INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS forward_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
payload TEXT,
created_at INTEGER DEFAULT (strftime('%s','now'))
);
CREATE INDEX IF NOT EXISTS idx_landmarks_ts ON landmarks(ts DESC);
CREATE INDEX IF NOT EXISTS idx_landmarks_forwarded ON landmarks(forwarded);
""")
conn.commit()
def insert_landmark(data):
"""Insert a landmark detection."""
conn = get_conn()
conn.execute("""
INSERT INTO landmarks (id, class_label, class_label_confidence, overall_confidence,
lat, lon, alt, azimuth, width, height, ts)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (data.get("id"), data["class_label"], data.get("class_label_confidence"),
data.get("overall_confidence"), data.get("lat"), data.get("lon"),
data.get("alt"), data.get("azimuth"), data.get("width"),
data.get("height"), data.get("ts")))
conn.commit()
return conn.execute("SELECT last_insert_rowid()").fetchone()[0]
def get_last_landmarks(n):
"""Get last N landmarks."""
conn = get_conn()
rows = conn.execute("SELECT * FROM landmarks ORDER BY ts DESC LIMIT ?", (n,)).fetchall()
return [dict(r) for r in rows]
def queue_forward(payload):
"""Queue a payload for later forwarding."""
conn = get_conn()
conn.execute("INSERT INTO forward_queue (payload) VALUES (?)", (json.dumps(payload),))
conn.commit()
def pop_queue(limit=50):
"""Pop items from forward queue."""
conn = get_conn()
rows = conn.execute("SELECT id, payload FROM forward_queue ORDER BY id LIMIT ?", (limit,)).fetchall()
if rows:
ids = [r["id"] for r in rows]
conn.execute(f"DELETE FROM forward_queue WHERE id IN ({','.join('?'*len(ids))})", ids)
conn.commit()
return [(r["id"], json.loads(r["payload"])) for r in rows]
def mark_forwarded(landmark_id):
"""Mark a landmark as forwarded."""
conn = get_conn()
conn.execute("UPDATE landmarks SET forwarded = 1 WHERE id = ?", (landmark_id,))
conn.commit()