File burst_capture_core.h¶
FileList > burst_capture > burst_capture_core.h
Go to the source code of this file
BurstCapture — acquisition's output turned into aligned bursts. More...
#include "clib_common.h"#include "jm_perf.h"#include "buffer/buffer.h"#include "dp_state.h"#include "burst_acq/burst_acq_core.h"#include "acq/acq_core.h"#include "corr2d/corr2d_core.h"#include "fft2d/fft2d_core.h"#include "fft/fft_core.h"#include "detection/detection_core.h"#include "pn/pn_core.h"
Classes¶
| Type | Name |
|---|---|
| struct | burst_capture_detection_t One raw detection, as the search reported it. |
| struct | burst_capture_event_t One captured burst's event, as events() hands it back. |
| struct | burst_capture_pending_t One detection between acquisition and emission. |
| struct | burst_capture_state_t BurstCapture state. |
Public Functions¶
| Type | Name |
|---|---|
| int | burst_capture_configure_search_raw (burst_capture_state_t * state, size_t doppler_bins, size_t n_noncoh) Pin the embedded acquisition's search grid directly. |
| burst_capture_state_t * | burst_capture_create (const uint8_t * acq_code, size_t acq_code_len, size_t burst_len, size_t reps, size_t spc, double chip_rate, double cn0_dbhz, double doppler_uncertainty, double pfa, double pd, int noise_mode) Create a burst capture: acquisition, refine and retention behind one push(). |
| burst_capture_state_t * | burst_capture_create_backed (const char * path, const uint8_t * acq_code, size_t acq_code_len, size_t burst_len, size_t reps, size_t spc, double chip_rate, double cn0_dbhz, double doppler_uncertainty, double pfa, double pd, int noise_mode) Create a capture whose look-back lives in a FILE. |
| void | burst_capture_destroy (burst_capture_state_t * state) Release a capture and everything it owns. NULL-safe. |
| size_t | burst_capture_detections (burst_capture_state_t * state, size_t n, burst_capture_detection_t * out, size_t max_out) Every hit the search made in the last push(), unfiltered. |
| size_t | burst_capture_detections_max_out (burst_capture_state_t * state, size_t n) Raw detections available from the last push(). n is ignored. |
| const burst_capture_event_t * | burst_capture_event_at (const burst_capture_state_t * state, size_t i) Borrow event i of the last push(), or NULL if out of range. |
| size_t | burst_capture_events (burst_capture_state_t * state, size_t n, burst_capture_event_t * out, size_t max_out) The event record for each burst the last push() returned. |
| size_t | burst_capture_events_max_out (burst_capture_state_t * state, size_t n) Records available from the last push(). n is ignored. |
| double | burst_capture_get_cn0_dbhz_est (const burst_capture_state_t * state) |
| size_t | burst_capture_get_code_bins (const burst_capture_state_t * state) Code-phase hypotheses per Doppler row. |
| size_t | burst_capture_get_doppler_bins (const burst_capture_state_t * state) Doppler hypotheses searched (the coherent depth). |
| double | burst_capture_get_doppler_hz_est (const burst_capture_state_t * state) |
| double | burst_capture_get_doppler_res_hz (const burst_capture_state_t * state) |
| double | burst_capture_get_doppler_span_hz (const burst_capture_state_t * state) Unambiguous Doppler half-range, Hz (+/- this). |
| uint64_t | burst_capture_get_dropped (const burst_capture_state_t * state) |
| double | burst_capture_get_eta (const burst_capture_state_t * state) Coherent detection gate; in force when n_noncoh == 1 . |
| double | burst_capture_get_eta_nc (const burst_capture_state_t * state) Non-coherent gate; in force when n_noncoh > 1 (the usual case). |
| size_t | burst_capture_get_min_gap (const burst_capture_state_t * state) Dead air a caller must leave between bursts, edge to edge. |
| uint64_t | burst_capture_get_n_bursts (const burst_capture_state_t * state) |
| size_t | burst_capture_get_n_noncoh (const burst_capture_state_t * state) Non-coherent looks combined per decision. |
| double | burst_capture_get_pd_predicted (const burst_capture_state_t * state) Detection probability the sized grid actually predicts. |
| size_t | burst_capture_get_pending (const burst_capture_state_t * state) |
| uint64_t | burst_capture_get_preamble_start (const burst_capture_state_t * state) |
| double | burst_capture_get_refine_margin (const burst_capture_state_t * state) |
| void | burst_capture_get_state (const burst_capture_state_t * state, void * blob) Serialize into blob , which must be state_bytes() long. |
| double | burst_capture_get_straddle_loss (const burst_capture_state_t * state) Correlation kept, worst case, by a burst landing between bins. |
| size_t | burst_capture_push (burst_capture_state_t * state, const float _Complex * x, size_t x_len, float _Complex * out, size_t max_out) Stream samples; get back every burst whose window has arrived. |
| size_t | burst_capture_push_max_out (burst_capture_state_t * state, size_t x_len) Upper bound on samples push() can return for x_len input. |
| size_t | burst_capture_ready (const burst_capture_state_t * state) Windows the last push() completed. |
| int | burst_capture_release (burst_capture_state_t * state, size_t i) Give back the span that window i of the last push() claimed. |
| void | burst_capture_reset (burst_capture_state_t * state) Return to the searching state. |
| int | burst_capture_set_state (burst_capture_state_t * state, const void * blob) Restore from blob . |
| size_t | burst_capture_state_bytes (const burst_capture_state_t * state) Bytes one blob occupies: a pure function of CONFIGURATION. |
| const float _Complex * | burst_capture_window (const burst_capture_state_t * state, size_t i) Borrow window i of the last push(), or NULL if out of range. |
Macros¶
| Type | Name |
|---|---|
| define | BURST_CAPTURE_HITS 16uDetections collected from acquisition per batch. |
| define | BURST_CAPTURE_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('B', 'C', 'A', 'P')State blob magic — a wrong blob is rejected, not reinterpreted. |
| define | BURST_CAPTURE_STATE_VERSION 2uState blob layout version. |
Detailed Description¶
Between a detector and whatever consumes a burst there is a stage nobody owned: acquisition reports an END anchor and a code phase that is a lag MODULO one code period, so it fixes the alignment WITHIN a repetition and never says WHICH one. A burst has a frame that begins in one specific repetition, so somebody has to resolve the period and reach BACK to a start that has already gone past. This object is that somebody.
It searches, refines, retains, and emits the burst's SAMPLES. It stops there — demodulating, recording, or shipping a window elsewhere is the caller's business. DsssBurstReceiver is this plus BurstDemod.
It OWNS its acquisition engine rather than accepting someone else's results, and that is a correctness choice rather than a convenience one: acq_result_t::samples_consumed is stream-absolute only for an engine fed continuously and never reset, in the caller's own sample coordinates. An object taking foreign results would have to require that and could not check it — and a violated assumption is not a slightly wrong window, it is refine searching the wrong repetition, which returns noise rather than a degraded frame. push() defining the coordinate system makes the invariant internal. See docs/design/dsss-burst-receiver.md §11.
Lifecycle: create, then push() repeatedly, then destroy. There is no step()/steps(): a burst is a frame, not a sample.
uint8_t code[31];
for (size_t i = 0; i < 31; i++) code[i] = (uint8_t)(i & 1u);
burst_capture_state_t *cap = burst_capture_create (
code, 31, 4096, 4, 4, 1.0e6, 55.0, 0.0, 1e-3, 0.9, 0);
float _Complex x[2048] = { 0 };
float _Complex win[4096];
size_t n = burst_capture_push (cap, x, 2048, win, 4096);
// n is a multiple of burst_len: burst i starts at i*burst_len
burst_capture_destroy (cap);
Public Functions Documentation¶
function burst_capture_configure_search_raw¶
Pin the embedded acquisition's search grid directly.
int burst_capture_configure_search_raw (
burst_capture_state_t * state,
size_t doppler_bins,
size_t n_noncoh
)
The escape hatch for a caller who wants a specific (doppler_bins, n_noncoh). Forwards to the engine, with one refusal of this object's own: a grid whose anchor can lag the preamble by more than refine reaches n_noncoh * doppler_bins code periods against k_lo is rejected rather than accepted and silently mis-refined. Acquisition stamps a hit at the end of the LAST accumulated look, so every look past the one holding the preamble moves the anchor a whole frame later; a burst has one frame of preamble, so n_noncoh = 1 is the grid a capture wants and the sizer now always picks (doppler#1181).
Returns:
DP_OK, or DP_ERR_INVALID if this object or the engine refused the grid.
>>> import numpy as np
>>> from doppler.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> cap.configure_search_raw(4, 1) # 4 Doppler bins, coherent only
function burst_capture_create¶
Create a burst capture: acquisition, refine and retention behind one push().
burst_capture_state_t * burst_capture_create (
const uint8_t * acq_code,
size_t acq_code_len,
size_t burst_len,
size_t reps,
size_t spc,
double chip_rate,
double cn0_dbhz,
double doppler_uncertainty,
double pfa,
double pd,
int noise_mode
)
Give it the preamble code and the geometry, say how long a burst is, and stream samples in. It searches blindly, recovers the exact preamble start, and hands back the burst's samples once they have all arrived.
The look-back buffer is NOT a parameter. Its span is derived from the geometry here (detection lag + refine search + the burst itself), because every term is already known and a caller asked to size a history buffer is a caller handed a way to lose bursts silently.
Parameters:
acq_codePreamble PN chips (0/1), lengthacq_code_len.acq_code_lenPreamble code length, chips.burst_lenSamples in one burst what gets captured.repsPreamble code repetitions.spcSamples per chip.chip_rateChip rate, Hz.cn0_dbhzC/N0 the search is sized for, dB-Hz.doppler_uncertaintyDoppler search half-range, Hz (0 = native).pfaTarget false-alarm probability, in (0, 1).pdTarget detection probability, in (0, 1).noise_modeCFAR reference: 0=mean, 1=median, 2=min, 3=max.
Returns:
Heap state, or NULL if any parameter is out of range.
>>> import numpy as np
>>> from doppler.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> cap.burst_len
512
>>> cap.retain_span == cap.refine_span + cap.burst_len
True
function burst_capture_create_backed¶
Create a capture whose look-back lives in a FILE.
burst_capture_state_t * burst_capture_create_backed (
const char * path,
const uint8_t * acq_code,
size_t acq_code_len,
size_t burst_len,
size_t reps,
size_t spc,
double chip_rate,
double cn0_dbhz,
double doppler_uncertainty,
double pfa,
double pd,
int noise_mode
)
Same object, same behaviour, one difference in where the history ring's pages come from: they are a MAP_SHARED mapping of path, so the ring's samples ARE the file's contents. There is no copy and no separate flush path — the kernel writes the pages back, and get_state() forces the point so a checkpoint and its history agree.
Two things follow, and they are the reason to reach for this constructor:
- The blob stops carrying the look-back. For an in-RAM capture the retained history IS the blob (measured: 2.57 MB at a 1029-symbol frame, 16.68 MB at 8029). Backed,
state_bytes()is a few hundred bytes plus the acquisition child, because the samples are already durable and the blob only has to name where in the ring they sit. - The history outlives the process. Point a new capture at the same path and the samples are there; restore the blob and it reaches back across the restart into a burst that began before it.
The file is created if absent and truncated to the ring's byte size, which zeroes it. An existing file of exactly that size is adopted as it stands. Because the capacity rounds up to a page, that size is capacity * sizeof(float _Complex) — do not compute it from burst_len.
A blob from a backed capture does NOT restore into an in-RAM one, or the reverse: state_bytes() differs, so jm's length check rejects it. That is the intent — they are different configurations, and silently accepting one for the other would resume a capture whose history was somewhere else.
Parameters:
pathFile to back the ring with; not NULL and not empty.acq_codePreamble PN chips (0/1), lengthacq_code_len.acq_code_lenPreamble code length, chips.burst_lenSamples in one burst what gets captured.repsPreamble code repetitions.spcSamples per chip.chip_rateChip rate, Hz.cn0_dbhzC/N0 the search is sized for, dB-Hz.doppler_uncertaintyDoppler search half-range, Hz (0 = native).pfaTarget false-alarm probability, in (0, 1).pdTarget detection probability, in (0, 1).noise_modeCFAR reference: 0=mean, 1=median, 2=min, 3=max.
Returns:
Heap state, or NULL if a parameter is out of range or the file could not be opened, sized or mapped.
>>> import numpy as np, tempfile, os
>>> from doppler.dsss import BurstCapture, PersistentBurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> path = os.path.join(tempfile.mkdtemp(), "ring.cf32")
>>> cap = PersistentBurstCapture(path, code, burst_len=512,
... reps=4, spc=2)
>>> ram = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> _ = cap.push(np.zeros(4096, dtype=np.complex64))
>>> # the look-back is in the file, so the blob stops carrying it
>>> ram.state_bytes() - cap.state_bytes() == ram.retain_span * 8
True
>>> os.path.getsize(path) > 0
True
function burst_capture_destroy¶
Release a capture and everything it owns. NULL-safe.
function burst_capture_detections¶
Every hit the search made in the last push(), unfiltered.
size_t burst_capture_detections (
burst_capture_state_t * state,
size_t n,
burst_capture_detection_t * out,
size_t max_out
)
BEFORE the claim rule and the suppression window: several rows can name one preamble, and a row can be a false alarm. That is the point this is what acquisition FOUND, and events() is what survived. Valid until the next push(), reset() or set_state().
>>> import numpy as np
>>> from doppler.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> _ = cap.push(np.zeros(4096, dtype=np.complex64))
>>> # what the search found, against what became a burst
>>> len(cap.detections()) >= len(cap.events())
True
function burst_capture_detections_max_out¶
Raw detections available from the last push(). n is ignored.
function burst_capture_event_at¶
Borrow event i of the last push(), or NULL if out of range.
const burst_capture_event_t * burst_capture_event_at (
const burst_capture_state_t * state,
size_t i
)
function burst_capture_events¶
The event record for each burst the last push() returned.
size_t burst_capture_events (
burst_capture_state_t * state,
size_t n,
burst_capture_event_t * out,
size_t max_out
)
Row i describes the window at i*burst_len. Valid until the next push(), reset() or set_state().
>>> import numpy as np
>>> from doppler.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> win = cap.push(np.zeros(4096, dtype=np.complex64))
>>> len(cap.events()) == win.size // cap.burst_len
True
function burst_capture_events_max_out¶
Records available from the last push(). n is ignored.
function burst_capture_get_cn0_dbhz_est¶
function burst_capture_get_code_bins¶
Code-phase hypotheses per Doppler row.
function burst_capture_get_doppler_bins¶
Doppler hypotheses searched (the coherent depth).
function burst_capture_get_doppler_hz_est¶
function burst_capture_get_doppler_res_hz¶
function burst_capture_get_doppler_span_hz¶
Unambiguous Doppler half-range, Hz (+/- this).
function burst_capture_get_dropped¶
function burst_capture_get_eta¶
Coherent detection gate; in force when n_noncoh == 1 .
function burst_capture_get_eta_nc¶
Non-coherent gate; in force when n_noncoh > 1 (the usual case).
function burst_capture_get_min_gap¶
Dead air a caller must leave between bursts, edge to edge.
function burst_capture_get_n_bursts¶
function burst_capture_get_n_noncoh¶
Non-coherent looks combined per decision.
function burst_capture_get_pd_predicted¶
Detection probability the sized grid actually predicts.
function burst_capture_get_pending¶
function burst_capture_get_preamble_start¶
function burst_capture_get_refine_margin¶
function burst_capture_get_state¶
Serialize into blob , which must be state_bytes() long.
function burst_capture_get_straddle_loss¶
Correlation kept, worst case, by a burst landing between bins.
function burst_capture_push¶
Stream samples; get back every burst whose window has arrived.
size_t burst_capture_push (
burst_capture_state_t * state,
const float _Complex * x,
size_t x_len,
float _Complex * out,
size_t max_out
)
Windows are concatenated: burst i occupies burst_len samples starting at i*burst_len, and events() returns the matching record for each. Every sample of x is consumed. An empty return is normal it means no burst completed in this call.
Parameters:
stateCapture.xInput samples,x_lenlong.x_lenSamples inx.outWritten with the completed windows; may be NULL to drop.max_outCapacity ofout, in samples.
Returns:
Samples written always a multiple of burst_len.
>>> import numpy as np
>>> from doppler.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> win = cap.push(np.zeros(4096, dtype=np.complex64))
>>> win.size % cap.burst_len # whole windows, never a partial
0
>>> win.size # silence, so no burst completed
0
function burst_capture_push_max_out¶
Upper bound on samples push() can return for x_len input.
Distinct bursts cannot overlap, so x_len samples complete at most x_len/burst_len + 1 of them, plus whatever is already queued.
function burst_capture_ready¶
Windows the last push() completed.
The C consumer's face, and the reason a composing object pays no second copy: burst_capture_window() borrows straight out of the scratch that push() filled.
function burst_capture_release¶
Give back the span that window i of the last push() claimed.
An emitted window owns its whole span: a detection inside it is the payload firing against the acquisition code, not a new burst, so it is HELD rather than reported. Whether the window WAS a burst is a verdict this object cannot reach it stops at samples; error detection, whatever form the frame gives it, is the consumer's so a consumer that knows better calls this for that window, and the held detections are searched again on the next push(). Unreleased, they are dropped when the next push() begins, which is exactly the behaviour a consumer with no verdict always had.
What it prevents (doppler#1181): a spurious window ending just after a real burst begins used to swallow that burst's first detections the receiver's own design says only a DECODED burst may own a span (§10.3, doppler#1004), and the capture underneath had been owning it on emission.
Must be called BEFORE the next push(): i indexes THIS push's windows.
Returns:
DP_OK, or DP_ERR_INVALID if i is not a window of the last push().
>>> import numpy as np
>>> from doppler.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> _ = cap.push(np.zeros(4096, dtype=np.complex64))
>>> cap.release(0) # no window 0 in a quiet push
Traceback (most recent call last):
...
ValueError: release failed (rc=-4)
function burst_capture_reset¶
Return to the searching state.
Resets the embedded acquisition, rewinds the history ring, clears every queued detection and every read-back. Construction parameters are untouched; dropped deliberately survives, because a lost burst stays lost.
>>> import numpy as np
>>> from doppler.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> cap.push(np.zeros(4096, dtype=np.complex64)).size
0
>>> cap.reset()
>>> cap.pending
0
function burst_capture_set_state¶
Restore from blob .
Returns:
DP_OK or DP_ERR_INVALID.
A wrong-object, wrong-version, wrong-size or foreign-endian blob is refused, never reinterpreted; so is a blob from the other flavour (a backed and an in-RAM capture have different state_bytes()). A backed capture restores POSITIONS only the samples are the file's so it also refuses a blob whose retained span the file cannot hold: a file create() made fresh that this capture has not written that far into, or a span the ring has since wrapped past (more than the ring's capacity pushed since the checkpoint). A capture restoring a checkpoint it took itself is the normal case and is accepted (doppler#1190): set_state(blob) -> push(chunk) -> get_state() per call is a service shape this object supports, on both flavours.
function burst_capture_state_bytes¶
Bytes one blob occupies: a pure function of CONFIGURATION.
function burst_capture_window¶
Borrow window i of the last push(), or NULL if out of range.
Contiguous, burst_len samples, valid until the next push(), reset() or set_state(). The caller must not free it.
Macro Definition Documentation¶
define BURST_CAPTURE_HITS¶
Detections collected from acquisition per batch.
A BATCHING parameter, never a correctness one: push() loops until acq has absorbed the whole chunk, so a smaller array means more iterations and nothing else. Growing it to "be safe" would hide the fact that acq_push() stops once its result array is full and abandons the rest of its input.
define BURST_CAPTURE_STATE_MAGIC¶
State blob magic — a wrong blob is rejected, not reinterpreted.
define BURST_CAPTURE_STATE_VERSION¶
State blob layout version.
The documentation for this class was generated from the following file native/inc/burst_capture/burst_capture_core.h