Files
potato-mesh/data/nodes.py
2025-09-14 09:12:57 +02:00

104 lines
3.7 KiB
Python

import json, os, sqlite3, time, threading
from pathlib import Path
try: # meshtastic is optional for tests
from meshtastic.serial_interface import SerialInterface
from meshtastic.mesh_interface import MeshInterface
except ModuleNotFoundError: # pragma: no cover - imported lazily for hardware usage
SerialInterface = None # type: ignore
MeshInterface = None # type: ignore
DB = os.environ.get("MESH_DB", "nodes.db")
schema = Path(__file__).with_name("nodes.sql").read_text()
conn = sqlite3.connect(DB, check_same_thread=False)
conn.executescript(schema)
conn.commit()
def _get(obj, key, default=None):
"""Return value for key/attribute from dicts or objects."""
if isinstance(obj, dict):
return obj.get(key, default)
return getattr(obj, key, default)
def upsert_node(node_id, n):
user = _get(n, "user") or {}
met = _get(n, "deviceMetrics") or {}
pos = _get(n, "position") or {}
lh = _get(n, "lastHeard")
row = (
node_id,
_get(n, "num"),
_get(user, "shortName"),
_get(user, "longName"),
_get(user, "macaddr"),
_get(user, "hwModel") or _get(n, "hwModel"),
_get(user, "role"),
_get(user, "publicKey"),
_get(user, "isUnmessagable"),
_get(n, "isFavorite"),
_get(n, "hopsAway"),
_get(n, "snr"),
lh,
lh,
_get(met, "batteryLevel"),
_get(met, "voltage"),
_get(met, "channelUtilization"),
_get(met, "airUtilTx"),
_get(met, "uptimeSeconds"),
_get(pos, "time"),
_get(pos, "locationSource"),
_get(pos, "latitude"),
_get(pos, "longitude"),
_get(pos, "altitude"),
)
conn.execute(
"""
INSERT INTO nodes(node_id,num,short_name,long_name,macaddr,hw_model,role,public_key,is_unmessagable,is_favorite,
hops_away,snr,last_heard,first_heard,battery_level,voltage,channel_utilization,air_util_tx,uptime_seconds,
position_time,location_source,latitude,longitude,altitude)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ON CONFLICT(node_id) DO UPDATE SET
num=excluded.num, short_name=excluded.short_name, long_name=excluded.long_name, macaddr=excluded.macaddr,
hw_model=excluded.hw_model, role=excluded.role, public_key=excluded.public_key, is_unmessagable=excluded.is_unmessagable,
is_favorite=excluded.is_favorite, hops_away=excluded.hops_away, snr=excluded.snr, last_heard=excluded.last_heard,
battery_level=excluded.battery_level, voltage=excluded.voltage, channel_utilization=excluded.channel_utilization,
air_util_tx=excluded.air_util_tx, uptime_seconds=excluded.uptime_seconds, position_time=excluded.position_time,
location_source=excluded.location_source, latitude=excluded.latitude, longitude=excluded.longitude,
altitude=excluded.altitude
""",
row,
)
def load_nodes_from_file(path: str | Path):
"""Populate the database from a nodes.json file."""
nodes = json.loads(Path(path).read_text())
for node_id, node in nodes.items():
upsert_node(node_id, node)
conn.commit()
def main():
if SerialInterface is None:
raise RuntimeError("meshtastic library not installed")
iface = SerialInterface(
# or whatever serial interface it is
devPath="/dev/ttyACM0"
)
print("Nodes ingestor running. Ctrl+C to stop.")
while True:
try:
for node_id, n in (getattr(iface, "nodes", {}) or {}).items():
upsert_node(node_id, n)
conn.commit()
except Exception as e:
print("node snapshot error:", e)
time.sleep(30)
if __name__ == "__main__":
main()