use crate::iq::acq::{acquire_peak, AcqConfig};
use crate::iq::scene::synthetic::{build_scene, SceneParams};
use crate::iq::signals::by_name::{build_code, SIGNAL_NAMES};
use crate::iq::track::design::Design;
use crate::iq::track::sink::CollectSink;
use crate::iq::track::{ChannelInit, SessionChannel, TrackSession};
use crate::iq::{SampleSpec, SpreadingCode, VecSink, VecSource};
use serde_json::{json, Value};
use std::sync::Arc;
pub const MAX_DURATION_S: f64 = 0.25;
pub const MAX_SAMPLES: usize = 1_000_000;
pub const MAX_SATELLITES: usize = 8;
pub fn signals_json() -> Value {
json!(SIGNAL_NAMES)
}
#[derive(Clone, Debug)]
pub struct ShortRequest {
pub fs_hz: f64,
pub duration_s: f64,
pub signal: String,
pub ids: Vec<i64>,
pub dopplers: Vec<f64>,
pub cn0_dbhz: Option<f64>,
pub seed: u64,
}
fn check(r: &ShortRequest) -> Result<(), String> {
if !(r.fs_hz.is_finite() && r.fs_hz > 0.0) {
return Err("fs_hz must be a positive number".into());
}
if !(r.duration_s.is_finite() && r.duration_s > 0.0 && r.duration_s <= MAX_DURATION_S) {
return Err(format!("duration_s must be in (0, {MAX_DURATION_S}]"));
}
if r.ids.is_empty() || r.ids.len() > MAX_SATELLITES {
return Err(format!("ids must hold 1 to {MAX_SATELLITES} satellites"));
}
if r.dopplers.iter().any(|d| !d.is_finite()) {
return Err("dopplers must be finite".into());
}
let n = r.fs_hz * r.duration_s;
if n > MAX_SAMPLES as f64 {
return Err(format!(
"{n:.0} samples requested; a short run holds at most {MAX_SAMPLES}"
));
}
Ok(())
}
pub fn run_short(r: &ShortRequest) -> Result<Value, String> {
check(r)?;
let params = SceneParams {
fs_hz: r.fs_hz,
duration_s: r.duration_s,
signal: r.signal.clone(),
ids: r.ids.clone(),
dopplers: r.dopplers.clone(),
center_hz: None,
if_hz: 0.0,
cn0_dbhz: r.cn0_dbhz,
noise_figure_db: 2.0,
noise: true,
seed: r.seed,
data: false,
threads: 1,
};
let scene = build_scene(¶ms)?;
let spec: SampleSpec = scene.config().spec;
let mut sink = VecSink::default();
let mut truth = Vec::new();
scene
.generate(&mut sink, &mut truth)
.map_err(|e| e.to_string())?;
let samples = sink.samples;
let codes = r
.ids
.iter()
.map(|&id| build_code(&r.signal, id))
.collect::<Result<Vec<_>, _>>()?;
let design = Design::builtin_default();
let cfg: AcqConfig = design.acq_config(codes[0].period_s());
let mut acquisition = Vec::new();
let mut inits = Vec::new();
for (id, code) in r.ids.iter().zip(&codes) {
let found = acquire_peak(&samples, &spec, code, &cfg)?;
acquisition.push(json!({
"id": id,
"code": found.code_name,
"acquired": found.acquired,
"doppler_hz": found.doppler_hz,
"code_phase_chips": found.code_phase_chips,
"statistic": found.statistic,
"threshold": found.threshold,
}));
if found.acquired {
let arc: Arc<dyn SpreadingCode + Send + Sync> = Arc::new(code.clone());
inits.push((
*id,
ChannelInit::from_acquisition(arc, &found, &spec, 0, None),
));
}
}
let ids: Vec<i64> = inits.iter().map(|(id, _)| *id).collect();
let channels = inits
.into_iter()
.map(|(_, init)| SessionChannel::from_design(init, &design))
.collect();
let mut tracking = Vec::new();
if !ids.is_empty() {
let mut session = TrackSession::new(spec, channels)?.with_threads(1);
let mut src = VecSource::new(spec, samples.clone());
let mut out = CollectSink::default();
session
.run(&mut src, None, &mut out)
.map_err(|e| e.to_string())?;
for (k, id) in ids.iter().enumerate() {
let epochs = out.channels.get(k).map(Vec::as_slice).unwrap_or(&[]);
let last = epochs.last();
tracking.push(json!({
"id": id,
"epochs": epochs.len(),
"final_state": last.map(|(_, s)| s.as_str()),
"final_doppler_hz": last.map(|(e, _)| e.doppler_hz),
"final_cn0_dbhz": last.map(|(e, _)| e.cn0_nwpr_dbhz),
}));
}
}
Ok(json!({
"fs_hz": spec.fs_hz,
"samples": samples.len(),
"acquisition": acquisition,
"tracking": tracking,
}))
}