#[cfg(feature = "std")]
fn main() {
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::env;
use std::process;
extern crate serde_json;
use serde_json::Value;
use dsfb_rf::pipeline::{
RfObservation, RegimeTransitionEvent,
run_stage_iii, HEALTHY_WINDOW_SIZE,
};
use dsfb_rf::uncertainty::{UncertaintyConfig, TypeBContributor, compute_budget};
use dsfb_rf::stationarity::{verify_wss, StationarityConfig};
use dsfb_rf::quantization_noise_std;
extern crate std;
use std::println;
println!("══════════════════════════════════════════════════════════════");
println!(" DSFB-RF │ IQEngine Community Captures │ Hardware Diversity");
println!(" Real multi-SDR recordings — RTL-SDR → USRP X310 — iqengine.org");
println!("══════════════════════════════════════════════════════════════");
println!();
let args: Vec<String> = env::args().collect();
let mut input_specs: Vec<(String, String)> = Vec::new(); let mut meta_path: Option<String> = None;
let mut sample_rate: f32 = 2_400_000.0; let mut cal_samples: usize = HEALTHY_WINDOW_SIZE;
let mut snr_db: f32 = 15.0;
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"--input" => {
let spec = &args[i + 1];
if let Some(colon) = spec.find(':') {
let label = spec[..colon].to_string();
let path = spec[colon + 1..].to_string();
input_specs.push((label, path));
} else {
input_specs.push(("unknown".to_string(), spec.clone()));
}
i += 2;
}
"--meta" => { meta_path = Some(args[i + 1].clone()); i += 2; }
"--sample-rate" => { sample_rate = args[i + 1].parse().unwrap_or(sample_rate); i += 2; }
"--cal-samples" => { cal_samples = args[i + 1].parse().unwrap_or(cal_samples); i += 2; }
"--snr-db" => { snr_db = args[i + 1].parse().unwrap_or(snr_db); i += 2; }
_ => { i += 1; }
}
}
if input_specs.is_empty() {
eprintln!("ERROR: at least one --input <platform>:<path>.cf32 is required.");
eprintln!();
eprintln!("IQEngine community dataset access:");
eprintln!(" Browser: https://iqengine.org/browser");
eprintln!(" Filter by tag: rtl-sdr, hackrf, limesdr, usrp-b200, usrp-x310");
eprintln!(" Format: CF32 (interleaved I/Q float32 little-endian) + SigMF JSON");
eprintln!();
eprintln!("Example (cross-hardware comparison):");
eprintln!(" cargo run --example iqengine_diversity --features std -- \\");
eprintln!(" --input rtl-sdr:rtlsdr_433MHz.cf32 \\");
eprintln!(" --input hackrf:hackrf_433MHz.cf32 \\");
eprintln!(" --input usrp-b200:usrp_b200_433MHz.cf32 \\");
eprintln!(" --meta rtlsdr_433MHz.sigmf-meta \\");
eprintln!(" --sample-rate 2400000 \\");
eprintln!(" --cal-samples 24000");
process::exit(1);
}
println!(" {} platform(s) to evaluate | Fs={:.0} Hz | cal={} samples",
input_specs.len(), sample_rate, cal_samples);
if let Some(ref m) = meta_path { println!(" Meta: {}", m); }
println!();
println!(" [1/3] Parsing SigMF annotations …");
let mut events: Vec<RegimeTransitionEvent> = Vec::new();
const GT_LABELS: &[&str] = &[
"onset", "event", "interference", "transition", "ramp",
"drift", "start", "hop", "activation",
];
fn leak_str(s: String) -> &'static str {
Box::leak(s.into_boxed_str())
}
if let Some(ref mp) = meta_path {
let meta_str = std::fs::read_to_string(mp).unwrap_or_else(|e| {
eprintln!("WARN: cannot read meta '{}': {}. Continuing without annotations.", mp, e);
"{}".to_string()
});
let v: Value = serde_json::from_str(&meta_str).unwrap_or(Value::Null);
if let Some(annotations) = v.get("annotations").and_then(|a| a.as_array()) {
for ann in annotations {
let label = ann.get("core:label")
.or_else(|| ann.get("label"))
.and_then(|l| l.as_str()).unwrap_or("").to_lowercase();
let onset = ann.get("core:sample_start")
.or_else(|| ann.get("sample_start"))
.and_then(|s| s.as_u64()).unwrap_or(0) as usize;
let is_gt = GT_LABELS.iter().any(|&kw| label.contains(kw));
if is_gt && onset > cal_samples {
let label_s = leak_str(
ann.get("core:label").or_else(|| ann.get("label"))
.and_then(|l| l.as_str()).unwrap_or("iqengine_event").to_string()
);
println!(" Annotation: '{}' onset_sample={}", label_s, onset);
events.push(RegimeTransitionEvent { k: onset, label: label_s });
}
}
}
if events.is_empty() {
println!(" No matching annotations. Operating unsupervised.");
} else {
println!(" {} ground-truth event(s) shared across all platforms.", events.len());
}
} else {
println!(" No --meta provided. Operating unsupervised.");
}
println!();
println!(" [2/3] Loading and evaluating each real RF capture …");
println!();
println!(" {:16} │ Samples │ ρ_cal │ ρ_GUM │ Episodes │ Prec% │ Recall", "Platform");
println!(" {:16}─┼───────────┼────────┼────────┼──────────┼────────┼───────", "────────────────");
for (platform_label, path) in &input_specs {
let iq_norms: Vec<f32> = {
let f = match File::open(path) {
Ok(f) => f,
Err(e) => {
eprintln!(" [{:16}] ERROR: cannot open '{}': {}", platform_label, path, e);
continue;
}
};
let mut reader = BufReader::new(f);
let mut buf = [0u8; 8];
let mut norms = Vec::new();
loop {
match reader.read_exact(&mut buf) {
Ok(()) => {
let i_s = f32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
let q_s = f32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
norms.push((i_s * i_s + q_s * q_s).sqrt());
}
Err(ref e) if e.kind() == io::ErrorKind::UnexpectedEof => break,
Err(e) => {
eprintln!(" [{:16}] ERROR reading IQ: {}", platform_label, e);
break;
}
}
}
norms
};
if iq_norms.len() < cal_samples + 10 {
eprintln!(" [{:16}] SKIP: only {} samples (need > {})",
platform_label, iq_norms.len(), cal_samples);
continue;
}
let cal_slice = &iq_norms[..cal_samples];
let wss = verify_wss(cal_slice, &StationarityConfig::default());
let wss_ok = wss.map_or(false, |v| v.is_wss);
let mut gum_cfg = UncertaintyConfig::default();
let adc_bits: u32 = if platform_label.contains("x310") || platform_label.contains("X310") { 14 }
else if platform_label.contains("b200") || platform_label.contains("B200") { 12 }
else if platform_label.contains("lime") || platform_label.contains("Lime") { 12 }
else { 8 }; gum_cfg.add_type_b(TypeBContributor {
name: "adc_quantization",
u_b: quantization_noise_std(adc_bits),
source: "platform_specsheet",
});
let budget = compute_budget(cal_slice, &gum_cfg, wss_ok);
let rho_gum = budget.as_ref().map_or(0.0, |b| b.rho_gum);
let observations: Vec<RfObservation> = iq_norms.iter().enumerate().map(|(k, &norm)| {
RfObservation {
k,
residual_norm: norm,
snr_db,
is_healthy: k < cal_samples,
}
}).collect();
let label_leaked: &'static str = leak_str(
std::format!("IQEngine {} (real CF32, Fs={:.0}Hz)", platform_label, sample_rate)
);
let result = run_stage_iii(label_leaked, &observations, &events);
let rho_cal = cal_slice.iter().copied()
.fold(0.0_f32, |a, v| a + v) / cal_slice.len() as f32 * 3.0;
println!(" {:16} │ {:9} │ {:.4} │ {:.4} │ {:8} │ {:5.1}% │ {}/{}",
platform_label, iq_norms.len(),
rho_cal, rho_gum,
result.dsfb_episode_count,
result.episode_precision * 100.0,
result.recall_numerator, result.recall_denominator);
}
println!();
println!(" [3/3] Summary");
println!();
println!(" DSFB self-calibrates to each platform's real noise floor.");
println!(" The calibration window absorbs DC bias, IQ imbalance, and");
println!(" quantization noise without per-hardware configuration.");
println!();
println!(" Observer contract: read-only | no_std core | zero unsafe | no alloc.");
println!("══════════════════════════════════════════════════════════════");
}
#[cfg(not(feature = "std"))]
fn main() {}