1use std::str::FromStr;
2
3#[cfg(doc)]
4use crate::prelude::DORIS;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use crate::{
10 constants::USO_FREQ_HZ,
11 prelude::{Matcher, ParsingError, DOMES},
12};
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17pub struct GroundStation {
18 pub label: String,
20
21 pub site: String,
23
24 pub domes: DOMES,
26
27 pub beacon_revision: u8,
29
30 pub k_frequency_shift: i8,
32
33 pub(crate) code: u16,
35}
36
37impl Default for GroundStation {
38 fn default() -> Self {
41 Self {
42 label: Default::default(),
43 site: Default::default(),
44 domes: DOMES::from_str("10003S005").unwrap(),
45 beacon_revision: 3,
46 k_frequency_shift: 0,
47 code: 0,
48 }
49 }
50}
51
52impl GroundStation {
53 pub fn with_site_name(&self, name: &str) -> Self {
55 let mut s = self.clone();
56 s.site = name.to_string();
57 s
58 }
59
60 pub fn with_site_label(&self, label: &str) -> Self {
63 let mut s = self.clone();
64 s.label = label.to_string();
65 s
66 }
67
68 pub fn with_domes_str(&self, domes: &str) -> Result<Self, ParsingError> {
70 let domes = DOMES::from_str(domes)?;
71 Ok(self.with_domes(domes))
72 }
73
74 pub fn with_domes(&self, domes: DOMES) -> Self {
76 let mut s = self.clone();
77 s.domes = domes;
78 s
79 }
80
81 pub fn with_beacon_revision(&self, revision: u8) -> Self {
83 let mut s = self.clone();
84 s.beacon_revision = revision;
85 s
86 }
87
88 pub fn with_frequency_shift(&self, shift: i8) -> Self {
90 let mut s = self.clone();
91 s.k_frequency_shift = shift;
92 s
93 }
94
95 pub fn with_unique_id(&self, code: u16) -> Self {
98 let mut s = self.clone();
99 s.code = code;
100 s
101 }
102
103 pub fn matches<'a>(&self, matcher: &'a Matcher) -> bool {
105 match matcher {
106 Matcher::ID(code) => self.code == *code,
107 Matcher::Site(site) => self.site == *site,
108 Matcher::DOMES(domes) => self.domes == *domes,
109 Matcher::Label(label) => self.label == *label,
110 }
111 }
112
113 pub fn s1_frequency_shift(&self) -> f64 {
115 543.0
116 * USO_FREQ_HZ
117 * (3.0 / 4.0 + 87.0 * self.k_frequency_shift as f64 / 5.0 * 2.0_f64.powi(26))
118 }
119
120 pub fn u2_frequency_shift(&self) -> f64 {
122 107.0
123 * USO_FREQ_HZ
124 * (3.0 / 4.0 + 87.0 * self.k_frequency_shift as f64 / 5.0 * 2.0_f64.powi(26))
125 }
126}
127
128impl std::str::FromStr for GroundStation {
129 type Err = ParsingError;
130 fn from_str(content: &str) -> Result<Self, Self::Err> {
131 if content.len() < 40 {
132 return Err(ParsingError::GroundStation);
133 }
134
135 let content = content.split_at(1).1;
136 let (key, rem) = content.split_at(4);
137 let (label, rem) = rem.split_at(5);
138 let (name, rem) = rem.split_at(30);
139 let (domes, rem) = rem.split_at(10);
140 let (gen, rem) = rem.split_at(3);
141 let (k_factor, _) = rem.split_at(3);
142
143 Ok(GroundStation {
144 site: name.trim().to_string(),
145 label: label.trim().to_string(),
146 domes: DOMES::from_str(domes.trim())?,
147 beacon_revision: gen
148 .trim()
149 .parse::<u8>()
150 .map_err(|_| ParsingError::GroundStation)?,
151 k_frequency_shift: k_factor
152 .trim()
153 .parse::<i8>()
154 .map_err(|_| ParsingError::GroundStation)?,
155 code: key
156 .trim()
157 .parse::<u16>()
158 .map_err(|_| ParsingError::GroundStation)?,
159 })
160 }
161}
162
163impl std::fmt::Display for GroundStation {
164 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
165 write!(
166 f,
167 "D{:02} {} {:<29} {} {} {}",
168 self.code,
169 self.label,
170 self.site,
171 self.domes,
172 self.beacon_revision,
173 self.k_frequency_shift
174 )
175 }
176}
177
178#[cfg(test)]
179mod test {
180 use super::GroundStation;
181 use crate::prelude::{DOMESTrackingPoint, DOMES};
182 use std::str::FromStr;
183
184 #[test]
185 fn default_station() {
186 let _ = GroundStation::default();
187 }
188
189 #[test]
190 fn station_parsing() {
191 for (desc, expected) in [
192 (
193 "D01 OWFC OWENGA 50253S002 3 0",
194 GroundStation {
195 label: "OWFC".to_string(),
196 site: "OWENGA".to_string(),
197 domes: DOMES {
198 area: 502,
199 site: 53,
200 sequential: 2,
201 point: DOMESTrackingPoint::Instrument,
202 },
203 beacon_revision: 3,
204 k_frequency_shift: 0,
205 code: 1,
206 },
207 ),
208 (
209 "D17 GRFB GREENBELT 40451S178 3 0",
210 GroundStation {
211 label: "GRFB".to_string(),
212 site: "GREENBELT".to_string(),
213 domes: DOMES {
214 area: 404,
215 site: 51,
216 sequential: 178,
217 point: DOMESTrackingPoint::Instrument,
218 },
219 beacon_revision: 3,
220 k_frequency_shift: 0,
221 code: 17,
222 },
223 ),
224 ] {
225 let station = GroundStation::from_str(desc).unwrap();
226 assert_eq!(station, expected, "station parsing error");
227 assert_eq!(station.to_string(), desc, "station reciprocal error");
228 }
229 }
230}