1use super::*;
18
19#[derive(Default, Clone, Debug, PartialEq)]
23pub struct GroupAssignmentCommand {
24 pub own_vessel: bool,
26
27 pub station: Station,
29
30 pub mmsi: u32,
32
33 pub ne_lat: Option<f64>,
35
36 pub ne_lon: Option<f64>,
38
39 pub sw_lat: Option<f64>,
41
42 pub sw_lon: Option<f64>,
44
45 pub station_type: StationType,
47
48 pub ship_type: ShipType,
50
51 pub cargo_type: CargoType,
53
54 pub txrx: u8,
60
61 pub interval: StationInterval,
63
64 pub quiet: Option<u8>,
68}
69
70#[derive(Clone, Copy, Debug, PartialEq)]
72pub enum StationType {
73 AllTypes,
75
76 Reserved1,
78
79 AllTypesOfClassBMobile,
81
82 SarAirborneMobile,
84
85 AidToNavigation,
87
88 ClassBShipBorneMobile,
90
91 Regional6,
93
94 Regional7,
96
97 Regional8,
99
100 Regional9,
102
103 Reserved10,
105
106 Reserved11,
108
109 Reserved12,
111
112 Reserved13,
114
115 Reserved14,
117
118 Reserved15,
120}
121
122impl Default for StationType {
123 fn default() -> Self {
124 StationType::AllTypes
125 }
126}
127
128impl StationType {
129 fn new(val: u8) -> Result<StationType, String> {
130 match val {
131 0 => Ok(StationType::AllTypes),
132 1 => Ok(StationType::Reserved1),
133 2 => Ok(StationType::AllTypesOfClassBMobile),
134 3 => Ok(StationType::SarAirborneMobile),
135 4 => Ok(StationType::AidToNavigation),
136 5 => Ok(StationType::ClassBShipBorneMobile),
137 6 => Ok(StationType::Regional6),
138 7 => Ok(StationType::Regional7),
139 8 => Ok(StationType::Regional8),
140 9 => Ok(StationType::Regional9),
141 10 => Ok(StationType::Reserved10),
142 11 => Ok(StationType::Reserved11),
143 12 => Ok(StationType::Reserved12),
144 13 => Ok(StationType::Reserved13),
145 14 => Ok(StationType::Reserved14),
146 15 => Ok(StationType::Reserved15),
147 _ => Err(format!("Station type value out of range: {}", val)),
148 }
149 }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq)]
154pub enum StationInterval {
155 Autonomous,
157
158 Time10min,
160
161 Time6min,
163
164 Time3min,
166
167 Time1min,
169
170 Time30sec,
172
173 Time15sec,
175
176 Time10sec,
178
179 Time5sec,
181
182 NextShorterReportingInverval,
184
185 NextLongerReportingInverval,
187
188 Reserved11,
190
191 Reserved12,
193
194 Reserved13,
196
197 Reserved14,
199
200 Reserved15,
202}
203
204impl StationInterval {
205 fn new(val: u8) -> Result<StationInterval, String> {
206 match val {
207 0 => Ok(StationInterval::Autonomous),
208 1 => Ok(StationInterval::Time10min),
209 2 => Ok(StationInterval::Time6min),
210 3 => Ok(StationInterval::Time3min),
211 4 => Ok(StationInterval::Time1min),
212 5 => Ok(StationInterval::Time30sec),
213 6 => Ok(StationInterval::Time15sec),
214 7 => Ok(StationInterval::Time10sec),
215 8 => Ok(StationInterval::Time5sec),
216 9 => Ok(StationInterval::NextShorterReportingInverval),
217 10 => Ok(StationInterval::NextLongerReportingInverval),
218 11 => Ok(StationInterval::Reserved11),
219 12 => Ok(StationInterval::Reserved12),
220 13 => Ok(StationInterval::Reserved13),
221 14 => Ok(StationInterval::Reserved14),
222 15 => Ok(StationInterval::Reserved15),
223 _ => Err(format!("Station interval value out of range: {}", val)),
224 }
225 }
226}
227
228impl Default for StationInterval {
229 fn default() -> Self {
230 StationInterval::Autonomous
231 }
232}
233
234pub(crate) fn handle(
238 bv: &BitVec,
239 station: Station,
240 own_vessel: bool,
241) -> Result<ParsedMessage, ParseError> {
242 Ok(ParsedMessage::GroupAssignmentCommand(
243 GroupAssignmentCommand {
244 own_vessel: { own_vessel },
245 station: { station },
246 mmsi: { pick_u64(bv, 8, 30) as u32 },
247 ne_lat: { Some(pick_i64(bv, 58, 17) as f64 / 600.0) },
248 ne_lon: { Some(pick_i64(bv, 40, 18) as f64 / 600.0) },
249 sw_lat: { Some(pick_i64(bv, 93, 17) as f64 / 600.0) },
250 sw_lon: { Some(pick_i64(bv, 75, 18) as f64 / 600.0) },
251 station_type: StationType::new(pick_u64(bv, 110, 4) as u8)?,
252 ship_type: ShipType::new(pick_u64(bv, 114, 8) as u8),
253 cargo_type: CargoType::new(pick_u64(bv, 114, 8) as u8),
254 txrx: {
255 let val = pick_u64(bv, 144, 2) as u8;
256 if val < 4 {
257 val
258 } else {
259 return Err(format!("Tx/Tr mode field out of range: {}", val).into());
260 }
261 },
262 interval: StationInterval::new(pick_u64(bv, 146, 4) as u8)?,
263 quiet: {
264 let val = pick_u64(bv, 150, 4) as u8;
265 match val {
266 0 => None,
267 1..=15 => Some(val),
268 _ => {
269 unreachable!("This should never be reached as all four bit cases are covered (value: {})", val);
270 }
271 }
272 },
273 },
274 ))
275}
276
277#[cfg(test)]
280mod test {
281 use super::*;
282
283 #[test]
284 fn test_parse_vdm_type23() {
285 let mut p = NmeaParser::new();
286 match p.parse_sentence("!AIVDM,1,1,,B,G02:Kn01R`sn@291nj600000900,2*12") {
287 Ok(ps) => {
288 match ps {
289 ParsedMessage::GroupAssignmentCommand(gac) => {
291 assert_eq!(gac.mmsi, 2268120);
292 assert::close(gac.ne_lat.unwrap_or(0.0), 30642.0 / 600.0, 0.1);
293 assert::close(gac.ne_lon.unwrap_or(0.0), 1578.0 / 600.0, 0.1);
294 assert::close(gac.sw_lat.unwrap_or(0.0), 30408.0 / 600.0, 0.1);
295 assert::close(gac.sw_lon.unwrap_or(0.0), 1096.0 / 600.0, 0.1);
296 assert_eq!(gac.station_type, StationType::Regional6);
297 assert_eq!(gac.ship_type, ShipType::NotAvailable);
298 assert_eq!(gac.cargo_type, CargoType::Undefined);
299 assert_eq!(gac.txrx, 0);
300 assert_eq!(gac.interval, StationInterval::NextShorterReportingInverval);
301 assert_eq!(gac.quiet, None);
302 }
303 ParsedMessage::Incomplete => {
304 assert!(false);
305 }
306 _ => {
307 assert!(false);
308 }
309 }
310 }
311 Err(e) => {
312 assert_eq!(e.to_string(), "OK");
313 }
314 }
315 }
316}