Eines der Block-Tests-Unterverzeichnisse (siehe ../README.md
für die Übersicht). Ausgangspunkt war die Frage, ob (und wie) sich ein
eigener Probe-Block bauen lässt, der Daten bis zur Abholung puffert,
statt wie Probe Signal Vector nur den zuletzt empfangenen Block zu behalten
oder wie Vector Sink unbegrenzt und ohne dokumentierte Thread-Sicherheit zu
wachsen.
buffered_probeEin eigener gr.sync_block (reines Python, keine externe Bibliothek außer
NumPy). Sammelt alle eingehenden Float-Samples in einem
collections.deque(maxlen=...), abgesichert durch ein eigenes
threading.Lock. Zwei Methoden nach außen:
get_and_clear() — gibt alle gepufferten Samples zurück und leert dentotal_in_count() — Gesamtzahl je empfangener Samples, zur Erkennung vonmax_len erreicht und ältere Samples verworfen wurden).Anders als bei Vector Sink ist die Thread-Sicherheit hier explizit selbst
gebaut und getestet (siehe unten), nicht implizit vorausgesetzt.
buffered_probe.py — die eigentliche Blockimplementierung. Direkt alsbuffered_probe_demo.grc) — keinetest_buffered_probe.py — drei automatisierte Pass/Fail-Tests (siehe unten).test_buffered_probe_signal.py — Signaltreue-Analyse mit PNG-Ausgabebuffered_probe_demo.grc/.py — minimaler Flowgraph (Testton →buffered_probe als Embedded Python Block), belegt die GRC-Kompatibilität.python3 test_buffered_probe.py
python3 test_buffered_probe.py
=== 1. Luecken-Test ===
gesammelte Samples: 8192, Luecken: 0
OK - keine Luecken
=== 2. Nebenlaeufigkeits-Stresstest ===
Poll-Thread-Fehler: 0
total_in_count(): 196596, tatsaechlich abgeholt: 196596
Luecken trotz Dauerbelastung: 0
OK - keine Exceptions, keine Datenverluste, keine Luecken unter Last
=== 3. Overflow-Test (max_len-Grenze) ===
buffered_count() nach Ueberlastung: 1000 (max_len=1000)
OK - Puffer korrekt auf max_len begrenzt
=== 1. Luecken-Test ===
gesammelte Samples: 8192, Luecken: 0
OK - keine Luecken
=== 2. Nebenlaeufigkeits-Stresstest ===
Poll-Thread-Fehler: 0
total_in_count(): 196596, tatsaechlich abgeholt: 196596
Luecken trotz Dauerbelastung: 0
OK - keine Exceptions, keine Datenverluste, keine Luecken unter Last
=== 3. Overflow-Test (max_len-Grenze) ===
buffered_count() nach Ueberlastung: 1000 (max_len=1000)
OK - Puffer korrekt auf max_len begrenzt
Test 2 ist der entscheidende: Ein separater Python-Thread ruft get_and_clear()
ohne Pause auf, während GNU Radio parallel im Scheduler-Thread work()
aufruft — bewusst so aggressiv wie möglich, um mit dem Lock zu konkurrieren.
Über ~197.000 Samples hinweg: keine Exception, keine verlorenen oder
duplizierten Samples, keine Lücken. Das ist genau die Garantie, die bei
blocks.vector_sink_x nicht offiziell dokumentiert ist (siehe
SignalAnalyzer-Diskussion).
python3 test_buffered_probe_signal.py
python3 test_buffered_probe_signal.py
=== Signaltreue ===
verglichene Samples: 96000
RMS-Fehler: 0.00e+00
Maximaler Fehler: 0.00e+00
(Werte im Bereich der float32-Rechengenauigkeit = verlust- und lückenlos)
=== Signaltreue ===
verglichene Samples: 96000
RMS-Fehler: 0.00e+00
Maximaler Fehler: 0.00e+00
(Werte im Bereich der float32-Rechengenauigkeit = verlust- und lückenlos)

buffered_probe, sondern an GNU Radios interner Standard-Puffergröße (8192 Items): Throttle gibt Daten nicht gleichmäßig Sample für Sample weiter, sondern in Schüben, sobald ein interner Puffer voll ist. Separat mit einer einfachen Poll-Schleife nachgemessen: 20 Abfragen im 50-ms-Takt, Ergebnis abwechselnd 0, 0, 8192, 0, 0, 8191, ... — ein Muster, das sich exakt mit 96000 Samples ÷ 8192 ≈ 11,7 Schüben deckt. Für buffered_probe ist das unerheblich (es puffert ja gerade für diesen Fall), aber wichtig für die Erwartungshaltung, wie granular GNU-Radio-Daten tatsächlich beim Poller ankommen.grcc buffered_probe_demo.grc -o .
python3 -c "
from buffered_probe_demo import buffered_probe_demo
tb = buffered_probe_demo()
tb.start()
import time; time.sleep(0.5)
print(len(tb.epy_block_0.get_and_clear()), 'Samples abgeholt')
tb.stop(); tb.wait()
"
grcc buffered_probe_demo.grc -o .
python3 -c "
from buffered_probe_demo import buffered_probe_demo
tb = buffered_probe_demo()
tb.start()
import time; time.sleep(0.5)
print(len(tb.epy_block_0.get_and_clear()), 'Samples abgeholt')
tb.stop(); tb.wait()
"
Ergebnis: 8192 echte Kosinus-Testtonsamples abgeholt — der unveränderte
buffered_probe.py-Code läuft identisch in GRC wie im Unit-Test.
buffered_probe_demo.grc: Wird die Klasse im Embedded-Python-Block-Quellcode per from buffered_probe import buffered_probe as blk umbenannt importiert, generiert grcc trotzdem einen Aufruf der Form epy_block_0.buffered_probe(...) statt epy_block_0.blk(...) — GRC übernimmt offenbar den tatsächlichen Klassennamen (__name__), nicht den lokalen Alias. Lösung: ohne as blk importieren, dann stimmen Klassenname und generierter Aufruf überein.Probe Signal Vector |
Vector Sink |
buffered_probe (eigen) |
|
|---|---|---|---|
| Verhalten | behält nur den letzten Block | akkumuliert alles, unbegrenzt | akkumuliert alles, bis max_len |
| Thread-Sicherheit | offiziell dokumentiert | nicht dokumentiert | selbst gebaut, per Stresstest verifiziert |
| Speicherwachstum | konstant (fester vlen) |
unbegrenzt ohne manuelles reset() |
begrenzt, älteste Samples fallen bei Überlauf weg |
| Geeignet für | Live-Anzeige (aktueller Schnappschuss) | kurze, kontrollierte Aufzeichnungen | Auswertung über mehrere Blöcke hinweg, ohne Daten zu verlieren |
Weitere eigene Blöcke, die sich nach demselben Muster (Implementierung +
Unit-Test + GRC-Demo-Flowgraph) hier ergänzen ließen — Platz für zukünftige
Experimente jenseits von buffered_probe.