melvin_ob/
main.rs

1#![allow(dead_code, clippy::similar_names)]
2#![warn(clippy::shadow_reuse, clippy::shadow_same, clippy::builtin_type_shadow)]
3//! Welcome to the onboard software for **Team 03 — "Cache us if you can"** competing in the **2024/2025 ESA Computer in a Room Challenge**. 
4//! This repository contains the embedded code running on the simulated MELVIN onboard computer, responsible 
5//! for command execution, event detection, task scheduling and DRS communication during the mission.
6
7mod console_communication;
8mod flight_control;
9mod http_handler;
10mod imaging;
11mod mode_control;
12mod objective;
13mod scheduling;
14mod util;
15
16#[cfg(not(target_env = "msvc"))]
17use tikv_jemallocator::Jemalloc;
18
19#[cfg(not(target_env = "msvc"))]
20#[global_allocator]
21static GLOBAL: Jemalloc = Jemalloc;
22
23use crate::flight_control::{
24    FlightComputer, FlightState,
25    orbit::{ClosedOrbit, OrbitBase, OrbitCharacteristics, OrbitUsabilityError},
26};
27use crate::imaging::CameraAngle;
28use crate::mode_control::{
29    ModeContext, OpExitSignal,
30    mode::{GlobalMode, OrbitReturnMode},
31};
32use crate::objective::BeaconController;
33use crate::util::{Keychain, KeychainWithOrbit};
34use chrono::TimeDelta;
35use fixed::types::I32F32;
36use std::{env, sync::Arc, time::Duration};
37
38/// Shared 0-length timedelta in chrono units
39const DT_0: TimeDelta = TimeDelta::seconds(0);
40/// Shared 0-length timedelta in std time units
41const DT_0_STD: Duration = Duration::from_secs(0);
42
43/// Static orbit velocity for closed orbit
44const STATIC_ORBIT_VEL: (I32F32, I32F32) = (I32F32::lit("6.40"), I32F32::lit("7.40"));
45/// Environment variable holding the DRS url
46const ENV_BASE_URL: &str = "DRS_BASE_URL";
47/// Environment variable indicating whether to skip the initial reset or not
48const ENV_SKIP_RESET: &str = "SKIP_RESET";
49
50#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
51async fn main() {
52    let base_url_var = env::var(ENV_BASE_URL);
53    let base_url = base_url_var.as_ref().map_or("http://localhost:33000", |v| v.as_str());
54    let (context, start_mode) = init(base_url).await;
55
56    let mut global_mode = start_mode;
57    loop {
58        let phase = context.o_ch_clone().await.mode_switches();
59        info!("Starting phase {phase} in {}!", global_mode.type_name());
60        match global_mode.init_mode(Arc::clone(&context)).await {
61            OpExitSignal::ReInit(mode) => {
62                global_mode = mode;
63                continue;
64            }
65            OpExitSignal::Continue => (),
66        };
67        match global_mode.exec_task_queue(Arc::clone(&context)).await {
68            OpExitSignal::ReInit(mode) => {
69                global_mode = mode;
70                continue;
71            }
72            OpExitSignal::Continue => {
73                global_mode = global_mode.exit_mode(Arc::clone(&context)).await;
74                continue;
75            }
76        }
77    }
78    // drop(console_messenger);
79}
80
81#[allow(clippy::cast_precision_loss)]
82async fn init(url: &str) -> (Arc<ModeContext>, Box<dyn GlobalMode>) {
83    let (init_k, obj_rx, beac_rx) = Keychain::new(url).await;
84
85    let supervisor_clone = init_k.supervisor();
86    tokio::spawn(async move {
87        supervisor_clone.run_obs_obj_mon().await;
88    });
89
90    if env::var(ENV_SKIP_RESET).is_ok_and(|s| s == "1") {
91        warn!("Skipping reset!");
92        FlightComputer::avoid_transition(&init_k.f_cont()).await;
93    } else {
94        init_k.f_cont().write().await.reset().await;
95    }
96
97    let (beac_cont, beac_state_rx) = {
98        let res = BeaconController::new(beac_rx);
99        (Arc::new(res.0), res.1)
100    };
101
102    let supervisor_clone = init_k.supervisor();
103    tokio::spawn(async move {
104        supervisor_clone.run_announcement_hub().await;
105    });
106    let supervisor_clone = init_k.supervisor();
107    let init_k_c_cont = init_k.c_cont();
108    tokio::spawn(async move {
109        supervisor_clone.run_daily_map_uploader(init_k_c_cont).await;
110    });
111    let beac_cont_clone = Arc::clone(&beac_cont);
112    let handler = Arc::clone(&init_k.client());
113    tokio::spawn(async move {
114        beac_cont_clone.run(handler).await;
115    });
116
117    tokio::time::sleep(Duration::from_secs(5)).await;
118
119    if let Some(c_orbit) = ClosedOrbit::try_from_env() {
120        info!(
121            "Imported existing Orbit with {}% coverage!",
122            c_orbit.get_coverage() * 100
123        );
124        let orbit_char = OrbitCharacteristics::new(&c_orbit, &init_k.f_cont()).await;
125        let supervisor = init_k.supervisor();
126        let mode_context = ModeContext::new(
127            KeychainWithOrbit::new(init_k, c_orbit),
128            orbit_char,
129            obj_rx,
130            beac_state_rx,
131            supervisor,
132            beac_cont,
133        );
134        return (mode_context, Box::new(OrbitReturnMode::new()));
135    }
136
137    let c_orbit: ClosedOrbit = {
138        info!("Creating new Static Orbit!");
139        if init_k.f_cont().read().await.current_battery() < I32F32::lit("50") {
140            FlightComputer::charge_full_wait(&init_k.f_cont()).await;
141        }
142        let f_cont_lock = init_k.f_cont();
143        FlightComputer::set_state_wait(init_k.f_cont(), FlightState::Acquisition).await;
144        FlightComputer::set_vel_wait(init_k.f_cont(), STATIC_ORBIT_VEL.into(), false).await;
145        FlightComputer::set_angle_wait(init_k.f_cont(), CameraAngle::Narrow).await;
146        let f_cont = f_cont_lock.read().await;
147        ClosedOrbit::new(OrbitBase::new(&f_cont), CameraAngle::Wide).unwrap_or_else(|e| match e {
148            OrbitUsabilityError::OrbitNotClosed => fatal!("Static orbit is not closed"),
149            OrbitUsabilityError::OrbitNotEnoughOverlap => {
150                fatal!("Static orbit is not overlapping enough")
151            }
152        })
153    };
154
155    let orbit_char = OrbitCharacteristics::new(&c_orbit, &init_k.f_cont()).await;
156    let supervisor = init_k.supervisor();
157    let mode_context = ModeContext::new(
158        KeychainWithOrbit::new(init_k, c_orbit),
159        orbit_char,
160        obj_rx,
161        beac_state_rx,
162        supervisor,
163        beac_cont,
164    );
165    let mode = OrbitReturnMode::get_next_mode(&mode_context).await;
166    (mode_context, mode)
167}