1use hpr_core::DVec3;
5use serde::{Deserialize, Serialize};
6
7use crate::dynamics::Phase;
8use crate::error::SimError;
9use crate::flight::FlightEvent;
10use crate::metrics::Stability;
11use crate::state::State;
12
13#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
15pub struct Sample {
16 pub time_s: f64,
18 pub phase: Phase,
20 pub state: State,
22 pub cg_enu_m: DVec3,
24 pub cg_velocity_enu_m_s: DVec3,
26 pub height_above_ground_m: f64,
28 pub vertical_speed_m_s: f64,
30 pub acceleration_enu_m_s2: DVec3,
32 pub airspeed_m_s: f64,
34 pub mach: f64,
36 pub angle_of_attack_rad: f64,
38 pub dynamic_pressure_pa: f64,
40 pub axial_coefficient: f64,
42 pub thrust_n: f64,
44 pub mass_kg: f64,
46 pub recovery_drag_area_m2: f64,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53#[non_exhaustive]
54pub enum Channel {
55 Time,
57 Position,
59 Velocity,
61 Attitude,
63 BodyRates,
65 CgPosition,
67 HeightAboveGround,
69 VerticalSpeed,
71 Acceleration,
73 Airspeed,
75 Mach,
77 AngleOfAttack,
79 DynamicPressure,
81 AxialCoefficient,
83 Thrust,
85 Mass,
87 RecoveryDragArea,
89}
90
91impl Channel {
92 pub const ALL: &'static [Channel] = &[
94 Channel::Time,
95 Channel::Position,
96 Channel::Velocity,
97 Channel::Attitude,
98 Channel::BodyRates,
99 Channel::CgPosition,
100 Channel::HeightAboveGround,
101 Channel::VerticalSpeed,
102 Channel::Acceleration,
103 Channel::Airspeed,
104 Channel::Mach,
105 Channel::AngleOfAttack,
106 Channel::DynamicPressure,
107 Channel::AxialCoefficient,
108 Channel::Thrust,
109 Channel::Mass,
110 Channel::RecoveryDragArea,
111 ];
112
113 #[must_use]
115 pub fn columns(self) -> Vec<String> {
116 let three = |base: &str, unit: &str, axes: [&str; 3]| {
117 axes.iter()
118 .map(|axis| format!("{base}_{axis}_{unit}"))
119 .collect()
120 };
121 let one = |name: &str| vec![name.to_owned()];
122 let enu = ["east", "north", "up"];
123 let xyz = ["x", "y", "z"];
124 match self {
125 Self::Time => one("time_s"),
126 Self::Position => three("position", "m", enu),
127 Self::Velocity => three("velocity", "m_s", enu),
128 Self::Attitude => ["attitude_w", "attitude_x", "attitude_y", "attitude_z"]
129 .iter()
130 .map(|s| (*s).to_owned())
131 .collect(),
132 Self::BodyRates => three("body_rate", "rad_s", xyz),
133 Self::CgPosition => three("cg", "m", enu),
134 Self::HeightAboveGround => one("height_above_ground_m"),
135 Self::VerticalSpeed => one("vertical_speed_m_s"),
136 Self::Acceleration => three("acceleration", "m_s2", enu),
137 Self::Airspeed => one("airspeed_m_s"),
138 Self::Mach => one("mach"),
139 Self::AngleOfAttack => one("angle_of_attack_rad"),
140 Self::DynamicPressure => one("dynamic_pressure_pa"),
141 Self::AxialCoefficient => one("axial_coefficient"),
142 Self::Thrust => one("thrust_n"),
143 Self::Mass => one("mass_kg"),
144 Self::RecoveryDragArea => one("recovery_drag_area_m2"),
145 }
146 }
147
148 pub fn push(self, sample: &Sample, row: &mut Vec<f64>) {
150 let s = &sample.state;
151 match self {
152 Self::Time => row.push(sample.time_s),
153 Self::Position => row.extend(s.position_enu_m.to_array()),
154 Self::Velocity => row.extend(s.velocity_enu_m_s.to_array()),
155 Self::Attitude => {
156 let q = s.unit_attitude();
157 row.extend([q.w, q.x, q.y, q.z]);
158 }
159 Self::BodyRates => row.extend(s.body_rate_rad_s.to_array()),
160 Self::CgPosition => row.extend(sample.cg_enu_m.to_array()),
161 Self::HeightAboveGround => row.push(sample.height_above_ground_m),
162 Self::VerticalSpeed => row.push(sample.vertical_speed_m_s),
163 Self::Acceleration => row.extend(sample.acceleration_enu_m_s2.to_array()),
164 Self::Airspeed => row.push(sample.airspeed_m_s),
165 Self::Mach => row.push(sample.mach),
166 Self::AngleOfAttack => row.push(sample.angle_of_attack_rad),
167 Self::DynamicPressure => row.push(sample.dynamic_pressure_pa),
168 Self::AxialCoefficient => row.push(sample.axial_coefficient),
169 Self::Thrust => row.push(sample.thrust_n),
170 Self::Mass => row.push(sample.mass_kg),
171 Self::RecoveryDragArea => row.push(sample.recovery_drag_area_m2),
172 }
173 }
174}
175
176pub trait FlightStep {
178 fn phase(&self) -> Phase;
180 fn start_s(&self) -> f64;
182 fn end_s(&self) -> f64;
184 fn state_at(&self, t_s: f64) -> State;
186 fn sample(&self, t_s: f64) -> Result<Sample, SimError>;
193
194 fn stability(&self, t_s: f64) -> Result<Stability, SimError>;
203}
204
205pub trait Observer {
207 fn step(&mut self, step: &dyn FlightStep) -> Result<(), SimError> {
213 let _ = step;
214 Ok(())
215 }
216
217 fn event(&mut self, event: &FlightEvent) {
219 let _ = event;
220 }
221}
222
223impl Observer for () {}
224
225impl<T: Observer + ?Sized> Observer for &mut T {
227 fn step(&mut self, step: &dyn FlightStep) -> Result<(), SimError> {
228 (**self).step(step)
229 }
230
231 fn event(&mut self, event: &FlightEvent) {
232 (**self).event(event);
233 }
234}
235
236impl<A: Observer, B: Observer> Observer for (A, B) {
238 fn step(&mut self, step: &dyn FlightStep) -> Result<(), SimError> {
239 self.0.step(step)?;
240 self.1.step(step)
241 }
242
243 fn event(&mut self, event: &FlightEvent) {
244 self.0.event(event);
245 self.1.event(event);
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Serialize)]
259pub struct Recorder {
260 channels: Vec<Channel>,
261 interval_s: Option<f64>,
262 next_index: u64,
264 rows: Vec<Vec<f64>>,
265 samples: usize,
266 last_time_s: Option<f64>,
267}
268
269impl Recorder {
270 pub fn new(channels: Vec<Channel>, interval_s: Option<f64>) -> Result<Self, SimError> {
277 if let Some(dt) = interval_s
278 && !(dt.is_finite() && dt > 0.0)
279 {
280 return Err(SimError::Domain {
281 what: "recorder interval",
282 value: dt,
283 });
284 }
285 if channels
286 .iter()
287 .enumerate()
288 .any(|(i, channel)| channels[..i].contains(channel))
289 {
290 return Err(SimError::Unsupported {
291 what: "a recorder channel listed twice",
292 });
293 }
294 Ok(Self {
295 channels,
296 interval_s,
297 next_index: 0,
298 rows: Vec::new(),
299 samples: 0,
300 last_time_s: None,
301 })
302 }
303
304 #[cfg(test)]
306 pub(crate) fn with_rows(channels: Vec<Channel>, rows: Vec<Vec<f64>>) -> Self {
307 Self {
308 channels,
309 interval_s: None,
310 next_index: 0,
311 samples: rows.len(),
312 last_time_s: None,
313 rows,
314 }
315 }
316
317 pub fn clear(&mut self) {
319 self.next_index = 0;
320 self.rows.clear();
321 self.samples = 0;
322 self.last_time_s = None;
323 }
324
325 #[must_use]
327 pub fn columns(&self) -> Vec<String> {
328 self.channels.iter().flat_map(|c| c.columns()).collect()
329 }
330
331 #[must_use]
333 pub fn rows(&self) -> &[Vec<f64>] {
334 &self.rows
335 }
336
337 fn record(&mut self, sample: &Sample) {
338 let mut row = Vec::new();
339 for channel in &self.channels {
340 channel.push(sample, &mut row);
341 }
342 self.rows.push(row);
343 self.samples += 1;
344 self.last_time_s = Some(sample.time_s);
345 }
346}
347
348impl Observer for Recorder {
349 fn step(&mut self, step: &dyn FlightStep) -> Result<(), SimError> {
350 match self.interval_s {
351 None => {
352 if self.samples == 0 {
353 self.record(&step.sample(step.start_s())?);
354 }
355 self.record(&step.sample(step.end_s())?);
356 }
357 Some(dt) => {
358 let first = (step.start_s() / dt).ceil();
360 if (self.next_index as f64) < first {
361 self.next_index = first as u64;
362 }
363 if !(dt.is_finite() && dt > 0.0) {
364 return Err(SimError::Domain {
365 what: "recorder interval",
366 value: dt,
367 });
368 }
369 loop {
370 let t = self.next_index as f64 * dt;
371 if t > step.end_s() {
372 break;
373 }
374 let sample = step.sample(t)?;
375 self.record(&sample);
376 self.next_index += 1;
377 }
378 }
379 }
380 Ok(())
381 }
382
383 fn event(&mut self, event: &FlightEvent) {
384 if self.interval_s.is_some() && self.last_time_s != Some(event.sample.time_s) {
385 self.record(&event.sample);
386 }
387 }
388}