auto: update RTK rover/base + config (2026-05-01 17:00:43)

This commit is contained in:
2026-05-01 17:00:43 +02:00
parent 50be327305
commit ec63cadd60
3 changed files with 216 additions and 1 deletions
+25 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys, time, struct, argparse, threading, os, sqlite3, uuid, select, termios, tty import sys, time, struct, argparse, threading, os, sqlite3, uuid, select, termios, tty, json
from typing import Optional, Tuple, Dict from typing import Optional, Tuple, Dict
import serial import serial
from serial.serialutil import SerialException from serial.serialutil import SerialException
@@ -570,6 +570,8 @@ def main():
help="Start SQLite recording automatically on service start") help="Start SQLite recording automatically on service start")
ap.add_argument("--record-note", default="auto", ap.add_argument("--record-note", default="auto",
help="Note stored in sessions table when --auto-record is used") help="Note stored in sessions table when --auto-record is used")
ap.add_argument("--cmd-file", default="/home/naue/rtk/rtk_rover_cmd.json",
help="Command file for web UI start/stop recording")
ap.add_argument("--site-log", action="store_true", ap.add_argument("--site-log", action="store_true",
help="Enable construction-site FIX-only CSV/GeoJSON/status logging") help="Enable construction-site FIX-only CSV/GeoJSON/status logging")
@@ -667,6 +669,28 @@ def main():
recorder.start(note="manual") recorder.start(note="manual")
print(f"⏺️ Recording START (session={recorder.session_id})") print(f"⏺️ Recording START (session={recorder.session_id})")
# Web/API command file: {"cmd":"start"} or {"cmd":"stop"}
try:
if args.cmd_file and os.path.exists(args.cmd_file):
with open(args.cmd_file, "r", encoding="utf-8") as f:
cmd = json.load(f)
try:
os.remove(args.cmd_file)
except Exception:
pass
action = str(cmd.get("cmd", "")).lower()
note = str(cmd.get("note", "web"))
if action == "start" and not recorder.is_recording():
recorder.start(note=note)
print(f"⏺️ Web Recording START (session={recorder.session_id})", flush=True)
elif action == "stop" and recorder.is_recording():
recorder.stop()
print("⏹️ Web Recording STOP", flush=True)
except Exception as e:
print(f"WEB CMD ERROR: {e}", flush=True)
f = read_ubx_frame(zed, timeout=0.05) f = read_ubx_frame(zed, timeout=0.05)
pvt = None pvt = None
hpp = None hpp = None
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
import sqlite3, json, time, os
from flask import Flask, render_template, request, Response, redirect, url_for
DB_PATH = "/home/naue/rtk/rtk_rover.db"
CMD_FILE = "/home/naue/rtk/rtk_rover_cmd.json"
app = Flask(__name__)
def db():
con = sqlite3.connect(DB_PATH)
con.row_factory = sqlite3.Row
return con
def write_cmd(cmd):
tmp = CMD_FILE + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump({"cmd": cmd, "note": "web", "ts": time.time()}, f)
os.replace(tmp, CMD_FILE)
@app.route("/", methods=["GET"])
def index():
con = db()
sessions = con.execute("""
SELECT session_id,
datetime(start_ts,'unixepoch','localtime') AS start_time,
CASE WHEN end_ts IS NULL THEN '' ELSE datetime(end_ts,'unixepoch','localtime') END AS end_time,
note,
end_ts
FROM sessions
ORDER BY start_ts DESC
LIMIT 50
""").fetchall()
active_session = con.execute("""
SELECT session_id, datetime(start_ts,'unixepoch','localtime') AS start_time, note
FROM sessions
WHERE end_ts IS NULL
ORDER BY start_ts DESC
LIMIT 1
""").fetchone()
session_id = request.args.get("session_id") or (sessions[0]["session_id"] if sessions else "")
only_fix = request.args.get("only_fix", "0") == "1"
params = []
where = ""
if session_id:
where += " WHERE session_id=?"
params.append(session_id)
if only_fix:
where += " AND carr_soln=2" if where else " WHERE carr_soln=2"
points = con.execute(f"""
SELECT datetime(unix_ts,'unixepoch','localtime') AS t,
lat, lon, hmsl_corr_m, hacc_m, vacc_m, carr_soln, num_sats, source
FROM gnss_pvt
{where}
ORDER BY unix_ts DESC
LIMIT 200
""", params).fetchall()
count = con.execute(f"SELECT COUNT(*) AS n FROM gnss_pvt {where}", params).fetchone()["n"]
return render_template("index.html",
sessions=sessions,
session_id=session_id,
only_fix=only_fix,
points=points,
count=count,
active_session=active_session)
@app.route("/record/start", methods=["POST"])
def record_start():
write_cmd("start")
return redirect(url_for("index"))
@app.route("/record/stop", methods=["POST"])
def record_stop():
write_cmd("stop")
return redirect(url_for("index"))
@app.route("/export.csv")
def export_csv():
session_id = request.args.get("session_id")
only_fix = request.args.get("only_fix", "0") == "1"
params = []
where = ""
if session_id:
where += " WHERE session_id=?"
params.append(session_id)
if only_fix:
where += " AND carr_soln=2" if where else " WHERE carr_soln=2"
con = db()
rows = con.execute(f"""
SELECT datetime(unix_ts,'unixepoch','localtime') AS t,
lat, lon, hmsl_m, hmsl_corr_m, hacc_m, vacc_m,
fix_type, carr_soln, diff_soln, gnss_ok, num_sats, source
FROM gnss_pvt
{where}
ORDER BY unix_ts ASC
""", params).fetchall()
def generate():
yield "time,lat,lon,hmsl_m,hmsl_corr_m,hacc_m,vacc_m,fix_type,carr_soln,diff_soln,gnss_ok,num_sats,source\n"
for r in rows:
yield ",".join(str(r[k]) if r[k] is not None else "" for k in r.keys()) + "\n"
return Response(generate(), mimetype="text/csv",
headers={"Content-Disposition": "attachment; filename=rtk_export.csv"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
+76
View File
@@ -0,0 +1,76 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>RTK Rover DB</title>
<style>
body { font-family: sans-serif; margin: 2rem; background: #f6f6f6; }
h1 { margin-top: 0; }
.card { background: white; padding: 1rem; border-radius: 10px; margin-bottom: 1rem; box-shadow: 0 1px 5px #ccc; }
table { width: 100%; border-collapse: collapse; background: white; }
th, td { padding: .45rem; border-bottom: 1px solid #ddd; font-size: .9rem; }
th { text-align: left; background: #eee; }
.fix { color: green; font-weight: bold; }
.float { color: orange; font-weight: bold; }
select, button, input { padding: .4rem; }
</style>
</head>
<body>
<h1>RTK Rover Datenbank</h1>
<div class="card">
<form method="get">
<label>Session:</label>
<select name="session_id">
{% for s in sessions %}
<option value="{{s.session_id}}" {% if s.session_id == session_id %}selected{% endif %}>
{{s.start_time}} | {{s.note}} | {{s.session_id[:8]}}
</option>
{% endfor %}
</select>
<label>
<input type="checkbox" name="only_fix" value="1" {% if only_fix %}checked{% endif %}>
nur RTK FIX
</label>
<button type="submit">Anzeigen</button>
<a href="/export.csv?session_id={{session_id}}{% if only_fix %}&only_fix=1{% endif %}">CSV exportieren</a>
</form>
</div>
<div class="card">
<b>Punkte:</b> {{count}} |
<b>Anzeige:</b> letzte {{points|length}}
</div>
<table>
<tr>
<th>Zeit</th>
<th>Lat</th>
<th>Lon</th>
<th>hMSL corr</th>
<th>hAcc</th>
<th>vAcc</th>
<th>RTK</th>
<th>Sats</th>
<th>Quelle</th>
</tr>
{% for p in points %}
<tr>
<td>{{p.t}}</td>
<td>{{"%.8f"|format(p.lat)}}</td>
<td>{{"%.8f"|format(p.lon)}}</td>
<td>{{"%.3f"|format(p.hmsl_corr_m)}}</td>
<td>{{"%.3f"|format(p.hacc_m)}}</td>
<td>{{"%.3f"|format(p.vacc_m)}}</td>
<td class="{% if p.carr_soln == 2 %}fix{% elif p.carr_soln == 1 %}float{% endif %}">
{% if p.carr_soln == 2 %}FIX{% elif p.carr_soln == 1 %}FLOAT{% else %}—{% endif %}
</td>
<td>{{p.num_sats}}</td>
<td>{{p.source}}</td>
</tr>
{% endfor %}
</table>
</body>
</html>