Kivy-Frontpanel — Frequenzanzeige + matplotlib-Spektrum/Zeitsignal

Nachbau von Frequenzregler/-anzeige und Spektrumdarstellung des
USB-Sprachempfängers in Kivy — siehe
homepage/src/projekte/ssb-usb-frontpanel/index.md für den Hintergrund
(native GRC-Widgets vs. Alternativen).

Trennt Signalverarbeitung und Darstellung bewusst:

Ausführen

pip install kivy kivy_garden.matplotlib
python3 main.py
pip install kivy kivy_garden.matplotlib
python3 main.py

Voraussetzung: ../ssb-usb-sender/ssb_usb_144300_testsignal.iq muss
existieren (mit ssb-usb-sender/ssb_usb_sender_testsignal.grc erzeugt).

Datenformat der Probe-Blöcke

Jeder Probe Signal Vector-Block liefert bei .level() immer einen
kompletten Vektor auf einmal
, nicht sequenziell:

Frequenzsteuerung

carrier_freq ist eine normale GRC-Variable, die als center_freq des
Freq Xlating FIR Filter verwendet wird. GRC generiert dafür automatisch
set_carrier_freq(), das intern freq_xlating_fir_filter_xxx_0.set_center_freq()
aufruft — ein Kivy-Button kann diese Methode direkt aufrufen, um den
Empfänger live umzustimmen (siehe main.py:_step_frequency).

Bekannte Einschränkung: nur ein Widget pro Frame aktualisieren

kivy_garden.matplotlib hat einen Race-Condition-Bug: Zeichnen mehrere
FigureCanvasKivyAgg-Widgets ihre Textur im selben Kivy-Frame neu, kann
die GPU-Textur eines Widgets die eines anderen überschreiben — nur das
zuletzt gezeichnete Widget bleibt sichtbar, die anderen bleiben schwarz.
Reale, wiederholt reproduzierte Beobachtung (GNU Radio 3.10.12.0, Kivy 2.3.1,
matplotlib 3.10.1, macOS/AMD-GPU über OpenGL 2.1): Werden spectrum_orig,
spectrum_demod und time_widget synchron in einem _update()-Aufruf
aktualisiert, rendert zuverlässig nur das zuletzt gesetzte Widget.

Workaround in main.py: _update() aktualisiert reihum immer nur genau
eines der drei Widgets pro Tick (Round-Robin über eine Liste aus
(Widget, Probe)-Paaren), nie alle drei gleichzeitig. Damit bleibt jedes
Widget bei jedem dritten Tick (bei UPDATE_HZ=20 also ~6,7-mal pro Sekunde)
aktuell — für eine Live-Anzeige ausreichend, und über mehrere Minuten Laufzeit
stabil getestet, ohne dass die Race Condition erneut auftritt.

Ergebnis

GNU Radio 3.10.12.0, Kivy 2.3.1, matplotlib 3.10.1,
per Widget.export_to_png() geprüft — screenshot.png zeigt die
laufende App bei 500 kHz mit allen drei unabhängigen Widgets: Original-Spektrum
mit deutlichem Träger, demoduliertes Spektrum und sauberes Zeitsignal.

Nächste Schritte

Weitere QT-Elemente aus dem Empfänger, die sich nach demselben Muster
nachbauen ließen: Lautstärke-/Squelch-Regler (Kivy Slider), LED-Indikator
für Squelch-Status, S-Meter (analog.probe_avg_mag_sqrd_f statt
probe_signal_vf, aggregierter Einzelwert statt eines Vektors).