Files
aoa-commissionings/bg22_live_hybrid.py
T

182 lines
7.9 KiB
Python

"""Live-Messung mit dem BG22 und dem hybriden Algorithmus (aoa_music_komplett.py).
Verbindung, Tag-Suche und CTE-Empfang kommen aus bg22_AoA_v2.py (unveraendert).
Beide Dateien muessen im selben Ordner liegen.
1. Kalibrierung aufnehmen (Tag vor dem Board, ca. 60 s, Analyzer-Werte notieren):
python bg22_live_hybrid.py --nur-aufnehmen --iq-out kalibrierung_neu.jsonl
2. Messen (ohne --kalibrierung: Test ohne Kalibrierung):
python bg22_live_hybrid.py --kalibrierung kalibrierung_neu.jsonl \
--kal-az <Az> --kal-el <El> --ref-az <Az Analyzer> --ref-el <El Analyzer> --iq-out test_neu_a.jsonl
"""
import argparse
import importlib.util
import json
import os
import sys
import time as _time
from datetime import datetime, timezone
import numpy as np
hier = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, hier)
import aoa_music_komplett as ab # noqa: E402
# =============================================================================
# Kommandozeile
# =============================================================================
ap = argparse.ArgumentParser(add_help=False)
ap.add_argument("--kalibrierung")
ap.add_argument("--kal-az", type=float, default=0.0)
ap.add_argument("--kal-el", type=float, default=90.0)
ap.add_argument("--polar", default="VH", choices=["V", "VH"])
ap.add_argument("--abstand-mm", type=float, default=ab.ABSTAND_M * 1000)
ap.add_argument("--ref-az", type=float)
ap.add_argument("--ref-el", type=float)
ap.add_argument("--nur-aufnehmen", action="store_true")
ap.add_argument("--winkel-out", default="winkel_tabelle.json")
ap.add_argument("--iq-out", default="iq_samples.jsonl")
a, rest = ap.parse_known_args()
sys.argv = [sys.argv[0], "--iq-out", a.iq_out, "--angle-out", "winkel_tabelle_alt.json"] + rest
# =============================================================================
# Verbindung aus bg22_AoA_v2.py uebernehmen
# =============================================================================
spec = importlib.util.spec_from_file_location("bg22_AoA_v2", os.path.join(hier, "bg22_AoA_v2.py"))
bg22 = importlib.util.module_from_spec(spec)
sys.modules["bg22_AoA_v2"] = bg22
spec.loader.exec_module(bg22)
N_ANT = 16 if a.polar == "V" else 32
if a.polar == "VH":
bg22.RF_ORDER = tuple(ab.MUSTER_V) + tuple(ab.MUSTER_H)
with open(a.iq_out + ".meta.json", "w", encoding="utf-8") as f:
json.dump({"n_antennes": N_ANT, "polarisations": a.polar, "slot_us": bg22.SLOT_DURATION_US,
"motif": [bg22.CP_RF_VALUE] + list(bg22.RF_ORDER)}, f, indent=1)
bg22.SELBSTKAL_AKTIV = False
bg22.KALIBRIER_DATEI = "__nicht_verwendet__.json" # alte Kalibrierung abgeschaltet
bg22.process_packet = lambda paket, cfg: None # alte MUSIC-Berechnung abgeschaltet
open(a.iq_out, "w", encoding="utf-8").close() # IQ-Datei bei jedem Start leeren
# =============================================================================
# Schaetzer vorbereiten
# =============================================================================
schaetzer = None
if not a.nur_aufnehmen:
array = ab.brd4191a(a.polar, a.abstand_mm / 1000)
if a.kalibrierung:
if not os.path.exists(a.kalibrierung):
sys.exit(f"Kalibrierdatei nicht gefunden: {a.kalibrierung}")
kal = ab.Kalibrierung.aus_aufnahme(array, ab.lese_iq_datei(a.kalibrierung, N_ANT), a.kal_az, a.kal_el)
else:
kal = ab.Kalibrierung(array.n) # ohne Kalibrierung: alle Faktoren = 1
print("bg22_live: OHNE Kalibrierung (nur zum Test)", file=sys.stderr)
einst = ab.Einstellungen(polarisationen=(0,) if a.polar == "V" else (0, 1))
schaetzer = ab.MusicSchaetzer(array, kal, einst)
print(f"bg22_live: Hybrid (Beamformer + MUSIC), {N_ANT} Antennen, {a.abstand_mm:.1f} mm, "
f"Kalibrierung {a.kalibrierung or 'keine'} ({len(kal.je_kanal)} Kanaele)", file=sys.stderr)
else:
print(f"bg22_live: nur Aufnahme ({N_ANT} Antennen) nach {a.iq_out}", file=sys.stderr)
tabelle = []
# =============================================================================
# Verarbeitung je Paket
# =============================================================================
def paket_verarbeiten(i_werte, q_werte, kanal, t):
if schaetzer is None:
return
p = ab.paket_aus_iq(i_werte, q_werte, kanal, t, N_ANT, float(bg22.SLOT_DURATION_US))
s = schaetzer.verarbeiten(p) if p is not None else None
if s is None or not np.isfinite(s.az_grad):
return
vergleich = ""
if a.ref_az is not None and a.ref_el is not None:
d = ab.winkelabstand(ab.richtung(s.az_grad * ab.GRAD, s.el_grad * ab.GRAD),
ab.richtung(a.ref_az * ab.GRAD, a.ref_el * ab.GRAD)) / ab.GRAD
vergleich = f" | Abweichung {d:4.1f}"
print(f">>> Az {s.az_grad:+7.1f} +-{s.unsicherheit_az:4.1f} El {s.el_grad:5.1f} +-{s.unsicherheit_el:4.1f} "
f"Konfidenz {s.konfidenz:.2f}{vergleich} {', '.join(s.gruende)}", file=sys.stderr, flush=True)
tabelle.append({"zeit": datetime.fromtimestamp(t, timezone.utc).isoformat(),
"azimut": round(s.az_grad, 2), "elevation": round(s.el_grad, 2),
"unsicherheit_az": round(s.unsicherheit_az, 2), "unsicherheit_el": round(s.unsicherheit_el, 2),
"konfidenz": round(s.konfidenz, 3), "gruende": s.gruende})
if len(tabelle) % 5 == 0:
speichern()
def speichern():
with open(a.winkel_out, "w", encoding="utf-8") as f:
json.dump(tabelle, f, indent=1, ensure_ascii=False)
_original_speichern = bg22.save_iq_report
def save_iq_report(event):
_original_speichern(event) # IQ-Datei wie bisher schreiben
try:
i_werte, q_werte = bg22.signed_iq(event.samples)
paket_verarbeiten(i_werte, q_werte, int(event.channel), _time.time())
except Exception as fehler: # Rechenfehler darf die Messung nicht abbrechen
print(f"bg22_live: Paket uebersprungen ({fehler})", file=sys.stderr)
bg22.save_iq_report = save_iq_report
# =============================================================================
# Zusammenfassung beim Beenden (Strg+C)
# =============================================================================
def zusammenfassung():
T = tabelle[len(tabelle) // 10:] # Einschwingphase weglassen
if not T:
return
S = [ab.Schaetzung(0, t["azimut"], t["elevation"], 0, 0, t["konfidenz"], 0, 0, 0, 0) for t in T]
r = ab.ergebnis(S, a.ref_az, a.ref_el)
txt = f"\n===== ERGEBNIS: Azimut {r['az']:+.1f}, Elevation {r['el']:.1f}, Konfidenz {r['konfidenz']:.2f}"
if "abweichung" in r:
txt += (f"\n===== ANALYZER: Azimut {a.ref_az:+.1f}, Elevation {a.ref_el:.1f} -> "
f"ABWEICHUNG {r['abweichung']:.1f} Grad (Az {r['abw_az']:+.1f}, El {r['abw_el']:+.1f})")
print(txt + "\n", file=sys.stderr, flush=True)
# =============================================================================
# Start (5 s Bootzeit, bis zu 3 Versuche)
# =============================================================================
class _LaengereBootzeit:
def __getattr__(self, name):
return getattr(_time, name)
def sleep(self, sekunden):
_time.sleep(5.0 if sekunden == 2.0 else sekunden)
bg22.time = _LaengereBootzeit()
if __name__ == "__main__":
try:
from bgapi.bglib import CommandError
except ImportError:
CommandError = Exception
try:
for versuch in range(1, 4):
try:
bg22.main()
break
except CommandError as fehler:
print(f"bg22_live: Versuch {versuch}/3, BG22 antwortet nicht ({fehler}).", file=sys.stderr)
if versuch == 3:
sys.exit("Abbruch: AoA Analyzer trennen, Reset am BG22, neu starten.")
bg22.SHUTTING_DOWN = False
_time.sleep(3.0)
finally:
speichern()
print(f"bg22_live: {len(tabelle)} Schaetzungen in {a.winkel_out}", file=sys.stderr)
zusammenfassung()