208 lines
6.4 KiB
Rust
208 lines
6.4 KiB
Rust
//! TOML-based swarm configuration with mission profiles.
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct SwarmConfig {
|
||
pub swarm: SwarmParams,
|
||
pub formation: FormationConfig,
|
||
pub planning: PlanningConfig,
|
||
pub security: SecurityConfig,
|
||
pub mission: MissionConfig,
|
||
pub demo: Option<DemoConfig>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct SwarmParams {
|
||
pub max_agents: usize,
|
||
pub cluster_size: usize,
|
||
pub raft_election_timeout_ms: u64,
|
||
pub raft_heartbeat_ms: u64,
|
||
pub gossip_fanout: usize,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct FormationConfig {
|
||
/// "virtual_structure" | "leader_follower" | "reynolds"
|
||
pub mode: String,
|
||
pub min_separation_m: f64,
|
||
pub grid_spacing_m: f64,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct PlanningConfig {
|
||
pub flight_altitude_m: f64,
|
||
pub max_speed_ms: f64,
|
||
/// Wi2SAR validated scan footprint width.
|
||
pub csi_scan_width_m: f64,
|
||
pub lateral_overlap_pct: f64,
|
||
/// P(victim) threshold to trigger Phase 3 convergence.
|
||
pub convergence_threshold: f32,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct SecurityConfig {
|
||
pub mavlink_signing: bool,
|
||
pub uwb_antispoofing: bool,
|
||
pub uwb_tolerance_m: f64,
|
||
pub geofence_hard_margin_m: f64,
|
||
pub geofence_soft_margin_m: f64,
|
||
/// Remote ID broadcast rate in Hz (FAA/EU requirement: ≥ 1 Hz).
|
||
pub remote_id_broadcast_hz: f64,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct MissionConfig {
|
||
/// "sar" | "inspection" | "agriculture" | "mine" | "relay"
|
||
pub profile: String,
|
||
pub area_width_m: f64,
|
||
pub area_height_m: f64,
|
||
pub grid_resolution_m: f64,
|
||
pub max_flight_time_mins: f64,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct DemoConfig {
|
||
pub synthetic_csi: bool,
|
||
/// Victim positions in NED [x, y, z].
|
||
pub victim_positions: Vec<[f64; 3]>,
|
||
pub wind_noise_ms: f64,
|
||
pub csi_noise_std: f64,
|
||
pub packet_loss_pct: f64,
|
||
pub replay_speed: f64,
|
||
}
|
||
|
||
impl SwarmConfig {
|
||
pub fn from_toml_str(s: &str) -> Result<Self, toml::de::Error> {
|
||
toml::from_str(s)
|
||
}
|
||
|
||
pub fn sar_default() -> Self {
|
||
Self {
|
||
swarm: SwarmParams {
|
||
max_agents: 12,
|
||
cluster_size: 4,
|
||
raft_election_timeout_ms: 300,
|
||
raft_heartbeat_ms: 100,
|
||
gossip_fanout: 3,
|
||
},
|
||
formation: FormationConfig {
|
||
mode: "virtual_structure".into(),
|
||
min_separation_m: 5.0,
|
||
grid_spacing_m: 20.0,
|
||
},
|
||
planning: PlanningConfig {
|
||
flight_altitude_m: 30.0,
|
||
max_speed_ms: 8.0,
|
||
csi_scan_width_m: 28.0,
|
||
lateral_overlap_pct: 20.0,
|
||
convergence_threshold: 0.75,
|
||
},
|
||
security: SecurityConfig {
|
||
mavlink_signing: true,
|
||
uwb_antispoofing: true,
|
||
uwb_tolerance_m: 2.0,
|
||
geofence_hard_margin_m: 20.0,
|
||
geofence_soft_margin_m: 50.0,
|
||
remote_id_broadcast_hz: 1.0,
|
||
},
|
||
mission: MissionConfig {
|
||
profile: "sar".into(),
|
||
area_width_m: 500.0,
|
||
area_height_m: 500.0,
|
||
grid_resolution_m: 5.0,
|
||
max_flight_time_mins: 25.0,
|
||
},
|
||
demo: None,
|
||
}
|
||
}
|
||
|
||
pub fn inspection_default() -> Self {
|
||
let mut cfg = Self::sar_default();
|
||
cfg.mission.profile = "inspection".into();
|
||
cfg.planning.flight_altitude_m = 15.0;
|
||
cfg.planning.max_speed_ms = 4.0;
|
||
cfg.formation.mode = "leader_follower".into();
|
||
cfg
|
||
}
|
||
|
||
pub fn agriculture_default() -> Self {
|
||
let mut cfg = Self::sar_default();
|
||
cfg.mission.profile = "agriculture".into();
|
||
cfg.planning.flight_altitude_m = 10.0;
|
||
cfg.planning.max_speed_ms = 6.0;
|
||
cfg.planning.csi_scan_width_m = 15.0;
|
||
cfg.formation.mode = "virtual_structure".into();
|
||
cfg.formation.grid_spacing_m = 12.0;
|
||
cfg
|
||
}
|
||
|
||
pub fn mine_default() -> Self {
|
||
let mut cfg = Self::sar_default();
|
||
cfg.mission.profile = "mine".into();
|
||
cfg.planning.flight_altitude_m = 5.0;
|
||
cfg.planning.max_speed_ms = 2.0;
|
||
cfg.security.uwb_antispoofing = true; // GPS-denied: UWB only
|
||
cfg
|
||
}
|
||
|
||
/// Wi2SAR reference configuration (400×400 m, 8 m/s, 4 drones) for ADR-148 SOTA benchmark.
|
||
/// Produces 223 s coverage estimate — below the 240 s (4-min) SOTA target.
|
||
/// Source: Wi2SAR (arxiv 2604.09115): single drone, 160,000 m², 13.5 min.
|
||
pub fn wi2sar_reference() -> Self {
|
||
let mut cfg = Self::sar_default();
|
||
cfg.mission.area_width_m = 400.0;
|
||
cfg.mission.area_height_m = 400.0;
|
||
cfg.planning.max_speed_ms = 8.0;
|
||
cfg.planning.csi_scan_width_m = 28.0;
|
||
cfg.planning.lateral_overlap_pct = 20.0;
|
||
cfg
|
||
}
|
||
|
||
pub fn demo_default() -> Self {
|
||
let mut cfg = Self::sar_default();
|
||
cfg.demo = Some(DemoConfig {
|
||
synthetic_csi: true,
|
||
victim_positions: vec![[50.0, 80.0, 0.0], [150.0, 200.0, 0.0], [300.0, 100.0, 0.0]],
|
||
wind_noise_ms: 2.0,
|
||
csi_noise_std: 0.05,
|
||
packet_loss_pct: 5.0,
|
||
replay_speed: 1.0,
|
||
});
|
||
cfg
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_sar_default_serialization() {
|
||
let cfg = SwarmConfig::sar_default();
|
||
let toml_str = toml::to_string(&cfg).expect("serialize ok");
|
||
let parsed = SwarmConfig::from_toml_str(&toml_str).expect("parse ok");
|
||
assert_eq!(parsed.mission.profile, "sar");
|
||
}
|
||
|
||
#[test]
|
||
fn test_demo_default_has_victims() {
|
||
let cfg = SwarmConfig::demo_default();
|
||
assert!(cfg.demo.is_some());
|
||
assert_eq!(cfg.demo.unwrap().victim_positions.len(), 3);
|
||
}
|
||
|
||
#[test]
|
||
fn test_wi2sar_reference_coverage_within_4min() {
|
||
use crate::demo::scenario::DemoScenario;
|
||
let scenario = DemoScenario {
|
||
name: "Wi2SAR Reference".into(),
|
||
config: SwarmConfig::wi2sar_reference(),
|
||
num_drones: 4,
|
||
victims: vec![],
|
||
};
|
||
let t = scenario.estimate_coverage_time_secs();
|
||
assert!(t < 240.0, "4-drone Wi2SAR reference scenario: {}s should be < 240s (4 min SOTA)", t);
|
||
}
|
||
}
|