# buffered_probe

Eines der Block-Tests-Unterverzeichnisse (siehe [../README.md](../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_probe`

Ein 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 den
  Puffer, threadsicher aufrufbar aus jedem Thread.
- `total_in_count()` — Gesamtzahl je empfangener Samples, zur Erkennung von
  Datenverlust (falls `max_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.

## Dateien

- `buffered_probe.py` — die eigentliche Blockimplementierung. Direkt als
  Python-Modul importierbar (für Tests) **und** unverändert als Quelle eines
  GRC Embedded Python Blocks nutzbar (siehe `buffered_probe_demo.grc`) — keine
  Code-Duplizierung zwischen Test und Flowgraph.
- `test_buffered_probe.py` — drei automatisierte Pass/Fail-Tests (siehe unten).
- `test_buffered_probe_signal.py` — Signaltreue-Analyse mit PNG-Ausgabe
  (siehe unten): Chirp-Testsignal, periodisches Poll-Intervall wie in der
  echten Anwendung, Original vs. rekonstruiertes Signal verglichen.
- `buffered_probe_demo.grc`/`.py` — minimaler Flowgraph (Testton →
  `buffered_probe` als Embedded Python Block), belegt die GRC-Kompatibilität.

## Tests

```bash
python3 test_buffered_probe.py
```

<div class="good box">
<strong>Verifiziert</strong> (GNU Radio 3.10.12.0, tatsächlich ausgeführt):

```
=== 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&nbsp;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](/projekte/ssb-usb-frontpanel-kivy/)).
</div>

## Signaltreue-Test mit PNG-Ausgabe

```bash
python3 test_buffered_probe_signal.py
```

<div class="good box">
<strong>Verifiziert</strong> (GNU Radio 3.10.12.0, tatsächlich ausgeführt) — ein
Chirp-Testsignal (300&nbsp;Hz → 3000&nbsp;Hz, 2&nbsp;s bei 48&nbsp;kHz) läuft durch
`Throttle` + `buffered_probe`, wird mit 20&nbsp;Hz gepollt (wie
`Clock.schedule_interval` im Kivy-Frontpanel-Projekt) und aus allen
`get_and_clear()`-Häppchen wieder zusammengesetzt:

```
=== Signaltreue ===
verglichene Samples: 96000
RMS-Fehler: 0.00e+00
Maximaler Fehler: 0.00e+00
(Werte im Bereich der float32-Rechengenauigkeit = verlust- und lückenlos)
```

![Original- und rekonstruiertes Chirp-Signal liegen deckungsgleich uebereinander, Fehlerkurve konstant bei Null](buffered_probe_signal_fidelity.png)
</div>

<div class="info box">Auffälligkeit bei den Poll-Größen: Erwartet wurden bei 48&nbsp;kHz und 20&nbsp;Hz-Polling im Schnitt ca. 2400 Samples pro Abfrage — tatsächlich liefern die meisten Polls **0** Samples, dafür alle paar Zyklen ein Schub von genau 8191–8192 Samples. Das liegt nicht an <code>buffered_probe</code>, sondern an GNU Radios interner Standard-Puffergröße (8192 Items): <code>Throttle</code> 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 <code>buffered_probe</code> 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.</div>

## GRC-Einbindung verifiziert

```bash
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.

<div class="info box">Kleine, real aufgetretene GRC-Falle beim Bau von <code>buffered_probe_demo.grc</code>: Wird die Klasse im Embedded-Python-Block-Quellcode per <code>from buffered_probe import buffered_probe as blk</code> umbenannt importiert, generiert <code>grcc</code> trotzdem einen Aufruf der Form <code>epy_block_0.buffered_probe(...)</code> statt <code>epy_block_0.blk(...)</code> — GRC übernimmt offenbar den <strong>tatsächlichen Klassennamen</strong> (<code>__name__</code>), nicht den lokalen Alias. Lösung: ohne <code>as blk</code> importieren, dann stimmen Klassenname und generierter Aufruf überein.</div>

## Vergleich mit den eingebauten Alternativen

<div class="table-scroll table-striped">

| | `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 |

</div>

## Nächste Schritte

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`.
