Compare commits

..

No commits in common. "main" and "pre-liberation-v1" have entirely different histories.

6 changed files with 32 additions and 101 deletions

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Sulkta Coop
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -41,7 +41,7 @@ Config file: `/data/adacam/config.json`
```json
{
"device_id": "auto-generated UUID",
"adamaps_key": "<your-adamaps-ingest-key>",
"adamaps_key": "***REMOVED***",
"adamaps_api": "https://api.adamaps.org",
"ap_interface": "wlp1s0f0",
"tunnel_host": "",

View file

@ -112,7 +112,6 @@ def create_app():
# ── DEBUG ENDPOINTS ────────────────────────────────────────────────────
@app.route('/api/1/debug/redis-keys')
@require_auth
def redis_keys():
"""Debug endpoint — list Redis keys for GPS/IMU troubleshooting."""
try:

View file

@ -8,7 +8,7 @@ FIRMWARE_VERSION = "adacam-1.0.0"
_defaults = {
"device_id": None,
"adamaps_key": "",
"adamaps_key": "***REMOVED***",
"adamaps_api": "https://api.adamaps.org",
"ap_interface": "wlp1s0f0",
"tunnel_host": "",

View file

@ -1,87 +1,40 @@
"""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
"""
"""Redis client for GPS and IMU data."""
import json
import os
import sqlite3
import time
import redis
ODC_DB = '/data/recording/odc-api.db'
GNSS_REDIS_KEYS = ['GNSSFusion30Hz', 'GnssData', 'GnssPvt', 'GnssPosition']
_redis_client = None
_client = None
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_client():
"""Get Redis connection."""
global _client
if _client is None:
_client = redis.Redis(host="localhost", port=6379, decode_responses=True)
return _client
def get_latest_gnss():
"""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
"""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
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

View file

@ -1,3 +1,3 @@
flask>=2.3.2
redis>=5.0
requests>=2.32.0
flask>=2.0
redis>=4.0
requests>=2.25