1use super::{
2 flight_state::FlightState,
3 orbit::{BurnSequence, ClosedOrbit, IndexedOrbitPosition},
4};
5use crate::http_handler::{
6 http_client,
7 http_request::{
8 control_put::ControlSatelliteRequest,
9 observation_get::ObservationRequest,
10 request_common::{JSONBodyHTTPRequestType, NoBodyHTTPRequestType},
11 reset_get::ResetRequest,
12 },
13};
14use crate::imaging::CameraAngle;
15use crate::util::{Vec2D, WrapDirection, helpers::MAX_DEC};
16use crate::{STATIC_ORBIT_VEL, error, fatal, info, log, log_burn, warn};
17use crate::scheduling::TaskController;
18use chrono::{DateTime, TimeDelta, Utc};
19use fixed::types::{I32F32, I64F64};
20use num::{ToPrimitive, Zero};
21use rand::Rng;
22use std::{
23 sync::Arc,
24 time::{Duration, Instant},
25};
26use tokio::sync::RwLock;
27
28pub type TurnsClockCClockTup = (
29 Vec<(Vec2D<I32F32>, Vec2D<I32F32>)>,
30 Vec<(Vec2D<I32F32>, Vec2D<I32F32>)>,
31);
32
33#[derive(Debug)]
46pub struct FlightComputer {
47 current_pos: Vec2D<I32F32>,
49 current_vel: Vec2D<I32F32>,
51 current_state: FlightState,
53 target_state: Option<FlightState>,
55 current_angle: CameraAngle,
57 current_battery: I32F32,
59 max_battery: I32F32,
61 fuel_left: I32F32,
63 last_observation_timestamp: DateTime<Utc>,
65 request_client: Arc<http_client::HTTPClient>,
67}
68
69impl FlightComputer {
70 pub const MIN_0: I32F32 = I32F32::ZERO;
72 pub const MAX_100: I32F32 = I32F32::lit("100.0");
74 pub const ACC_CONST: I32F32 = I32F32::lit("0.02");
76 pub const FUEL_CONST: I32F32 = I32F32::lit("0.03");
78 pub const VEL_BE_MAX_DECIMAL: u8 = MAX_DEC;
80 const DEF_COND_TO: u32 = 3000;
82 const DEF_COND_PI: u16 = 500;
84 const TO_SAFE_SLEEP: Duration = Duration::from_secs(60);
86 const MAX_OR_VEL_CHANGE_ABS: I32F32 = I32F32::lit("1.5");
88 const MAX_OR_VEL_CHANGE_DEV: I32F32 = I32F32::lit("160");
90 const MAX_OR_ACQ_ACC_TIME: I32F32 = I32F32::lit("160");
93 const MAX_OR_ACQ_TIME: I32F32 = I32F32::lit("156");
96 const AFTER_SAFE_MIN_BATT: I32F32 = I32F32::lit("50");
98 const EXIT_SAFE_MIN_BATT: I32F32 = I32F32::lit("10.0");
100 const DEF_BRAKE_ABS: I32F32 = I32F32::lit("1.0");
102 const MAX_DETUMBLE_DT: TimeDelta = TimeDelta::seconds(20);
104 const LEGAL_TARGET_STATES: [FlightState; 3] = [
106 FlightState::Acquisition,
107 FlightState::Charge,
108 FlightState::Comms,
109 ];
110
111 #[cfg(debug_assertions)]
113 pub fn one_time_safe(&mut self) {
114 self.current_state = FlightState::Transition;
115 self.target_state = None;
116 }
117
118 pub async fn new(request_client: Arc<http_client::HTTPClient>) -> FlightComputer {
126 let mut return_controller = FlightComputer {
127 current_pos: Vec2D::new(I32F32::zero(), I32F32::zero()),
128 current_vel: Vec2D::new(I32F32::zero(), I32F32::zero()),
129 current_state: FlightState::Deployment,
130 target_state: None,
131 current_angle: CameraAngle::Normal,
132 current_battery: I32F32::zero(),
133 max_battery: I32F32::zero(),
134 fuel_left: I32F32::zero(),
135 last_observation_timestamp: Utc::now(),
136 request_client,
137 };
138 return_controller.update_observation().await;
139 if return_controller.current_state == FlightState::Transition {
140 return_controller.target_state = Some(FlightState::Transition);
141 }
142 return_controller
143 }
144
145 pub fn trunc_vel(vel: Vec2D<I32F32>) -> (Vec2D<I32F32>, Vec2D<I64F64>) {
156 let factor = I32F32::from_num(10f32.powi(i32::from(Self::VEL_BE_MAX_DECIMAL)));
157 let factor_f64 = I64F64::from_num(10f64.powi(i32::from(Self::VEL_BE_MAX_DECIMAL)));
158 let trunc_x = (vel.x() * factor).floor() / factor;
159 let trunc_y = (vel.y() * factor).floor() / factor;
160 let dev_x = (I64F64::from_num(vel.x()) * factor_f64).frac() / factor_f64;
161 let dev_y = (I64F64::from_num(vel.y()) * factor_f64).frac() / factor_f64;
162 (Vec2D::new(trunc_x, trunc_y), Vec2D::new(dev_x, dev_y))
163 }
164
165 pub fn round_vel_expand(vel: Vec2D<I32F32>) -> Vec2D<I32F32> {
173 let factor = I32F32::from_num(10f32.powi(i32::from(Self::VEL_BE_MAX_DECIMAL)));
174 let trunc_x = (vel.x() * factor).round();
175 let trunc_y = (vel.y() * factor).round();
176 Vec2D::new(trunc_x, trunc_y)
177 }
178
179 pub fn round_vel(vel: Vec2D<I32F32>) -> (Vec2D<I32F32>, Vec2D<I64F64>) {
187 let factor = I32F32::from_num(10f32.powi(i32::from(Self::VEL_BE_MAX_DECIMAL)));
188 let factor_f64 = I64F64::from_num(10f64.powi(i32::from(Self::VEL_BE_MAX_DECIMAL)));
189 let trunc_x = (vel.x() * factor).round() / factor;
190 let trunc_y = (vel.y() * factor).round() / factor;
191 let dev_x = (I64F64::from_num(vel.x()) * factor_f64).frac() / factor_f64;
192 let dev_y = (I64F64::from_num(vel.y()) * factor_f64).frac() / factor_f64;
193 (Vec2D::new(trunc_x, trunc_y), Vec2D::new(dev_x, dev_y))
194 }
195
196 #[allow(clippy::cast_possible_truncation)]
208 pub fn compute_possible_turns(init_vel: Vec2D<I32F32>) -> TurnsClockCClockTup {
209 let start_x = init_vel.x();
210 let end_x = I32F32::zero();
211 let start_y = init_vel.y();
212 let end_y = I32F32::zero();
213
214 let step_x =
215 if start_x > end_x { -FlightComputer::ACC_CONST } else { FlightComputer::ACC_CONST };
216 let step_y =
217 if start_y > end_y { -FlightComputer::ACC_CONST } else { FlightComputer::ACC_CONST };
218
219 let y_const_x_change: Vec<(Vec2D<I32F32>, Vec2D<I32F32>)> = {
221 let mut x_pos_vel = Vec::new();
222 let step = Vec2D::new(step_x, I32F32::zero());
223 let i_last = (start_x / step_x).ceil().abs().to_num::<i32>();
224 let mut next_pos = Vec2D::new(I32F32::zero(), I32F32::zero());
225 let mut next_vel = init_vel + step;
226 x_pos_vel.push((next_pos.round(), next_vel.round_to_2()));
227 for i in 0..i_last {
228 next_pos = next_pos + next_vel;
229 if i == i_last - 1 {
230 next_vel = Vec2D::new(I32F32::zero(), start_y);
231 } else {
232 next_vel = next_vel + step;
233 }
234 x_pos_vel.push((next_pos.round(), next_vel.round_to_2()));
235 }
236 x_pos_vel
237 };
238
239 let x_const_y_change: Vec<(Vec2D<I32F32>, Vec2D<I32F32>)> = {
241 let mut y_pos_vel = Vec::new();
242 let step = Vec2D::new(I32F32::zero(), step_y);
243 let i_last = (start_y / step_y).ceil().abs().to_num::<i32>();
244 let mut next_pos = Vec2D::new(I32F32::zero(), I32F32::zero());
245 let mut next_vel = init_vel + step;
246 y_pos_vel.push((next_pos.round(), next_vel.round_to_2()));
247 for i in 0..i_last {
248 next_pos = next_pos + next_vel;
249 if i == i_last - 1 {
250 next_vel = Vec2D::new(start_x, I32F32::zero());
251 } else {
252 next_vel = next_vel + step;
253 }
254 y_pos_vel.push((next_pos.round(), next_vel.round_to_2()));
255 }
256 y_pos_vel
257 };
258
259 if step_x.signum() == step_y.signum() {
261 (y_const_x_change, x_const_y_change)
262 } else {
263 (x_const_y_change, y_const_x_change)
264 }
265 }
266
267 pub fn current_pos(&self) -> Vec2D<I32F32> { self.current_pos }
272
273 pub fn current_angle(&self) -> CameraAngle { self.current_angle }
278
279 pub fn current_vel(&self) -> Vec2D<I32F32> { self.current_vel }
284
285 pub fn max_battery(&self) -> I32F32 { self.max_battery }
292
293 pub fn current_battery(&self) -> I32F32 { self.current_battery }
298
299 pub fn fuel_left(&self) -> I32F32 { self.fuel_left }
304
305 pub fn state(&self) -> FlightState { self.current_state }
313
314 pub fn target_state(&self) -> Option<FlightState> { self.target_state }
321
322 pub fn client(&self) -> Arc<http_client::HTTPClient> { Arc::clone(&self.request_client) }
327
328 pub async fn reset(&mut self) {
335 ResetRequest {}
336 .send_request(&self.request_client)
337 .await
338 .unwrap_or_else(|_| fatal!("Failed to reset"));
339 Self::wait_for_duration(Duration::from_secs(4), false).await;
340 self.target_state = None;
341 log!("Reset request complete.");
342 }
343
344 pub fn safe_detected(&mut self) { self.target_state = Some(FlightState::Safe); }
346
347 pub async fn wait_for_duration(sleep: Duration, mute: bool) {
352 if sleep.as_secs() == 0 {
353 if !mute {
354 log!("Wait call rejected! Duration was 0!");
355 };
356 return;
357 }
358 if !mute {
359 info!("Waiting for {} seconds!", sleep.as_secs());
360 };
361 tokio::time::sleep(sleep).await;
362 }
363
364 async fn wait_for_condition<F>(
379 self_lock: &RwLock<Self>,
380 (condition, rationale): (F, String),
381 timeout_millis: u32,
382 poll_interval: u16,
383 mute: bool,
384 ) where
385 F: Fn(&Self) -> bool,
386 {
387 if !mute {
388 log!("Waiting for condition: {rationale}");
389 }
390 let start_time = Instant::now();
391 while start_time.elapsed().as_millis() < u128::from(timeout_millis) {
392 let cond = { condition(&*self_lock.read().await) };
393 if cond {
394 if !mute {
395 let dt = start_time.elapsed().as_millis();
396 if dt > 1000 {
397 log!("Condition met after {} s", dt / 1000);
398 } else {
399 log!("Condition met after {dt} ms");
400 }
401 }
402 return;
403 }
404 tokio::time::sleep(Duration::from_millis(u64::from(poll_interval))).await;
405 }
406 if mute {
407 warn!("Condition: {rationale} not met after {timeout_millis:#?} ms");
408 } else {
409 warn!("Condition not met after {timeout_millis:#?} ms");
410 }
411 }
412
413 pub async fn escape_safe(self_lock: Arc<RwLock<Self>>, force_charge: bool) {
420 let target_state = {
421 let init_batt = self_lock.read().await.current_battery();
422 if init_batt <= Self::AFTER_SAFE_MIN_BATT || force_charge {
423 FlightState::Charge
424 } else {
425 FlightState::Acquisition
426 }
427 };
428 let mut curr_state = self_lock.read().await.state();
429 info!("Safe Mode Runtime initiated. Transitioning back to {target_state} asap.");
430 if curr_state == FlightState::Transition {
431 Self::wait_for_duration(Self::TO_SAFE_SLEEP, false).await;
432 Self::avoid_transition(&self_lock).await;
433 curr_state = {
434 let mut lock = self_lock.write().await;
435 lock.target_state = None;
436 lock.current_state
437 };
438 }
439 if curr_state != FlightState::Safe {
440 error!("State is not safe but {}", curr_state);
441 }
442 let cond_min_charge = (
443 |cont: &FlightComputer| cont.current_battery() > Self::EXIT_SAFE_MIN_BATT,
444 format!("Battery level is higher than {}", Self::EXIT_SAFE_MIN_BATT),
445 );
446 Self::wait_for_condition(
447 &self_lock,
448 cond_min_charge,
449 450_000,
450 Self::DEF_COND_PI,
451 false,
452 )
453 .await;
454 Self::set_state_wait(self_lock, target_state).await;
455 }
456
457 pub async fn avoid_transition(self_lock: &Arc<RwLock<Self>>) {
462 let not_trans = (
463 |cont: &FlightComputer| cont.state() != FlightState::Transition,
464 format!("State is not {}", FlightState::Transition),
465 );
466 let max_dt = FlightState::Safe.dt_to(FlightState::Acquisition);
467 Self::wait_for_condition(
468 self_lock,
469 not_trans,
470 u32::try_from(max_dt.as_millis()).unwrap_or(u32::MAX),
471 Self::DEF_COND_PI,
472 false,
473 )
474 .await;
475 self_lock.write().await.target_state = None;
476 }
477
478 #[allow(clippy::cast_possible_wrap)]
483 pub async fn get_to_comms(self_lock: Arc<RwLock<Self>>) -> DateTime<Utc> {
484 if self_lock.read().await.state() == FlightState::Comms {
485 let batt_diff =
486 self_lock.read().await.current_battery() - TaskController::MIN_BATTERY_THRESHOLD;
487 let rem_t = (batt_diff / FlightState::Comms.get_charge_rate()).abs().ceil();
488 let add_t = TimeDelta::seconds(rem_t.to_num::<i64>()).min(TimeDelta::seconds(
489 TaskController::IN_COMMS_SCHED_SECS as i64,
490 ));
491 return Utc::now() + add_t;
492 }
493 let charge_dt = Self::get_charge_dt_comms(&self_lock).await;
494 log!("Charge time for comms: {}", charge_dt);
495
496 if charge_dt > 0 {
497 FlightComputer::set_state_wait(Arc::clone(&self_lock), FlightState::Charge).await;
498 FlightComputer::wait_for_duration(Duration::from_secs(charge_dt), false).await;
499 FlightComputer::set_state_wait(self_lock, FlightState::Comms).await;
500 } else {
501 FlightComputer::set_state_wait(self_lock, FlightState::Comms).await;
502 }
503 Utc::now() + TimeDelta::seconds(TaskController::IN_COMMS_SCHED_SECS as i64)
504 }
505
506 #[allow(clippy::cast_possible_wrap)]
511 pub async fn escape_if_comms(self_lock: Arc<RwLock<Self>>) -> DateTime<Utc> {
512 let (state, batt) = {
513 let f_cont = self_lock.read().await;
514 (f_cont.state(), f_cont.current_battery())
515 };
516 if state == FlightState::Comms {
517 let half_batt =
518 (TaskController::MAX_BATTERY_THRESHOLD + TaskController::MIN_BATTERY_THRESHOLD) / 2;
519 if batt > half_batt {
520 FlightComputer::set_state_wait(Arc::clone(&self_lock), FlightState::Acquisition)
521 .await;
522 } else {
523 FlightComputer::set_state_wait(Arc::clone(&self_lock), FlightState::Charge).await;
524 }
525 }
526 Utc::now()
527 }
528
529 #[allow(clippy::cast_possible_wrap)]
534 pub async fn get_to_comms_t_est(self_lock: Arc<RwLock<Self>>) -> DateTime<Utc> {
535 let t_time = FlightState::Charge.td_dt_to(FlightState::Comms);
536 if self_lock.read().await.state() == FlightState::Comms {
537 let batt_diff =
538 self_lock.read().await.current_battery() - TaskController::MIN_BATTERY_THRESHOLD;
539 let rem_t = (batt_diff / FlightState::Comms.get_charge_rate().abs()).abs().ceil();
540 return Utc::now() + TimeDelta::seconds(rem_t.to_num::<i64>());
541 }
542 let charge_dt = Self::get_charge_dt_comms(&self_lock).await;
543
544 if charge_dt > 0 {
545 Utc::now() + TimeDelta::seconds(charge_dt as i64) + t_time * 2
546 } else {
547 Utc::now() + t_time
548 }
549 }
550
551 pub async fn get_to_static_orbit_vel(self_lock: &Arc<RwLock<Self>>) {
556 let orbit_vel = Vec2D::from(STATIC_ORBIT_VEL);
557 let (batt, vel) = {
558 let f_cont = self_lock.read().await;
559 (f_cont.current_battery(), f_cont.current_vel())
560 };
561 if vel == orbit_vel {
562 return;
563 }
564 let vel_change_dt = Duration::from_secs_f32(
565 (orbit_vel.euclid_distance(&vel) / Self::ACC_CONST).to_num::<f32>(),
566 );
567 let charge_needed = {
568 let acq_acc_db =
569 FlightState::Acquisition.get_charge_rate() + FlightState::ACQ_ACC_ADDITION;
570 let or_vel_corr_db = I32F32::from_num(vel_change_dt.as_secs()) * acq_acc_db;
571 TaskController::MIN_BATTERY_THRESHOLD + or_vel_corr_db.abs()
572 };
573 log!("Getting back to orbit velocity {orbit_vel}. Minimum charge needed: {charge_needed}");
574 if batt < charge_needed {
575 FlightComputer::charge_full_wait(self_lock).await;
576 }
577 let state = self_lock.read().await.state();
578 if !matches!(state, FlightState::Acquisition) {
579 FlightComputer::set_state_wait(Arc::clone(self_lock), FlightState::Acquisition).await;
580 }
581 FlightComputer::set_vel_wait(Arc::clone(self_lock), orbit_vel, true).await;
582 }
583
584 async fn get_charge_dt_comms(self_lock: &Arc<RwLock<Self>>) -> u64 {
592 let batt_diff = (self_lock.read().await.current_battery()
593 - TaskController::MIN_COMMS_START_CHARGE)
594 .min(I32F32::zero());
595 (-batt_diff / FlightState::Charge.get_charge_rate()).ceil().to_num::<u64>()
596 }
597
598 pub async fn charge_full_wait(self_lock: &Arc<RwLock<Self>>) {
603 let max_batt = self_lock.read().await.max_battery;
604 Self::charge_to_wait(self_lock, max_batt).await;
605 }
606
607 pub async fn charge_to_wait(self_lock: &Arc<RwLock<Self>>, target_batt: I32F32) {
613 let (state, battery) = {
614 let f_cont = self_lock.read().await;
615 (f_cont.state(), f_cont.current_battery())
616 };
617 if battery >= target_batt {
618 return;
619 }
620 if state == FlightState::Safe {
621 FlightComputer::escape_safe(Arc::clone(self_lock), true).await;
622 } else {
623 FlightComputer::set_state_wait(Arc::clone(self_lock), FlightState::Charge).await;
624 }
625 let batt = self_lock.read().await.current_battery();
626 let dt = (target_batt - batt) / FlightState::Charge.get_charge_rate();
627 Self::wait_for_duration(Duration::from_secs(dt.to_num::<u64>()), false).await;
628 }
629
630 pub async fn set_state_wait(self_lock: Arc<RwLock<Self>>, new_state: FlightState) {
636 let init_state = { self_lock.read().await.current_state };
637 if new_state == init_state {
638 log!("State already set to {new_state}");
639 return;
640 } else if !Self::LEGAL_TARGET_STATES.contains(&new_state) {
641 fatal!("State {new_state} is not a legal target state");
642 } else if init_state == FlightState::Transition {
643 fatal!(" State cant be changed when in {init_state}");
644 }
645 self_lock.write().await.target_state = Some(new_state);
646 self_lock.read().await.set_state(new_state).await;
647
648 let transition_t = init_state.dt_to(new_state);
649
650 Self::wait_for_duration(transition_t, false).await;
651 let cond = (
652 |cont: &FlightComputer| cont.state() == new_state,
653 format!("State equals {new_state}"),
654 );
655 Self::wait_for_condition(
656 &self_lock,
657 cond,
658 Self::DEF_COND_TO,
659 Self::DEF_COND_PI,
660 false,
661 )
662 .await;
663 self_lock.write().await.target_state = None;
664 }
665
666 pub async fn set_vel_wait(self_lock: Arc<RwLock<Self>>, new_vel: Vec2D<I32F32>, mute: bool) {
672 let (current_state, current_vel) = {
673 let f_cont_read = self_lock.read().await;
674 (f_cont_read.state(), f_cont_read.current_vel())
675 };
676 if current_state != FlightState::Acquisition {
677 fatal!("Velocity cant be changed in state {current_state}");
678 }
679 let vel_change_dt = Duration::from_secs_f32(
680 (new_vel.euclid_distance(¤t_vel) / Self::ACC_CONST).to_num::<f32>(),
681 );
682 self_lock.read().await.set_vel(new_vel, mute).await;
683 if vel_change_dt.as_secs() > 0 {
684 Self::wait_for_duration(vel_change_dt, mute).await;
685 }
686 let comp_new_vel = Self::round_vel_expand(new_vel);
687 let cond = (
688 |cont: &FlightComputer| Self::round_vel_expand(cont.current_vel()) == comp_new_vel,
689 format!("Vel (Scaled) equals {new_vel}"),
690 );
691 Self::wait_for_condition(&self_lock, cond, Self::DEF_COND_TO, Self::DEF_COND_PI, mute)
692 .await;
693 }
694
695 pub async fn set_angle_wait(self_lock: Arc<RwLock<Self>>, new_angle: CameraAngle) {
707 let (current_angle, current_state) = {
708 let f_cont_read = self_lock.read().await;
709 (f_cont_read.current_angle, f_cont_read.state())
710 };
711 if current_angle == new_angle {
712 log!("Angle already set to {new_angle}");
713 return;
714 }
715 if current_state != FlightState::Acquisition {
716 fatal!("Angle cant be changed in state {current_state}");
717 }
718
719 self_lock.read().await.set_angle(new_angle).await;
720 let cond = (
721 |cont: &FlightComputer| cont.current_angle() == new_angle,
722 format!("Lens equals {new_angle}"),
723 );
724 Self::wait_for_condition(
725 &self_lock,
726 cond,
727 Self::DEF_COND_TO,
728 Self::DEF_COND_PI,
729 false,
730 )
731 .await;
732 }
733
734 pub async fn execute_burn(self_lock: Arc<RwLock<Self>>, burn: &BurnSequence) {
740 let burn_start = Utc::now();
741 for vel_change in burn.sequence_vel() {
742 let st = tokio::time::Instant::now();
743 let dt = Duration::from_secs(1);
744 FlightComputer::set_vel_wait(Arc::clone(&self_lock), *vel_change, true).await;
745 let el = st.elapsed();
746 if el < dt {
747 tokio::time::sleep(dt).await;
748 }
749 }
750 let target_pos = burn.sequence_pos().last().unwrap();
751 let target_vel = burn.sequence_vel().last().unwrap();
752 let (pos, vel) = {
753 let f_cont = self_lock.read().await;
754 (f_cont.current_pos(), f_cont.current_vel())
755 };
756 let burn_dt = (Utc::now() - burn_start).num_seconds();
757 log_burn!(
758 "Burn sequence finished after {burn_dt}s! Position: {pos}, Velocity: {vel:.2}, expected Position: {target_pos:.0}, expected Velocity: {target_vel:.2}."
759 );
760 }
761
762 pub async fn or_maneuver(self_lock: Arc<RwLock<Self>>, c_o: Arc<RwLock<ClosedOrbit>>) -> usize {
768 if self_lock.read().await.state() != FlightState::Acquisition {
769 FlightComputer::set_state_wait(Arc::clone(&self_lock), FlightState::Acquisition).await;
770 }
771 let o_unlocked = c_o.read().await;
772 let (mut pos, vel) = {
773 let f_cont = self_lock.read().await;
774 (f_cont.current_pos(), f_cont.current_vel())
775 };
776 log!("Starting Orbit Return Deviation Compensation.");
777 let start = Utc::now();
778 while !o_unlocked.will_visit(pos) {
779 let (ax, dev) = o_unlocked.get_closest_deviation(pos);
780 let (dv, h_dt) = Self::compute_vmax_and_hold_time(dev);
781 log_burn!("Computed Orbit Return. Deviation on {ax} is {dev:.2} and vel is {vel:.2}.");
782 let corr_v = vel + Vec2D::from_axis_and_val(ax, dv);
783 log_burn!(
784 "Correction velocity is {corr_v:.2}, ramping by {dv:.2}. Hold time will be {h_dt}s."
785 );
786 FlightComputer::set_vel_wait(Arc::clone(&self_lock), corr_v, false).await;
787 if h_dt > 0 {
788 FlightComputer::wait_for_duration(Duration::from_secs(h_dt), false).await;
789 }
790 FlightComputer::set_vel_wait(Arc::clone(&self_lock), vel, false).await;
791 pos = self_lock.read().await.current_pos();
792 }
793 let dt = (Utc::now() - start).num_seconds();
794 let entry_i = o_unlocked.get_i(pos).unwrap();
795 info!("Orbit Return Deviation Compensation finished in {dt}s. New Orbit Index: {entry_i}");
796 entry_i
797 }
798
799 pub fn max_or_maneuver_charge() -> I32F32 {
804 let acq_db = FlightState::Acquisition.get_charge_rate();
805 let acq_acc_db = acq_db + FlightState::ACQ_ACC_ADDITION;
806 Self::MAX_OR_ACQ_ACC_TIME * acq_acc_db + Self::MAX_OR_ACQ_TIME * acq_db
807 }
808
809 fn compute_vmax_and_hold_time(dev: I32F32) -> (I32F32, u64) {
819 let dv_triang = dev.signum() * (Self::ACC_CONST * dev.abs()).sqrt();
821 if dv_triang.abs() <= Self::MAX_OR_VEL_CHANGE_ABS {
822 (dv_triang, 0)
824 } else {
825 let t_ramp = Self::MAX_OR_VEL_CHANGE_ABS / Self::ACC_CONST;
827 let d_ramp = I32F32::from_num(0.5) * Self::MAX_OR_VEL_CHANGE_ABS * t_ramp; let d_hold = dev.abs() - 2 * d_ramp;
829 let t_hold = (d_hold / Self::MAX_OR_VEL_CHANGE_ABS).floor().to_num::<u64>();
830 (dev.signum() * Self::MAX_OR_VEL_CHANGE_ABS, t_hold)
831 }
832 }
833
834 pub async fn stop_ongoing_burn(self_lock: Arc<RwLock<Self>>) {
839 let (vel, state) = {
840 let f_cont = self_lock.read().await;
841 (f_cont.current_vel(), f_cont.state())
842 };
843 if state == FlightState::Acquisition {
844 FlightComputer::set_vel_wait(Arc::clone(&self_lock), vel, true).await;
845 }
846 }
847
848 pub async fn turn_for_2nd_target(
855 self_lock: Arc<RwLock<Self>>,
856 target: Vec2D<I32F32>,
857 deadline: DateTime<Utc>,
858 ) {
859 log!("Starting turn for second target");
860 let start = Utc::now();
861 let pos = self_lock.read().await.current_pos();
862 let mut last_to_target = pos.unwrapped_to(&target);
863 let ticker = 0;
864 loop {
865 let (pos, vel) = {
866 let f_cont = self_lock.read().await;
867 (f_cont.current_pos(), f_cont.current_vel())
868 };
869
870 let to_target = pos.unwrapped_to(&target);
871 let dt = to_target.abs() / vel.abs();
872 if !last_to_target.is_eq_signum(&to_target) {
873 let wait_dt = dt.to_num::<u64>()
874 + TaskController::ZO_IMAGE_FIRST_DEL.num_seconds().to_u64().unwrap();
875 log!("Overshot target! Holding velocity change and waiting for 5s!");
876 FlightComputer::set_vel_wait(Arc::clone(&self_lock), vel, true).await;
877 FlightComputer::wait_for_duration(Duration::from_secs(wait_dt), false).await;
878 return;
879 }
880 last_to_target = to_target;
881 let dx = (pos + vel * dt).to(&target).round_to_2();
882 let new_vel = to_target.normalize() * vel.abs();
883
884 if ticker % 10 == 0 {
885 log_burn!("Turning: DX: {dx:.2}, direct DT: {dt:.2}s");
886 }
887 if dx.abs() < vel.abs() / 2 {
888 let turn_dt = (Utc::now() - start).num_seconds();
889 log!("Turning finished after {turn_dt}s with remaining DX: {dx:.2} and dt {dt:2}s");
890 FlightComputer::stop_ongoing_burn(Arc::clone(&self_lock)).await;
891 let sleep_dt = Duration::from_secs(dt.to_num::<u64>()) + Duration::from_secs(5);
892 tokio::time::sleep(sleep_dt).await;
893 return;
894 }
895 if Utc::now() > deadline {
896 let turn_dt = (Utc::now() - start).num_seconds();
897 log!("Turning timeout after {turn_dt}s with remaining DX: {dx:.2} and dt {dt:2}s");
898 FlightComputer::stop_ongoing_burn(Arc::clone(&self_lock)).await;
899 }
900 self_lock.write().await.set_vel(new_vel, true).await;
901 tokio::time::sleep(Duration::from_secs(1)).await;
902 }
903 }
904
905 pub async fn detumble_to(
917 self_lock: Arc<RwLock<Self>>,
918 mut target: Vec2D<I32F32>,
919 lens: CameraAngle,
920 ) -> (DateTime<Utc>, Vec2D<I32F32>) {
921 let mut ticker: i32 = 0;
922 let max_speed = lens.get_max_speed();
923 let detumble_start = Utc::now();
924
925 let start_pos = self_lock.read().await.current_pos();
926 let mut to_target = start_pos.to(&target);
927 let mut dt;
928 let mut dx;
929 let mut last_to_target = to_target;
930 log!("Starting detumble to {target} (projected position).");
931 loop {
932 let (pos, vel) = {
933 let f_locked = self_lock.read().await;
934 (f_locked.current_pos(), f_locked.current_vel())
935 };
936 to_target = pos.to(&target);
937
938 if to_target.x().abs() > 2 * last_to_target.x().abs() {
940 target = target.wrap_by(&WrapDirection::WrapX);
941 to_target = pos.to(&target);
942 }
943 if to_target.y().abs() > 2 * last_to_target.y().abs() {
944 target = target.wrap_by(&WrapDirection::WrapY);
945 to_target = pos.to(&target);
946 }
947 last_to_target = to_target;
948 dt = (to_target.abs() / vel.abs()).round();
949 dx = (pos + vel * dt).to(&target).round_to_2();
950 let per_dx = dx.abs() / dt;
951
952 let acc = dx.normalize() * Self::ACC_CONST.min(per_dx * Self::rand_weight());
953 let mut new_vel = vel + FlightComputer::round_vel(acc).0;
954 let overspeed = new_vel.abs() > max_speed;
955 if overspeed {
956 let target_vel = new_vel.normalize() * (new_vel.abs() - Self::DEF_BRAKE_ABS);
957 let (trunc_vel, _) = FlightComputer::round_vel(target_vel);
958 new_vel = trunc_vel;
959 }
960 if ticker % 5 == 0 {
961 log_burn!("Detumbling Step {ticker}: DX: {dx:.2}, direct DT: {dt:2}s");
962 if overspeed {
963 let overspeed_amount = vel.abs() - max_speed;
964 warn!("Overspeeding by {overspeed_amount:.2}");
965 }
966 }
967 ticker += 1;
968 if dx.abs() < vel.abs() / 2 || Utc::now() - detumble_start > Self::MAX_DETUMBLE_DT {
969 let detumble_dt = (Utc::now() - detumble_start).num_seconds();
970 log!(
971 "Detumbling finished after {detumble_dt}s with rem. DX: {dx:.2} and dt {dt:.2}s"
972 );
973 FlightComputer::stop_ongoing_burn(Arc::clone(&self_lock)).await;
974 FlightComputer::set_angle_wait(Arc::clone(&self_lock), lens).await;
975 return (Utc::now() + TimeDelta::seconds(dt.to_num::<i64>()), target);
976 }
977 if overspeed {
978 FlightComputer::set_vel_wait(Arc::clone(&self_lock), new_vel, true).await;
979 } else {
980 self_lock.write().await.set_vel(new_vel, true).await;
981 }
982 tokio::time::sleep(Duration::from_secs(1)).await;
983 }
984 }
985
986 fn rand_weight() -> I32F32 {
991 let mut rng = rand::rng();
992 I32F32::from_num(rng.random_range(0.0..10.0))
993 }
994
995 pub async fn update_observation(&mut self) {
1000 if let Ok(obs) = (ObservationRequest {}.send_request(&self.request_client).await) {
1001 self.current_pos =
1002 Vec2D::from((I32F32::from_num(obs.pos_x()), I32F32::from_num(obs.pos_y())));
1003 self.current_vel =
1004 Vec2D::from((I32F32::from_num(obs.vel_x()), I32F32::from_num(obs.vel_y())));
1005 self.current_state = FlightState::from(obs.state());
1006 self.current_angle = CameraAngle::from(obs.angle());
1007 self.last_observation_timestamp = obs.timestamp();
1008 self.current_battery =
1009 I32F32::from_num(obs.battery()).clamp(Self::MIN_0, Self::MAX_100);
1010 self.max_battery =
1011 I32F32::from_num(obs.max_battery()).clamp(Self::MIN_0, Self::MAX_100);
1012 self.fuel_left = I32F32::from_num(obs.fuel()).clamp(Self::MIN_0, Self::MAX_100);
1013 } else {
1014 error!("Unnoticed HTTP Error in updateObservation()");
1015 }
1016 }
1017
1018 async fn set_state(&self, new_state: FlightState) {
1023 let req = ControlSatelliteRequest {
1024 vel_x: self.current_vel.x().to_f64().unwrap(),
1025 vel_y: self.current_vel.y().to_f64().unwrap(),
1026 camera_angle: self.current_angle.into(),
1027 state: new_state.into(),
1028 };
1029 if req.send_request(&self.request_client).await.is_ok() {
1030 info!("State change started to {new_state}");
1031 } else {
1032 error!("Unnoticed HTTP Error in set_state()");
1033 }
1034 }
1035
1036 async fn set_vel(&self, new_vel: Vec2D<I32F32>, mute: bool) {
1041 let (vel, _) = Self::round_vel(new_vel);
1042 let req = ControlSatelliteRequest {
1043 vel_x: vel.x().to_f64().unwrap(),
1044 vel_y: vel.y().to_f64().unwrap(),
1045 camera_angle: self.current_angle.into(),
1046 state: self.current_state.into(),
1047 };
1048
1049 if req.send_request(&self.request_client).await.is_ok() {
1050 if !mute {
1051 info!("Velocity change commanded to [{}, {}]", vel.x(), vel.y());
1052 }
1053 } else {
1054 error!("Unnoticed HTTP Error in set_state()");
1055 }
1056 }
1057
1058 async fn set_angle(&self, new_angle: CameraAngle) {
1063 let req = ControlSatelliteRequest {
1064 vel_x: self.current_vel.x().to_f64().unwrap(),
1065 vel_y: self.current_vel.y().to_f64().unwrap(),
1066 camera_angle: new_angle.into(),
1067 state: self.current_state.into(),
1068 };
1069
1070 if req.send_request(&self.request_client).await.is_ok() {
1071 info!("Angle change commanded to {new_angle}");
1072 } else {
1073 error!("Unnoticed HTTP Error in set_state()");
1074 }
1075 }
1076
1077 pub fn pos_in_dt(&self, now: IndexedOrbitPosition, dt: TimeDelta) -> IndexedOrbitPosition {
1085 let pos = self.current_pos
1086 + (self.current_vel * I32F32::from_num(dt.num_seconds())).wrap_around_map();
1087 let t = Utc::now() + dt;
1088 now.new_from_future_pos(pos, t)
1089 }
1090
1091 pub fn batt_in_dt(&self, dt: TimeDelta) -> I32F32 {
1099 self.current_battery
1100 + (self.current_state.get_charge_rate() * I32F32::from_num(dt.num_seconds()))
1101 }
1102}