use peat_ffi::{create_node, NodeConfig};
use std::f64::consts::PI;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
const ATLANTA_LAT: f64 = 33.749;
const ATLANTA_LON: f64 = -84.388;
struct FlightPattern {
name: &'static str,
pattern_type: PatternType,
center_lat: f64,
center_lon: f64,
radius_deg: f64, altitude_m: f64, speed_factor: f64, classification: &'static str,
category: &'static str,
}
#[derive(Clone, Copy, Debug)]
enum PatternType {
Orbit, Racetrack, Figure8, Lawnmower, }
fn main() {
println!("=== Peat CoT Test Client - Atlanta Flight Patterns ===\n");
let app_id = std::env::var("PEAT_APP_ID").unwrap_or_else(|_| "peat-test-cot-client".into());
let shared_key = std::env::var("PEAT_SHARED_KEY")
.unwrap_or_else(|_| "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".into());
let storage_path =
std::env::var("PEAT_STORAGE_PATH").unwrap_or_else(|_| "/tmp/peat-tak-client".into());
std::fs::create_dir_all(&storage_path).expect("Failed to create storage directory");
println!("Configuration:");
println!(" Formation: {}", app_id);
println!(" Storage: {}", storage_path);
println!(" Area: Atlanta ({:.3}, {:.3})", ATLANTA_LAT, ATLANTA_LON);
println!();
println!("Creating Peat node with mDNS discovery...");
let config = NodeConfig {
app_id: app_id.clone(),
shared_key: shared_key.clone(),
bind_address: Some("0.0.0.0:42008".into()), storage_path: storage_path.clone(),
transport: None, };
let node: Arc<peat_ffi::PeatNode> = match create_node(config) {
Ok(n) => n,
Err(e) => {
eprintln!("Failed to create Peat node: {:?}", e);
return;
}
};
println!("Node ID: {}", node.node_id());
println!("Endpoint: {}", node.endpoint_addr());
println!();
let patterns = vec![
FlightPattern {
name: "HAWK-1",
pattern_type: PatternType::Orbit,
center_lat: ATLANTA_LAT + 0.02, center_lon: ATLANTA_LON - 0.01,
radius_deg: 0.015,
altitude_m: 300.0,
speed_factor: 1.0,
classification: "a-f-A-M-F-Q", category: "aircraft",
},
FlightPattern {
name: "HAWK-2",
pattern_type: PatternType::Racetrack,
center_lat: ATLANTA_LAT - 0.015, center_lon: ATLANTA_LON + 0.02,
radius_deg: 0.02,
altitude_m: 250.0,
speed_factor: 0.8,
classification: "a-f-A-M-F-Q",
category: "aircraft",
},
FlightPattern {
name: "SCOUT-1",
pattern_type: PatternType::Figure8,
center_lat: ATLANTA_LAT, center_lon: ATLANTA_LON,
radius_deg: 0.025,
altitude_m: 400.0,
speed_factor: 0.6,
classification: "a-f-A-M-F-Q",
category: "aircraft",
},
FlightPattern {
name: "SEARCH-1",
pattern_type: PatternType::Lawnmower,
center_lat: ATLANTA_LAT + 0.01, center_lon: ATLANTA_LON + 0.04,
radius_deg: 0.03,
altitude_m: 200.0,
speed_factor: 0.5,
classification: "a-f-A-M-F-Q",
category: "aircraft",
},
];
let mut time_offset = 0u64;
publish_flight_patterns(&node, &patterns, time_offset);
publish_cells_and_nodes(&node);
println!("\n--- Verifying stored data ---");
match node.list_documents("cells") {
Ok(docs) => println!("Cells: {} stored", docs.len()),
Err(e) => println!("Error listing cells: {:?}", e),
}
match node.list_documents("tracks") {
Ok(docs) => println!("Tracks: {} stored", docs.len()),
Err(e) => println!("Error listing tracks: {:?}", e),
}
match node.list_documents("nodes") {
Ok(docs) => println!("Nodes: {} stored", docs.len()),
Err(e) => println!("Error listing nodes: {:?}", e),
}
println!("\n--- Starting P2P sync with mDNS discovery ---");
if let Err(e) = node.start_sync() {
eprintln!("Failed to start sync: {:?}", e);
return;
}
println!("Sync started. Initial peer count: {}", node.peer_count());
println!("\nFlying patterns over Atlanta... (Ctrl+C to exit)");
println!("Consumer plugins should discover this node via mDNS.\n");
let mut last_node_count = 0;
loop {
std::thread::sleep(std::time::Duration::from_secs(2));
time_offset += 2;
let peers = node.peer_count();
let connected = node.connected_peers();
publish_flight_patterns(&node, &patterns, time_offset);
update_node_positions(&node, &patterns, time_offset);
let nodes = node.get_nodes().unwrap_or_default();
if nodes.len() != last_node_count {
println!("\n=== Received {} nodes from network ===", nodes.len());
for p in &nodes {
if p.id.starts_with("node-") {
continue;
}
println!(
" [PLI] {} ({}) @ {:.4}, {:.4} - {}",
p.name,
p.id,
p.lat,
p.lon,
p.status.as_str()
);
}
println!();
last_node_count = nodes.len();
}
println!("[t={}s] Peers: {} | Tracks updated", time_offset, peers);
if !connected.is_empty() {
println!(" Connected: {:?}", connected);
}
}
}
fn current_timestamp() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as i64
}
fn calculate_position(pattern: &FlightPattern, time_secs: u64) -> (f64, f64, f64) {
let t = (time_secs as f64) * pattern.speed_factor * 0.1;
let (lat_offset, lon_offset) = match pattern.pattern_type {
PatternType::Orbit => {
let angle = t % (2.0 * PI);
(
pattern.radius_deg * angle.sin(),
pattern.radius_deg * angle.cos(),
)
}
PatternType::Racetrack => {
let cycle = t % (2.0 * PI);
if cycle < PI / 2.0 {
let progress = cycle / (PI / 2.0);
(
pattern.radius_deg * 0.5,
pattern.radius_deg * (progress - 0.5),
)
} else if cycle < PI {
let angle = (cycle - PI / 2.0) * 2.0;
(
pattern.radius_deg * 0.5 * angle.cos(),
pattern.radius_deg * 0.5 + pattern.radius_deg * 0.5 * angle.sin(),
)
} else if cycle < 3.0 * PI / 2.0 {
let progress = (cycle - PI) / (PI / 2.0);
(
-pattern.radius_deg * 0.5,
pattern.radius_deg * (0.5 - progress),
)
} else {
let angle = (cycle - 3.0 * PI / 2.0) * 2.0 + PI;
(
pattern.radius_deg * 0.5 * angle.cos(),
-pattern.radius_deg * 0.5 + pattern.radius_deg * 0.5 * angle.sin(),
)
}
}
PatternType::Figure8 => {
let angle = t % (2.0 * PI);
let denom = 1.0 + angle.sin().powi(2);
(
pattern.radius_deg * angle.sin() / denom,
pattern.radius_deg * angle.sin() * angle.cos() / denom,
)
}
PatternType::Lawnmower => {
let row_time = 10.0; let total_rows = 6.0;
let cycle_time = row_time * total_rows;
let t_cycle = t % cycle_time;
let row = (t_cycle / row_time).floor();
let row_progress = (t_cycle % row_time) / row_time;
let lat_offset = pattern.radius_deg * (row / total_rows - 0.5);
let lon_offset = if row as i32 % 2 == 0 {
pattern.radius_deg * (row_progress - 0.5)
} else {
pattern.radius_deg * (0.5 - row_progress)
};
(lat_offset, lon_offset)
}
};
let lat = pattern.center_lat + lat_offset;
let lon = pattern.center_lon + lon_offset;
let alt = pattern.altitude_m;
(lat, lon, alt)
}
fn calculate_heading(pattern: &FlightPattern, time_secs: u64) -> f64 {
let (lat1, lon1, _) = calculate_position(pattern, time_secs);
let (lat2, lon2, _) = calculate_position(pattern, time_secs + 1);
let dlat = lat2 - lat1;
let dlon = lon2 - lon1;
let heading_rad = dlon.atan2(dlat);
let heading_deg = heading_rad.to_degrees();
if heading_deg < 0.0 {
heading_deg + 360.0
} else {
heading_deg
}
}
fn publish_flight_patterns(node: &peat_ffi::PeatNode, patterns: &[FlightPattern], time_secs: u64) {
let now = current_timestamp();
for (i, pattern) in patterns.iter().enumerate() {
let (lat, lon, alt) = calculate_position(pattern, time_secs);
let heading = calculate_heading(pattern, time_secs);
let track_id = format!("track-{}", pattern.name.to_lowercase().replace('-', "_"));
let track = serde_json::json!({
"id": track_id,
"source_node": format!("node-{}", pattern.name.to_lowercase()),
"cell_id": "cell-atlanta-001",
"formation_id": "atlanta-isr",
"lat": lat,
"lon": lon,
"hae": alt,
"heading": heading,
"speed": 25.0 + (i as f64 * 5.0), "classification": pattern.classification,
"confidence": 0.95,
"category": pattern.category,
"attributes": {
"callsign": pattern.name,
"pattern": format!("{:?}", pattern.pattern_type),
"altitude_ft": (alt * 3.28084) as i32
},
"created_at": now,
"last_update": now
});
let json = track.to_string();
if let Err(e) = node.put_document("tracks", &track_id, &json) {
eprintln!("Error publishing track {}: {:?}", track_id, e);
} else {
if let Err(e) = node.sync_document("tracks", &track_id) {
if node.peer_count() > 0 {
eprintln!("Error syncing track {}: {:?}", track_id, e);
}
}
}
}
}
fn publish_cells_and_nodes(node: &peat_ffi::PeatNode) {
println!("--- Publishing cells and nodes ---");
let cell = serde_json::json!({
"id": "cell-atlanta-001",
"name": "Atlanta ISR Cell",
"status": "active",
"node_count": 4,
"center_lat": ATLANTA_LAT,
"center_lon": ATLANTA_LON,
"capabilities": ["ISR", "SURVEILLANCE", "RECON"],
"formation_id": "atlanta-isr",
"leader_id": "node-hawk_1",
"last_update": current_timestamp()
});
let json = cell.to_string();
match node.put_document("cells", "cell-atlanta-001", &json) {
Ok(()) => println!(" Published cell: cell-atlanta-001"),
Err(e) => eprintln!(" Error publishing cell: {:?}", e),
}
let nodes = vec![
(
"HAWK-1",
"UAV",
ATLANTA_LAT + 0.02,
ATLANTA_LON - 0.01,
300.0,
),
(
"HAWK-2",
"UAV",
ATLANTA_LAT - 0.015,
ATLANTA_LON + 0.02,
250.0,
),
("SCOUT-1", "UAV", ATLANTA_LAT, ATLANTA_LON, 400.0),
(
"SEARCH-1",
"UAV",
ATLANTA_LAT + 0.01,
ATLANTA_LON + 0.04,
200.0,
),
];
for (name, ptype, lat, lon, alt) in nodes {
let node_id = format!("node-{}", name.to_lowercase().replace('-', "_"));
let node_doc = serde_json::json!({
"id": node_id,
"name": name,
"node_type": ptype,
"lat": lat,
"lon": lon,
"hae": alt,
"readiness": 0.95,
"cell_id": "cell-atlanta-001",
"capabilities": ["ISR", "EO/IR"],
"status": "active",
"last_heartbeat": current_timestamp()
});
let json = node_doc.to_string();
match node.put_document("nodes", &node_id, &json) {
Ok(()) => println!(" Published node: {}", node_id),
Err(e) => eprintln!(" Error publishing node {}: {:?}", node_id, e),
}
}
}
fn update_node_positions(node: &peat_ffi::PeatNode, patterns: &[FlightPattern], time_secs: u64) {
let now = current_timestamp();
for pattern in patterns {
let (lat, lon, alt) = calculate_position(pattern, time_secs);
let heading = calculate_heading(pattern, time_secs);
let node_id = format!("node-{}", pattern.name.to_lowercase().replace('-', "_"));
let node_doc = serde_json::json!({
"id": node_id,
"name": pattern.name,
"node_type": "UAV",
"lat": lat,
"lon": lon,
"hae": alt,
"heading": heading,
"speed": 25.0,
"readiness": 0.95,
"cell_id": "cell-atlanta-001",
"capabilities": ["ISR", "EO/IR"],
"status": "active",
"last_heartbeat": now
});
let json = node_doc.to_string();
if let Err(e) = node.put_document("nodes", &node_id, &json) {
eprintln!("Error updating node {}: {:?}", node_id, e);
}
}
}