nmea_parser/ais/
vdm_t23.rs

1/*
2Copyright 2020 Timo Saarinen
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use super::*;
18
19// -------------------------------------------------------------------------------------------------
20
21/// Type 23: Group Assignment Command
22#[derive(Default, Clone, Debug, PartialEq)]
23pub struct GroupAssignmentCommand {
24    /// True if the data is about own vessel, false if about other.
25    pub own_vessel: bool,
26
27    /// AIS station type.
28    pub station: Station,
29
30    /// User ID (30 bits)
31    pub mmsi: u32,
32
33    /// Northeast latitude to 0.1 minutes.
34    pub ne_lat: Option<f64>,
35
36    /// Northeast longitude to 0.1 minutes.
37    pub ne_lon: Option<f64>,
38
39    /// Southwest latitude to 0.1 minutes.
40    pub sw_lat: Option<f64>,
41
42    /// Southwest longitude to 0.1 minutes.
43    pub sw_lon: Option<f64>,
44
45    /// AIS station type.
46    pub station_type: StationType,
47
48    /// Ship type
49    pub ship_type: ShipType,
50
51    /// Cargo type
52    pub cargo_type: CargoType,
53
54    /// TxRx mode:
55    /// 0 = TxA/TxB, RxA/RxB (default)
56    /// 1 = TxA, RxA/RxB
57    /// 2 = TxB, RxA/RxB
58    /// 3 = Reserved for future use
59    pub txrx: u8,
60
61    /// Report interval.
62    pub interval: StationInterval,
63
64    /// Quiet time specifies how many minutes the affected stations are to remain silent.
65    /// None = none
66    /// 1-15 = quiet time in minutes
67    pub quiet: Option<u8>,
68}
69
70/// Station Type (for message type 23).
71#[derive(Clone, Copy, Debug, PartialEq)]
72pub enum StationType {
73    /// All types of mobiles (default)
74    AllTypes,
75
76    /// Reserved for future use
77    Reserved1,
78
79    /// All types of Class B mobile stations
80    AllTypesOfClassBMobile,
81
82    /// SAR airborne mobile station
83    SarAirborneMobile,
84
85    /// Aid to Navigation station
86    AidToNavigation,
87
88    /// Class B shopborne mobile station (IEC62287 only)
89    ClassBShipBorneMobile,
90
91    /// Regional use and inland waterways
92    Regional6,
93
94    /// Regional use and inland waterways
95    Regional7,
96
97    /// Regional use and inland waterways
98    Regional8,
99
100    /// Regional use and inland waterways
101    Regional9,
102
103    /// Reserved for future use
104    Reserved10,
105
106    /// Reserved for future use
107    Reserved11,
108
109    /// Reserved for future use
110    Reserved12,
111
112    /// Reserved for future use
113    Reserved13,
114
115    /// Reserved for future use
116    Reserved14,
117
118    /// Reserved for future use
119    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/// Station interval (for message type 23)
153#[derive(Clone, Copy, Debug, PartialEq)]
154pub enum StationInterval {
155    /// As given by the autonomous mode
156    Autonomous,
157
158    /// 10 minutes
159    Time10min,
160
161    /// 6 minutes
162    Time6min,
163
164    /// 3 minutes
165    Time3min,
166
167    /// 1 minute
168    Time1min,
169
170    /// 30 seconds
171    Time30sec,
172
173    /// 15 seconds
174    Time15sec,
175
176    /// 10 seconds
177    Time10sec,
178
179    /// 5 seconds
180    Time5sec,
181
182    /// Next shorter reporting interval
183    NextShorterReportingInverval,
184
185    /// Next longer reporting interval
186    NextLongerReportingInverval,
187
188    /// Reserved for future use
189    Reserved11,
190
191    /// Reserved for future use
192    Reserved12,
193
194    /// Reserved for future use
195    Reserved13,
196
197    /// Reserved for future use
198    Reserved14,
199
200    /// Reserved for future use
201    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
234// -------------------------------------------------------------------------------------------------
235
236/// AIS VDM/VDO type 23: Group Assignment Command
237pub(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// -------------------------------------------------------------------------------------------------
278
279#[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                    // The expected result
290                    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}