use super::antenna::{Polarisation, SatPattern};
pub mod centispace;
pub mod generic;
pub mod generic_c_band;
pub mod gnss_meo;
pub mod iridium;
pub mod starlink;
pub mod xona_pulsar;
#[cfg(kshana_celeste)]
pub use crate::celeste_iod::link as celeste_iod;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SourceKind {
Public,
Representative,
Workshop,
}
impl SourceKind {
pub fn as_str(&self) -> &'static str {
match self {
SourceKind::Public => "PUBLIC",
SourceKind::Representative => "REPRESENTATIVE",
SourceKind::Workshop => "WORKSHOP",
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct PresetSource {
pub kind: SourceKind,
pub citation: &'static str,
pub urls: &'static [&'static str],
}
#[derive(Clone, Copy, Debug)]
pub struct BandPreset {
pub name: &'static str,
pub centre_hz: f64,
pub tx_bandwidth_hz: f64,
pub chip_rate_hz: f64,
pub code_length_chips: f64,
pub data_rate_bps: f64,
pub eirp_dbw: f64,
pub pattern: SatPattern,
pub polarisation: Polarisation,
pub axial_ratio_db: f64,
pub allocation: &'static str,
pub ranging: bool,
pub basis: &'static str,
}
#[derive(Clone, Copy, Debug)]
pub struct ShellPreset {
pub altitude_km: f64,
pub inclination_deg: Option<f64>,
pub total: usize,
pub planes: usize,
pub phasing: usize,
pub star: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct SystemPreset {
pub id: &'static str,
pub name: &'static str,
pub source: PresetSource,
pub shells: &'static [ShellPreset],
pub orbit_basis: &'static str,
pub bands: &'static [BandPreset],
pub cold_start_bits: f64,
pub notes: &'static str,
}
impl SystemPreset {
pub fn band(&self, name: &str) -> Option<&'static BandPreset> {
self.bands
.iter()
.find(|b| b.name.eq_ignore_ascii_case(name))
}
}
pub fn all() -> Vec<&'static SystemPreset> {
vec![
&generic::PRESET,
&generic_c_band::PRESET,
&xona_pulsar::PRESET,
&iridium::PRESET,
&starlink::PRESET,
¢ispace::PRESET,
#[cfg(kshana_celeste)]
&celeste_iod::PRESET,
]
}
pub fn by_id(id: &str) -> Option<&'static SystemPreset> {
all().into_iter().find(|p| p.id.eq_ignore_ascii_case(id))
}
pub fn fspl_db(range_m: f64, f_hz: f64) -> f64 {
crate::linkbudget::free_space_loss_db(range_m, f_hz)
}
pub fn slant_range_m(alt_m: f64, el_deg: f64) -> f64 {
let re = super::RE_EARTH;
let e = el_deg.to_radians();
((re + alt_m).powi(2) - (re * e.cos()).powi(2)).sqrt() - re * e.sin()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_preset_is_sourced_and_self_consistent() {
let presets = all();
let mut ids: Vec<&str> = presets.iter().map(|p| p.id).collect();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), presets.len(), "duplicate preset ids");
for p in presets {
assert!(!p.citation_is_empty(), "{} has no citation", p.id);
if p.source.kind == SourceKind::Public {
assert!(
!p.source.urls.is_empty(),
"{} is PUBLIC without a URL",
p.id
);
}
assert!(!p.bands.is_empty() && !p.shells.is_empty(), "{}", p.id);
for b in p.bands {
assert!(
b.centre_hz > 1e8 && b.eirp_dbw.is_finite(),
"{}/{}",
p.id,
b.name
);
assert!(!b.basis.is_empty(), "{}/{} has no basis", p.id, b.name);
}
}
}
#[test]
fn only_the_celeste_preset_is_sourced_from_the_workshop() {
for p in all() {
if p.source.kind == SourceKind::Workshop {
assert_eq!(p.id, "celeste-iod");
}
}
}
impl SystemPreset {
fn citation_is_empty(&self) -> bool {
self.source.citation.trim().is_empty()
}
}
}