1use crate::prelude::{Observable, ParsingError};
3
4#[cfg(feature = "nav")]
5use anise::{
6 math::Vector6,
7 prelude::{Epoch, Frame, Orbit},
8};
9
10#[cfg(feature = "qc")]
11use maud::{html, Markup, Render};
12
13#[derive(Default, Clone, Debug, PartialEq)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16pub struct Sensor {
17 pub observable: Observable,
19 pub model: Option<String>,
21 pub sensor_type: Option<String>,
23 pub accuracy: Option<f32>,
25 pub position: Option<(f64, f64, f64)>,
27 pub height: Option<f64>,
29}
30
31#[cfg(feature = "qc")]
32impl Render for Sensor {
33 fn render(&self) -> Markup {
34 html! {
35 table class="table is-bordered" {
36 tr {
37 th { "Observable" }
38 td { (self.observable.to_string()) }
39 }
40 @if let Some(model) = &self.model {
41 tr {
42 th { "Model" }
43 td { (model) }
44 }
45 }
46 @if let Some(sensor) = &self.sensor_type {
47 tr {
48 th { "Sensor Type" }
49 td { (sensor) }
50 }
51 }
52 @if let Some(accuracy) = self.accuracy {
53 tr {
54 th { "Sensor Accuracy" }
55 td { (format!("{:.3E}", accuracy)) }
56 }
57 }
58 @if cfg!(feature = "nav") {
59 @if let Some((x_ecef_m, y_ecef_m, z_ecef_m)) = self.position {
60 tr {
61 th { "Sensor position (ECEF m)" }
62 td {
63 (format!("{:.3E}m", x_ecef_m))
64 }
65 td {
66 (format!("{:.3E}m", y_ecef_m))
67 }
68 td {
69 (format!("{:.3E}m", z_ecef_m))
70 }
71 }
72 }
73 @if let Some(h) = self.height {
74 tr {
75 th { "Height" }
76 td { (format!("{:.3} m", h)) }
77 }
78 }
79 }
80 }
81 }
82 }
83}
84
85impl std::str::FromStr for Sensor {
86 type Err = ParsingError;
87 fn from_str(content: &str) -> Result<Self, Self::Err> {
88 let (model, rem) = content.split_at(20);
89 let (s_type, rem) = rem.split_at(20 + 6);
90 let (accuracy, rem) = rem.split_at(7 + 4);
91 let (observable, _) = rem.split_at(2);
92 Ok(Self {
93 model: {
94 if !model.trim().is_empty() {
95 Some(model.trim().to_string())
96 } else {
97 None
98 }
99 },
100 sensor_type: {
101 if !s_type.trim().is_empty() {
102 Some(s_type.trim().to_string())
103 } else {
104 None
105 }
106 },
107 accuracy: {
108 if let Ok(f) = f32::from_str(accuracy.trim()) {
109 Some(f)
110 } else {
111 None
112 }
113 },
114 height: None,
115 position: None,
116 observable: Observable::from_str(observable.trim())?,
117 })
118 }
119}
120
121impl std::fmt::Display for Sensor {
122 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
123 if let Some(model) = &self.model {
124 write!(f, "{:<width$}", model, width = 20)?
125 } else {
126 write!(f, "{:20}", "")?;
127 }
128
129 if let Some(stype) = &self.sensor_type {
130 write!(f, "{:<width$}", stype, width = 26)?;
131 } else {
132 write!(f, "{:26}", "")?;
133 }
134
135 if let Some(accuracy) = self.accuracy {
136 write!(f, "{:^11.1}", accuracy)?
137 } else {
138 write!(f, "{:11}", "")?
139 }
140 writeln!(f, "{} SENSOR MOD/TYPE/ACC", self.observable)?;
141
142 if let Some((x, y, z)) = self.position {
143 write!(f, "{:14.4}", x)?;
144 write!(f, "{:14.4}", y)?;
145 write!(f, "{:14.4}", z)?;
146
147 let h = self.height.unwrap_or(0.0);
148
149 write!(f, "{:14.4}", h)?;
150 writeln!(f, " {} SENSOR POS XYZ/H", self.observable)?
151 }
152 Ok(())
153 }
154}
155
156impl Sensor {
157 pub fn new(observable: Observable) -> Self {
159 Self::default().with_observable(observable)
160 }
161
162 pub fn with_model(&self, model: &str) -> Self {
164 let mut s = self.clone();
165 s.model = Some(model.to_string());
166 s
167 }
168
169 pub fn with_type(&self, stype: &str) -> Self {
171 let mut s = self.clone();
172 s.sensor_type = Some(stype.to_string());
173 s
174 }
175
176 pub fn with_observable(&self, observable: Observable) -> Self {
178 let mut s = self.clone();
179 s.observable = observable;
180 s
181 }
182
183 pub fn with_position(&self, position: (f64, f64, f64)) -> Self {
185 let mut s = self.clone();
186 s.position = Some(position);
187 s
188 }
189
190 pub fn with_height(&self, h: f64) -> Self {
192 let mut s = self.clone();
193 s.height = Some(h);
194 s
195 }
196
197 pub fn with_accuracy(&self, accuracy: f32) -> Self {
199 let mut s = self.clone();
200 s.accuracy = Some(accuracy);
201 s
202 }
203
204 #[cfg(feature = "nav")]
205 #[cfg_attr(docsrs, doc(cfg(feature = "nav")))]
206 pub fn rx_orbit(&self, t: Epoch, frame: Frame) -> Option<Orbit> {
208 let (x_ecef_m, y_ecef_m, z_ecef_m) = self.position?;
209
210 let pos_vel = Vector6::new(
211 x_ecef_m / 1000.0,
212 y_ecef_m / 1000.0,
213 z_ecef_m / 1000.0,
214 0.0,
215 0.0,
216 0.0,
217 );
218
219 Some(Orbit::from_cartesian_pos_vel(pos_vel, t, frame))
220 }
221}
222
223#[cfg(test)]
224mod test {
226 use super::*;
227 use std::str::FromStr;
229
230 #[test]
231 fn test_formatting() {
232 let s = Sensor::new(Observable::Temperature);
233 assert_eq!(
234 s.to_string(),
235 " TD SENSOR MOD/TYPE/ACC\n"
236 );
237 let s = s.with_model("PAROSCIENTIFIC");
238 assert_eq!(
239 s.to_string(),
240 "PAROSCIENTIFIC TD SENSOR MOD/TYPE/ACC\n"
241 );
242 let s = s.with_observable(Observable::Pressure);
243 let s = s.with_type("740-16B");
244 assert_eq!(
245 s.to_string(),
246 "PAROSCIENTIFIC 740-16B PR SENSOR MOD/TYPE/ACC\n"
247 );
248 let s = s.with_accuracy(0.2);
249 assert_eq!(
250 s.to_string(),
251 "PAROSCIENTIFIC 740-16B 0.2 PR SENSOR MOD/TYPE/ACC\n"
252 );
253
254 }
272
273 #[test]
274 fn from_str() {
275 let s = Sensor::from_str(" 0.0 PR ");
276 assert!(s.is_ok());
277 let s = s.unwrap();
278 assert_eq!(s.model, None);
279 assert_eq!(s.sensor_type, None);
280 assert_eq!(s.accuracy, Some(0.0));
281 assert_eq!(s.observable, Observable::Pressure);
282
283 let s = Sensor::from_str(
284 "PAROSCIENTIFIC 740-16B 0.2 PR SENSOR MOD/TYPE/ACC",
285 );
286 assert!(s.is_ok());
287
288 let s = Sensor::from_str(
289 " 0.0 PR SENSOR MOD/TYPE/ACC",
290 );
291
292 assert!(s.is_ok());
293 }
294}