163 lines
4.3 KiB
Python
163 lines
4.3 KiB
Python
"""aiosqlite connection helper: WAL pragmas, schema bootstrap, migrations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import aiosqlite
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
SCHEMA_VERSION = 1
|
|
|
|
|
|
SCHEMA_SQL: tuple[str, ...] = (
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS vessel_static (
|
|
mmsi INTEGER PRIMARY KEY,
|
|
name TEXT,
|
|
callsign TEXT,
|
|
imo INTEGER,
|
|
ship_type INTEGER,
|
|
dim_a INTEGER,
|
|
dim_b INTEGER,
|
|
dim_c INTEGER,
|
|
dim_d INTEGER,
|
|
eta TEXT,
|
|
draught REAL,
|
|
destination TEXT,
|
|
updated_at REAL NOT NULL
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS ais_dynamic (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
mmsi INTEGER NOT NULL,
|
|
ts REAL NOT NULL,
|
|
lat REAL,
|
|
lon REAL,
|
|
sog REAL,
|
|
cog REAL,
|
|
heading REAL,
|
|
nav_status INTEGER,
|
|
rot REAL,
|
|
raw_msg_type INTEGER
|
|
)
|
|
""",
|
|
"CREATE INDEX IF NOT EXISTS ix_ais_dynamic_mmsi_ts ON ais_dynamic(mmsi, ts)",
|
|
"CREATE INDEX IF NOT EXISTS ix_ais_dynamic_ts ON ais_dynamic(ts)",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS gps_fix (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ts REAL NOT NULL,
|
|
lat REAL,
|
|
lon REAL,
|
|
sog REAL,
|
|
cog REAL,
|
|
alt REAL,
|
|
fix_quality INTEGER,
|
|
sats INTEGER,
|
|
hdop REAL
|
|
)
|
|
""",
|
|
"CREATE INDEX IF NOT EXISTS ix_gps_fix_ts ON gps_fix(ts)",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS raw_nmea (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ts REAL NOT NULL,
|
|
source TEXT NOT NULL,
|
|
kind TEXT NOT NULL,
|
|
line TEXT NOT NULL
|
|
)
|
|
""",
|
|
"CREATE INDEX IF NOT EXISTS ix_raw_nmea_ts ON raw_nmea(ts)",
|
|
"CREATE INDEX IF NOT EXISTS ix_raw_nmea_kind_ts ON raw_nmea(kind, ts)",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS base_station (
|
|
mmsi INTEGER PRIMARY KEY,
|
|
ts REAL NOT NULL,
|
|
lat REAL,
|
|
lon REAL,
|
|
epfd INTEGER,
|
|
updated_at REAL NOT NULL
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS aton (
|
|
mmsi INTEGER PRIMARY KEY,
|
|
ts REAL NOT NULL,
|
|
lat REAL,
|
|
lon REAL,
|
|
aton_type INTEGER,
|
|
name TEXT,
|
|
virtual INTEGER,
|
|
updated_at REAL NOT NULL
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS radio_telemetry (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ts REAL NOT NULL,
|
|
channel TEXT,
|
|
rssi REAL,
|
|
snr REAL,
|
|
slot INTEGER,
|
|
raw_json TEXT
|
|
)
|
|
""",
|
|
"CREATE INDEX IF NOT EXISTS ix_radio_telemetry_ts ON radio_telemetry(ts)",
|
|
)
|
|
|
|
|
|
async def connect(path: str) -> aiosqlite.Connection:
|
|
"""Open the SQLite DB with WAL pragmas and ensure the schema exists."""
|
|
p = Path(path)
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
conn = await aiosqlite.connect(path, timeout=5.0)
|
|
# Pragmas — WAL + sane defaults for embedded devices.
|
|
await conn.execute("PRAGMA journal_mode=WAL")
|
|
await conn.execute("PRAGMA synchronous=NORMAL")
|
|
await conn.execute("PRAGMA busy_timeout=5000")
|
|
await conn.execute("PRAGMA foreign_keys=ON")
|
|
await conn.execute("PRAGMA temp_store=MEMORY")
|
|
|
|
await _ensure_schema(conn)
|
|
return conn
|
|
|
|
|
|
async def _ensure_schema(conn: aiosqlite.Connection) -> None:
|
|
for stmt in SCHEMA_SQL:
|
|
await conn.execute(stmt)
|
|
await conn.execute(
|
|
"INSERT OR REPLACE INTO schema_meta(key, value) VALUES('version', ?)",
|
|
(str(SCHEMA_VERSION),),
|
|
)
|
|
await conn.commit()
|
|
|
|
|
|
async def close(conn: aiosqlite.Connection | None) -> None:
|
|
if conn is None:
|
|
return
|
|
try:
|
|
await conn.commit()
|
|
except Exception:
|
|
pass
|
|
try:
|
|
await conn.close()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
__all__ = ["connect", "close", "SCHEMA_VERSION", "SCHEMA_SQL"]
|