melvin_ob/mode_control/mode/in_orbit_mode.rs
1use crate::scheduling::task::{BaseTask, Task};
2use crate::objective::KnownImgObjective;
3use crate::flight_control::FlightComputer;
4use super::{zo_prep_mode::ZOPrepMode, global_mode::{GlobalMode, OrbitalMode}};
5use crate::mode_control::{
6 base_mode::BaseMode,
7 mode_context::ModeContext,
8 signal::{ExecExitSignal, OpExitSignal, WaitExitSignal, OptOpExitSignal},
9};
10use crate::{fatal, obj, warn};
11use async_trait::async_trait;
12use chrono::{DateTime, Utc};
13use std::sync::Arc;
14use tokio_util::sync::CancellationToken;
15
16/// [`InOrbitMode`] is an implementation of [`GlobalMode`] and [`OrbitalMode`] that governs normal
17/// in-orbit operations such as transitioning between flight states, listening for event-driven
18/// interrupts (e.g., Safe Mode, ZO, BO), and executing scheduled task queues specific to the [`InOrbitMode`].
19///
20/// This mode represents the satellite operating in a stable orbital state, performing mapping or communication tasks.
21///
22/// The mode is interruptible and reactive to new objectives, flight events, or beacon detections.
23#[derive(Clone)]
24pub struct InOrbitMode {
25 /// The base operational context (e.g. Mapping or Beacon Objective Scanning).
26 base: BaseMode,
27}
28
29impl InOrbitMode {
30 /// The internal name of the mode used for logging and identification.
31 const MODE_NAME: &'static str = "InOrbitMode";
32
33 /// Constructs a new [`InOrbitMode`] instance using the given [`BaseMode`].
34 ///
35 /// # Arguments
36 /// * `base` – The high-level operational context (e.g., `MappingMode`).
37 ///
38 /// # Returns
39 /// * [`InOrbitMode`] – The initialized mode.
40 pub fn new(base: BaseMode) -> Self { Self { base } }
41}
42
43impl OrbitalMode for InOrbitMode {
44 /// Returns a reference to the current [`BaseMode`] context.
45 fn base(&self) -> &BaseMode { &self.base }
46}
47
48#[async_trait]
49impl GlobalMode for InOrbitMode {
50 /// Returns the static name of this mode.
51 fn type_name(&self) -> &'static str { Self::MODE_NAME }
52
53 /// Initializes the mode by running scheduling logic and listening for early exit signals.
54 ///
55 /// Reacts to Safe Mode signals and reinitializes if needed. Otherwise, continues
56 /// with task list execution.
57 ///
58 /// # Arguments
59 /// * `context` – The shared execution context for the mode.
60 ///
61 /// # Returns
62 /// * [`OpExitSignal`] – Signal indicating if the mode should continue or reinitialize.
63 async fn init_mode(&self, context: Arc<ModeContext>) -> OpExitSignal {
64 let cancel_task = CancellationToken::new();
65 let comms_end = self.base.handle_sched_preconditions(Arc::clone(&context)).await;
66 let sched_handle = {
67 let cancel_clone = cancel_task.clone();
68 self.base.get_schedule_handle(Arc::clone(&context), cancel_clone, comms_end, None).await
69 };
70 tokio::pin!(sched_handle);
71 let safe_mon = context.super_v().safe_mon();
72 tokio::select!(
73 _ = &mut sched_handle => {
74 context.k().con().send_tasklist().await;
75 },
76 () = safe_mon.notified() => {
77 cancel_task.cancel();
78 sched_handle.await.ok();
79
80 // Return to mapping mode
81 return OpExitSignal::ReInit(Box::new(self.clone()))
82 }
83 );
84 OpExitSignal::Continue
85 }
86
87 /// Delegates to the default orbital task wait logic, including early exits.
88 ///
89 /// # Arguments
90 /// * `c` – The mode context.
91 /// * `due` – Scheduled time of task execution.
92 ///
93 /// # Returns
94 /// * `WaitExitSignal` – Signal indicating why the wait was interrupted (if at all).
95 async fn exec_task_wait(&self, c: Arc<ModeContext>, due: DateTime<Utc>) -> WaitExitSignal {
96 <Self as OrbitalMode>::exec_task_wait(self, c, due).await
97 }
98
99 /// Executes a single scheduled task.
100 ///
101 /// Only state-switching tasks are valid in this mode. Other task types will cause a fatal error.
102 ///
103 /// # Arguments
104 /// * `context` – Mode context for task execution.
105 /// * `task` – Task to execute.
106 ///
107 /// # Returns
108 /// * `ExecExitSignal::Continue` – Always returned unless fatal occurs.
109 async fn exec_task(&self, context: Arc<ModeContext>, task: Task) -> ExecExitSignal {
110 match task.task_type() {
111 BaseTask::SwitchState(switch) => self.base.get_task(context, *switch).await,
112 _ => {
113 fatal!(
114 "Illegal task type {} for state {}!",
115 task.task_type(),
116 Self::MODE_NAME
117 )
118 }
119 }
120 ExecExitSignal::Continue
121 }
122
123 /// Handles the transition into `FlightState::Safe`, executing a fallback escape sequence
124 /// and finishing the orbit phase.
125 ///
126 /// # Arguments
127 /// * `context` – Mode context for safe handling.
128 ///
129 /// # Returns
130 /// * `OpExitSignal::ReInit` – Always reinitializes the current mode.
131 async fn safe_handler(&self, context: Arc<ModeContext>) -> OpExitSignal {
132 FlightComputer::escape_safe(context.k().f_cont(), false).await;
133 context.o_ch_lock().write().await.finish(
134 context.k().f_cont().read().await.current_pos(),
135 self.safe_mode_rationale(),
136 );
137 OpExitSignal::ReInit(Box::new(self.clone()))
138 }
139
140 /// Handles the detection of a new Zoned Objective.
141 ///
142 /// Attempts to switch to a `ZOPrepMode`. If the objective is unreachable, logs a warning and continues.
143 ///
144 /// # Arguments
145 /// * `c` – Shared context.
146 /// * `obj` – The newly received zoned objective.
147 ///
148 /// # Returns
149 /// * `Some(OpExitSignal::ReInit)` – If transition to `ZOPrepMode` is feasible.
150 /// * `None` – If the objective is not reachable (e.g., burn not possible).
151 async fn zo_handler(&self, c: &Arc<ModeContext>, obj: KnownImgObjective) -> OptOpExitSignal {
152 let id = obj.id();
153 obj!("Found new Zoned Objective {id}!");
154
155 if let Some(zo_mode) = ZOPrepMode::from_obj(c, obj, self.base).await {
156 c.o_ch_lock().write().await.finish(
157 c.k().f_cont().read().await.current_pos(),
158 self.new_zo_rationale(),
159 );
160 Some(OpExitSignal::ReInit(Box::new(zo_mode)))
161 } else {
162 warn!("Skipping Objective, burn not feasible.");
163 None
164 }
165 }
166
167 /// Handles a beacon objective event by toggling to the complementary base mode.
168 /// Logs the beacon transition reason and returns a reinit signal to the new mode.
169 ///
170 /// # Arguments
171 /// * `context` – Shared mode context.
172 ///
173 /// # Returns
174 /// * `Some(OpExitSignal::ReInit)` – Always switches to the next beacon mode.
175 async fn bo_event_handler(&self, context: &Arc<ModeContext>) -> OptOpExitSignal {
176 let base = self.base.bo_event();
177 self.log_bo_event(context, base).await;
178 Some(OpExitSignal::ReInit(Box::new(Self { base })))
179 }
180
181 /// Performs final cleanup when exiting the mode and marks the phase as finished.
182 ///
183 /// # Arguments
184 /// * `context` – Shared context.
185 ///
186 /// # Returns
187 /// * `Box<dyn GlobalMode>` – A boxed copy of the current mode.
188 async fn exit_mode(&self, context: Arc<ModeContext>) -> Box<dyn GlobalMode> {
189 context.o_ch_lock().write().await.finish(
190 context.k().f_cont().read().await.current_pos(),
191 self.tasks_done_rationale(),
192 );
193 Box::new(self.clone())
194 }
195}