use super::acquire::codes_from_args;
use super::track::{acquire_inits, handoff_from_args};
use super::{raw_sidecar, Args, Fail};
use crate::iq::io::inventory::open_recording;
use crate::iq::monitor::run::{run_monitors, MonitorConfig};
use crate::iq::monitor::MonitorReport;
use crate::iq::track::{CarrierLoop, LoopConfig};
use crate::iq::SpreadingCode;
use std::collections::BTreeMap;
use std::path::Path;
const SWITCHES: &[&str] = &["--power", "--spectral"];
fn loop_design(a: &Args) -> Result<LoopConfig, Fail> {
let mut cfg = LoopConfig {
label: "monitor".into(),
..LoopConfig::default()
};
if let Some(d) = a.num("--spacing").map_err(Fail::Usage)? {
cfg.spacing_chips = d;
}
if let Some(bn) = a.num("--dll-bw").map_err(Fail::Usage)? {
cfg.dll_bn_hz = bn;
}
if let Some(n) = a.num::<usize>("--coherent").map_err(Fail::Usage)? {
cfg.coherent_periods = n.max(1);
}
if let Some(n) = a.num::<usize>("--cn0-windows").map_err(Fail::Usage)? {
cfg.cn0_windows = n.max(1);
}
let pll: Option<f64> = a.num("--pll-bw").map_err(Fail::Usage)?;
let fll: Option<f64> = a.num("--fll-bw").map_err(Fail::Usage)?;
if pll.is_some() || fll.is_some() {
cfg.carrier = CarrierLoop::FllAssistedPll {
pll_order: 2,
pll_bn_hz: pll.unwrap_or(15.0),
fll_order: 1,
fll_bn_hz: fll.unwrap_or(10.0),
};
}
Ok(cfg)
}
fn config(a: &Args, settings_text: Option<&str>) -> Result<MonitorConfig, Fail> {
let mut cfg = match (settings_text, a.get("--settings")) {
(Some(t), _) => MonitorConfig::parse(t)?,
(None, Some(p)) => {
let text = std::fs::read_to_string(p).map_err(|e| Fail::Run(format!("{p}: {e}")))?;
MonitorConfig::parse(&text)?
}
(None, None) => MonitorConfig::default(),
};
let (pw, sp) = (a.has("--power"), a.has("--spectral"));
if pw || sp {
cfg.power = if pw {
Some(cfg.power.unwrap_or_default())
} else {
None
};
cfg.spectral = if sp {
Some(cfg.spectral.unwrap_or_default())
} else {
None
};
}
if let Some(b) = a.num::<f64>("--baseline").map_err(Fail::Usage)? {
if let Some(p) = cfg.power.as_mut() {
p.baseline_s = b;
}
if let Some(s) = cfg.spectral.as_mut() {
s.baseline_s = b;
}
cfg.epoch.cn0.baseline_s = b;
cfg.epoch.sqm.baseline_s = b;
}
Ok(cfg)
}
pub(crate) fn parse_args(args: &[String]) -> Result<Args, Fail> {
let a = Args::parse(args, SWITCHES).map_err(Fail::Usage)?;
a.need_pos(1, "monitor")?;
Ok(a)
}
pub(crate) fn report_from_args(
a: &Args,
settings_text: Option<&str>,
) -> Result<MonitorReport, Fail> {
let path = Path::new(&a.pos[0]);
let cfg = config(a, settings_text)?;
let design = loop_design(a)?;
let mut channels = Vec::new();
if a.get("--signal").is_some() || a.get("--prn").is_some() {
let codes = codes_from_args(a)?;
let mut opened = open_recording(path, raw_sidecar(a)?)?;
let spec = opened.source.spec();
let handoff = handoff_from_args(a)?;
let periods_per_bit: Option<usize> = a.num("--periods-per-bit").map_err(Fail::Usage)?;
let inits = acquire_inits(
&handoff,
periods_per_bit,
&spec,
&codes,
opened.source.as_mut(),
)?;
for (code, init) in codes.iter().zip(inits) {
channels.push((code.name(), init));
}
}
let mut src = open_recording(path, raw_sidecar(a)?)?;
let max = a
.num::<f64>("--max-seconds")
.map_err(Fail::Usage)?
.map(|s| (s * src.source.spec().fs_hz).round() as u64);
Ok(run_monitors(
src.source.as_mut(),
&channels,
&design,
&cfg,
max,
)?)
}
pub(crate) fn run(args: &[String]) -> Result<String, Fail> {
let a = parse_args(args)?;
let report = report_from_args(&a, None)?;
if let Some(p) = a.get("--json") {
std::fs::write(p, report.to_json()?).map_err(|e| Fail::Run(format!("{p}: {e}")))?;
}
if let Some(prefix) = a.get("--csv") {
for (suffix, body) in [
("series.csv", report.series_csv()),
("events.csv", report.events_csv()),
] {
let p = format!("{prefix}.{suffix}");
std::fs::write(&p, body).map_err(|e| Fail::Run(format!("{p}: {e}")))?;
}
}
Ok(summary(&report))
}
fn summary(r: &MonitorReport) -> String {
let mut by_kind: BTreeMap<(String, String), (usize, f64)> = BTreeMap::new();
for e in &r.events {
let k = (e.kind.clone(), e.channel.clone().unwrap_or_default());
let v = by_kind.entry(k).or_insert((0, e.t_alarm_s));
v.0 += 1;
v.1 = v.1.min(e.t_alarm_s);
}
let mut out = format!("{} event(s)\n", r.events.len());
if !by_kind.is_empty() {
out.push_str("kind\tchannel\tcount\tfirst_alarm_s\n");
for ((kind, ch), (n, t)) in &by_kind {
out.push_str(&format!(
"{kind}\t{}\t{n}\t{t:.3}\n",
if ch.is_empty() { "-" } else { ch }
));
}
}
for (k, v) in &r.notes {
out.push_str(&format!("{k} = {v}\n"));
}
out
}