#[cfg(feature = "std")]
mod inner {
extern crate std;
use std::vec::Vec;
use crate::engine::DsfbRfEngine;
use crate::platform::PlatformContext;
use crate::policy::PolicyDecision;
pub const EPISODE_RING_CAPACITY: usize = 256;
pub const CALIBRATION_WINDOW: usize = 100;
#[derive(Debug, Clone, Copy)]
pub struct GnuRadioIntegrationContract {
pub integration_mode: &'static str,
pub write_path_note: &'static str,
pub fail_safe_note: &'static str,
pub upstream_modified: bool,
}
pub const GNU_RADIO_CONTRACT: GnuRadioIntegrationContract =
GnuRadioIntegrationContract {
integration_mode: "gnuradio_sink_read_only_tap",
write_path_note:
"DsfbSinkB200 is a GNU Radio sink block (stream consumer). \
It reads CF32 samples; it has no output port connected to \
any upstream GNU Radio block. Disconnecting or removing it \
does not alter the flowgraph path from USRP Source to Demodulator.",
fail_safe_note:
"If DsfbSinkB200 is disconnected or crashes, the upstream \
flowgraph (USRP Source → Channel Filter → Demodulator) \
continues identically to its pre-DSFB state. No reconfiguration, \
no restart, no threshold adjustment is required.",
upstream_modified: false,
};
#[derive(Debug, Clone)]
pub struct TapHealthSummary {
pub samples_processed: u64,
pub episodes_emitted: u32,
pub calibration_locked: bool,
pub rho_locked: f32,
pub last_residual_norm: f32,
pub snr_db: f32,
pub current_policy: PolicyDecision,
}
#[derive(Debug, Clone)]
pub struct EpisodeAnnotation {
pub core_sample_start: u64,
pub core_sample_count: Option<u64>,
pub core_label: &'static str,
pub dsfb_motif: &'static str,
pub dsfb_dsa_score: f32,
pub dsfb_lyapunov_lambda: f32,
pub dsfb_policy: &'static str,
pub dsfb_platform_tag: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TapPhase {
Calibrating,
Operational,
}
pub struct DsfbSinkB200<const W: usize, const K: usize, const M: usize> {
engine: DsfbRfEngine<W, K, M>,
platform_tag: &'static str,
carrier_hz: f32,
sample_rate_hz: f32,
adc_bits: u8,
#[allow(dead_code)] snr_floor_db: f32,
phase: TapPhase,
calibration_buf: Vec<f32>,
samples_processed: u64,
episodes_emitted: u32,
rho_locked: f32,
last_residual_norm: f32,
current_policy: PolicyDecision,
contract: GnuRadioIntegrationContract,
}
impl<const W: usize, const K: usize, const M: usize>
DsfbSinkB200<W, K, M>
{
pub fn new(
platform_tag: &'static str,
carrier_hz: f32,
sample_rate_hz: f32,
adc_bits: u8,
snr_floor_db: f32,
) -> Self {
Self {
engine: DsfbRfEngine::<W, K, M>::new(0.0_f32, 2.0_f32),
platform_tag,
carrier_hz,
sample_rate_hz,
adc_bits,
snr_floor_db,
phase: TapPhase::Calibrating,
calibration_buf: Vec::with_capacity(CALIBRATION_WINDOW),
samples_processed: 0,
episodes_emitted: 0,
rho_locked: 0.0,
last_residual_norm: 0.0,
current_policy: PolicyDecision::Silent,
contract: GNU_RADIO_CONTRACT,
}
}
pub fn process(&mut self, samples: &[f32], snr_db: f32) -> TapHealthSummary {
debug_assert!(samples.len() <= u32::MAX as usize, "sample batch must fit u32 counters");
debug_assert!(snr_db.is_finite(), "snr_db must be finite");
for &s in samples {
self.samples_processed += 1;
match self.phase {
TapPhase::Calibrating => {
self.calibration_buf.push(s.abs());
if self.calibration_buf.len() >= CALIBRATION_WINDOW {
let mean: f32 = self.calibration_buf.iter().copied().sum::<f32>()
/ self.calibration_buf.len() as f32;
let var: f32 = self.calibration_buf
.iter()
.map(|&x| (x - mean) * (x - mean))
.sum::<f32>()
/ self.calibration_buf.len() as f32;
let std_dev = var.sqrt();
self.rho_locked = mean + 3.0 * std_dev;
self.engine = DsfbRfEngine::<W, K, M>::new(self.rho_locked, 2.0_f32);
self.phase = TapPhase::Operational;
}
}
TapPhase::Operational => {
let norm = s.abs();
self.last_residual_norm = norm;
let ctx = PlatformContext::with_snr(snr_db);
let result = self.engine.observe(norm, ctx);
self.current_policy = result.policy;
if matches!(
result.policy,
PolicyDecision::Review | PolicyDecision::Escalate
) {
self.episodes_emitted += 1;
}
}
}
}
TapHealthSummary {
samples_processed: self.samples_processed,
episodes_emitted: self.episodes_emitted,
calibration_locked: self.phase == TapPhase::Operational,
rho_locked: self.rho_locked,
last_residual_norm: self.last_residual_norm,
snr_db,
current_policy: self.current_policy,
}
}
pub fn contract(&self) -> &GnuRadioIntegrationContract {
&self.contract
}
pub fn platform_tag(&self) -> &'static str {
self.platform_tag
}
pub fn phase(&self) -> TapPhase {
self.phase
}
pub fn samples_processed(&self) -> u64 {
self.samples_processed
}
pub fn carrier_hz(&self) -> f32 {
self.carrier_hz
}
pub fn sample_rate_hz(&self) -> f32 {
self.sample_rate_hz
}
pub fn adc_bits(&self) -> u8 {
self.adc_bits
}
}
}
#[cfg(feature = "std")]
pub use inner::{
EPISODE_RING_CAPACITY, CALIBRATION_WINDOW,
GnuRadioIntegrationContract, GNU_RADIO_CONTRACT,
TapHealthSummary, EpisodeAnnotation, TapPhase, DsfbSinkB200,
};