melvin_ob/console_communication/
console_messenger.rs1use crate::flight_control::{FlightState, Supervisor};
2use crate::scheduling::TaskController;
3use crate::scheduling::task::{BaseTask, ImageTaskStatus};
4use crate::imaging::{CameraAngle, CameraController};
5use crate::util::Vec2D;
6use crate::info;
7use super::{
8 console_endpoint::{ConsoleEndpoint, ConsoleEvent},
9 melvin_messages,
10};
11
12use std::sync::Arc;
13
14pub struct ConsoleMessenger {
21 camera_controller: Arc<CameraController>,
23 task_controller: Arc<TaskController>,
25 supervisor: Arc<Supervisor>,
26 endpoint: Arc<ConsoleEndpoint>,
28}
29
30impl ConsoleMessenger {
31 #[allow(clippy::cast_possible_wrap)]
41 pub(crate) fn start(
42 camera_controller: Arc<CameraController>,
43 task_controller: Arc<TaskController>,
44 supervisor: Arc<Supervisor>,
45 ) -> Self {
46 let endpoint = Arc::new(ConsoleEndpoint::start());
47 let mut receiver = endpoint.subscribe_upstream_events();
48 let endpoint_local = endpoint.clone();
49 let camera_controller_local = camera_controller.clone();
50 let supervisor_local = supervisor.clone();
51 let t_cont_local = task_controller.clone();
52 tokio::spawn(async move {
53 while let Ok(event) = receiver.recv().await {
54 match event {
55 ConsoleEvent::Message(
56 melvin_messages::UpstreamContent::CreateSnapshotImage(_),
57 ) => {
58 camera_controller_local.create_thumb_snapshot().await.unwrap();
59 }
60 ConsoleEvent::Message(
61 melvin_messages::UpstreamContent::GetSnapshotDiffImage(_),
62 ) => {
63 if let Ok(encoded_image) =
64 camera_controller_local.diff_thumb_snapshot().await
65 {
66 endpoint_local.send_downstream(
67 melvin_messages::DownstreamContent::Image(
68 melvin_messages::Image::from_encoded_image_extract(
69 encoded_image,
70 ),
71 ),
72 );
73 }
74 }
75 ConsoleEvent::Message(melvin_messages::UpstreamContent::GetFullImage(_)) => {
76 if let Ok(encoded_image) =
77 camera_controller_local.export_full_thumbnail_png().await
78 {
79 endpoint_local.send_downstream(
80 melvin_messages::DownstreamContent::Image(
81 melvin_messages::Image::from_encoded_image_extract(
82 encoded_image,
83 ),
84 ),
85 );
86 }
87 Self::send_tasklist_from_endpoint(&endpoint_local, &t_cont_local).await;
88 }
89 ConsoleEvent::Message(melvin_messages::UpstreamContent::SubmitObjective(
90 submit_objective,
91 )) => {
92 let c_cont_lock_local_clone = camera_controller_local.clone();
93 let endpoint_local_clone = endpoint_local.clone();
94 tokio::spawn(async move {
95 let objective_id = submit_objective.objective_id;
96 let result = c_cont_lock_local_clone
97 .export_and_upload_objective_png(
98 objective_id as usize,
99 Vec2D::new(
100 submit_objective.offset_x,
101 submit_objective.offset_y,
102 ),
103 Vec2D::new(submit_objective.width, submit_objective.height),
104 None,
105 None,
106 )
107 .await;
108 info!("Submitted objective '{objective_id}' with result: {result:?}");
109 endpoint_local_clone.send_downstream(
110 melvin_messages::DownstreamContent::SubmitResponse(
111 melvin_messages::SubmitResponse {
112 success: result.is_ok(),
113 objective_id: Some(submit_objective.objective_id),
114 },
115 ),
116 );
117 });
118 }
119 ConsoleEvent::Message(
120 melvin_messages::UpstreamContent::ScheduleSecretObjective(objective),
121 ) => {
122 supervisor_local
123 .schedule_secret_objective(objective.objective_id as usize, [
124 objective.offset_x as i32,
125 objective.offset_y as i32,
126 (objective.offset_x + objective.width) as i32,
127 (objective.offset_y + objective.height) as i32,
128 ])
129 .await;
130 }
131 ConsoleEvent::Message(melvin_messages::UpstreamContent::SubmitDailyMap(_)) => {
132 let c_cont_lock_local_clone = camera_controller_local.clone();
133 let endpoint_local_clone = endpoint_local.clone();
134 tokio::spawn(async move {
135 let mut success =
136 c_cont_lock_local_clone.export_full_snapshot().await.is_ok();
137 if success {
138 success =
139 c_cont_lock_local_clone.upload_daily_map_png().await.is_ok();
140 }
141 endpoint_local_clone.send_downstream(
142 melvin_messages::DownstreamContent::SubmitResponse(
143 melvin_messages::SubmitResponse { success, objective_id: None },
144 ),
145 );
146 });
147 }
148 _ => {}
149 }
150 }
151 });
152 Self { camera_controller, task_controller, supervisor, endpoint }
153 }
154
155 pub(crate) fn send_thumbnail(&self, offset: Vec2D<u32>, angle: CameraAngle) {
163 if !self.endpoint.is_console_connected() {
164 return;
165 }
166 let endpoint_local = self.endpoint.clone();
167 let camera_controller_local = self.camera_controller.clone();
168 tokio::spawn(async move {
169 if !endpoint_local.is_console_connected() {
170 return;
171 }
172 if let Ok(encoded_image) =
173 camera_controller_local.export_thumbnail_png(offset, angle).await
174 {
175 endpoint_local.send_downstream(melvin_messages::DownstreamContent::Image(
176 melvin_messages::Image::from_encoded_image_extract(encoded_image),
177 ));
178 }
179 });
180 }
181
182 pub(crate) async fn send_tasklist(&self) {
186 ConsoleMessenger::send_tasklist_from_endpoint(&self.endpoint, &self.task_controller).await;
187 }
188
189 pub(crate) async fn send_tasklist_from_endpoint(
193 endpoint: &Arc<ConsoleEndpoint>,
194 t_cont: &Arc<TaskController>,
195 ) {
196 if !endpoint.is_console_connected() {
197 return;
198 }
199 let tasks = t_cont
200 .sched_arc()
201 .read()
202 .await
203 .iter()
204 .map(|task| melvin_messages::Task {
205 scheduled_on: task.t().timestamp_millis(),
206 task: Some(match task.task_type() {
207 BaseTask::TakeImage(take_image) => {
208 let actual_position = if let ImageTaskStatus::Done { actual_pos, .. } =
209 take_image.image_status
210 {
211 Some(actual_pos)
212 } else {
213 None
214 };
215 melvin_messages::TaskType::TakeImage(melvin_messages::TakeImage {
216 planned_position_x: take_image.planned_pos.x(),
217 planned_position_y: take_image.planned_pos.y(),
218 actual_position_x: actual_position.map(|p| p.x()),
219 actual_position_y: actual_position.map(|p| p.y()),
220 })
221 }
222 BaseTask::SwitchState(state) => {
223 melvin_messages::TaskType::SwitchState(match state.target_state() {
224 FlightState::Charge => melvin_messages::SatelliteState::Charge,
225 FlightState::Acquisition => {
226 melvin_messages::SatelliteState::Acquisition
227 }
228 FlightState::Deployment => melvin_messages::SatelliteState::Deployment,
229 FlightState::Transition => melvin_messages::SatelliteState::Transition,
230 FlightState::Comms => melvin_messages::SatelliteState::Communication,
231 FlightState::Safe => melvin_messages::SatelliteState::Safe,
232 } as i32)
233 }
234 BaseTask::ChangeVelocity(velocity_change_task) => {
235 melvin_messages::TaskType::VelocityChange(melvin_messages::BurnSequence {
236 rational: melvin_messages::VelocityChangeTaskRationale::OrbitEscape as i32,
237 target_x: 0,
238 target_y: 0,
239 add_target_x: None,
240 add_target_y: None,
241 position_x: velocity_change_task
242 .burn()
243 .sequence_pos()
244 .iter()
245 .map(|pos| pos.x().to_num())
246 .collect(),
247 position_y: velocity_change_task
248 .burn()
249 .sequence_pos()
250 .iter()
251 .map(|pos| pos.y().to_num())
252 .collect(),
253 velocity_x: velocity_change_task
254 .burn()
255 .sequence_vel()
256 .iter()
257 .map(|vel| vel.x().to_num())
258 .collect(),
259 velocity_y: velocity_change_task
260 .burn()
261 .sequence_vel()
262 .iter()
263 .map(|vel| vel.y().to_num())
264 .collect(),
265 acc_dt: velocity_change_task.burn().acc_dt() as u32,
266 detumble_dt: velocity_change_task.burn().detumble_dt() as u32,
267 rem_angle_dev: velocity_change_task.burn().rem_angle_dev().to_num(),
268 min_charge: velocity_change_task.burn().min_charge().to_num(),
269 min_fuel: velocity_change_task.burn().min_fuel().to_num(),
270 })
271 }
272 }),
273 })
274 .collect();
275
276 endpoint.send_downstream(melvin_messages::DownstreamContent::TaskList(
277 melvin_messages::TaskList { tasks },
278 ));
279 }
280}