nmea_parser/ais/
vdm_t24.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*/
16use super::*;
17
18/// AIS VDM/VDO type 24: Static data report
19pub(crate) fn handle(
20    bv: &BitVec,
21    _station: Station,
22    store: &mut NmeaParser,
23    own_vessel: bool,
24) -> Result<ParsedMessage, ParseError> {
25    // Check whether the message bit layout follows part A or part B format
26    // We use two complementary booleans to make the code more readable.
27    let (part_a, part_b) = match pick_u64(bv, 38, 2) {
28        0 => (true, false),
29        1 => (false, true),
30        _ => {
31            return Err(format!(
32                "AIVDM type 24 part number has unexpected value: {}",
33                pick_u64(bv, 38, 2)
34            )
35            .into());
36        }
37    };
38
39    // Pick the fields
40    let vsd = VesselStaticData {
41        own_vessel,
42        ais_type: AisClass::ClassB,
43        mmsi: pick_u64(bv, 8, 30) as u32,
44        ais_version_indicator: 0,
45        imo_number: None,
46        call_sign: {
47            if part_b {
48                let raw = pick_string(bv, 90, 7);
49                match raw.as_str() {
50                    "" => None,
51                    _ => Some(raw),
52                }
53            } else {
54                None
55            }
56        },
57        name: {
58            if part_a {
59                let raw = pick_string(bv, 40, 120);
60                match raw.as_str() {
61                    "" => None,
62                    _ => Some(raw),
63                }
64            } else {
65                None
66            }
67        },
68        ship_type: {
69            if part_b {
70                ShipType::new(pick_u64(bv, 40, 8) as u8)
71            } else {
72                ShipType::NotAvailable
73            }
74        },
75        cargo_type: {
76            if part_b {
77                CargoType::new(pick_u64(bv, 40, 8) as u8)
78            } else {
79                CargoType::Undefined
80            }
81        },
82        equipment_vendor_id: {
83            if part_b {
84                Some(pick_string(bv, 48, 3))
85            } else {
86                None
87            }
88        },
89        equipment_model: {
90            if part_b {
91                Some(pick_u64(bv, 66, 4) as u8)
92            } else {
93                None
94            }
95        },
96        equipment_serial_number: {
97            if part_b {
98                Some(pick_u64(bv, 70, 20) as u32)
99            } else {
100                None
101            }
102        },
103        dimension_to_bow: {
104            if part_b {
105                Some(pick_u64(bv, 132, 9) as u16)
106            } else {
107                None
108            }
109        },
110        dimension_to_stern: {
111            if part_b {
112                Some(pick_u64(bv, 141, 9) as u16)
113            } else {
114                None
115            }
116        },
117        dimension_to_port: {
118            if part_b {
119                Some(pick_u64(bv, 150, 6) as u16)
120            } else {
121                None
122            }
123        },
124        dimension_to_starboard: {
125            if part_b {
126                Some(pick_u64(bv, 156, 6) as u16)
127            } else {
128                None
129            }
130        },
131        position_fix_type: None,
132        eta: None,
133        draught10: None,
134        destination: None,
135        mothership_mmsi: {
136            if part_b {
137                Some(pick_u64(bv, 132, 30) as u32)
138            } else {
139                None
140            }
141        },
142    };
143
144    // Check whether we can return a complete or incomplete response
145    if let Some(vsd2) = store.pull_vsd(vsd.mmsi) {
146        Ok(ParsedMessage::VesselStaticData(vsd.merge(&vsd2)?))
147    } else {
148        store.push_vsd(vsd.mmsi, vsd);
149        Ok(ParsedMessage::Incomplete)
150    }
151}
152
153impl VesselStaticData {
154    /// Merge two data structures together. This is used to combine part A and B
155    /// of class B AIVDM type 24 messages.
156    fn merge(&self, other: &VesselStaticData) -> Result<VesselStaticData, String> {
157        if self.ais_type != other.ais_type {
158            Err(format!(
159                "Mismatching AIS types: {} != {}",
160                self.ais_type, other.ais_type
161            ))
162        } else if self.mmsi != other.mmsi {
163            Err(format!(
164                "Mismatching MMSI numbers: {} != {}",
165                self.mmsi, other.mmsi
166            ))
167        } else if self.imo_number != other.imo_number {
168            Err(format!(
169                "Mismatching IMO numbers: {} != {}",
170                self.mmsi, other.mmsi
171            ))
172        } else if self.ais_version_indicator != other.ais_version_indicator {
173            Err(format!(
174                "Mismatching AIS version indicators: {} != {}",
175                self.ais_version_indicator, other.ais_version_indicator
176            ))
177        } else {
178            Ok(VesselStaticData {
179                own_vessel: self.own_vessel,
180                ais_type: self.ais_type,
181                mmsi: self.mmsi,
182                ais_version_indicator: self.ais_version_indicator,
183                imo_number: choose_some(self.imo_number, other.imo_number),
184                call_sign: choose_some_string(&self.call_sign, &other.call_sign),
185                name: choose_some_string(&self.name, &other.name),
186                ship_type: {
187                    if self.ship_type != ShipType::NotAvailable {
188                        self.ship_type
189                    } else {
190                        other.ship_type
191                    }
192                },
193                cargo_type: {
194                    if self.cargo_type != CargoType::Undefined {
195                        self.cargo_type
196                    } else {
197                        other.cargo_type
198                    }
199                },
200                equipment_vendor_id: choose_some_string(
201                    &self.equipment_vendor_id,
202                    &other.equipment_vendor_id,
203                ),
204                equipment_model: choose_some(self.equipment_model, other.equipment_model),
205                equipment_serial_number: choose_some(
206                    self.equipment_serial_number,
207                    other.equipment_serial_number,
208                ),
209                dimension_to_bow: choose_some(self.dimension_to_bow, other.dimension_to_bow),
210                dimension_to_stern: choose_some(self.dimension_to_stern, other.dimension_to_stern),
211                dimension_to_port: choose_some(self.dimension_to_port, other.dimension_to_port),
212                dimension_to_starboard: choose_some(
213                    self.dimension_to_starboard,
214                    other.dimension_to_starboard,
215                ),
216                position_fix_type: choose_some(self.position_fix_type, other.position_fix_type),
217                eta: choose_some(self.eta, other.eta),
218                draught10: choose_some(self.draught10, other.draught10),
219                destination: choose_some_string(&self.destination, &other.destination),
220                mothership_mmsi: choose_some(self.mothership_mmsi, other.mothership_mmsi),
221            })
222        }
223    }
224}
225
226/// Choose the argument which is Some. If both are Some, choose the first one.
227fn choose_some<T>(a: Option<T>, b: Option<T>) -> Option<T> {
228    if a.is_some() {
229        a
230    } else {
231        b
232    }
233}
234
235/// Choose the argument which is Some. If both are Some, choose the first one.
236fn choose_some_string(a: &Option<String>, b: &Option<String>) -> Option<String> {
237    if a.is_some() {
238        a.clone()
239    } else {
240        b.clone()
241    }
242}
243
244// -------------------------------------------------------------------------------------------------
245
246#[cfg(test)]
247mod test {
248    use super::*;
249
250    #[test]
251    fn test_parse_vdm_type24() {
252        let mut p = NmeaParser::new();
253
254        let s1 = "!AIVDM,1,1,,A,H42O55i18tMET00000000000000,2*6D";
255        match p.parse_sentence(s1) {
256            Ok(ps) => {
257                match ps {
258                    // The expected result
259                    ParsedMessage::VesselStaticData(_) => {
260                        assert!(false);
261                        return;
262                    }
263                    ParsedMessage::Incomplete => {
264                        // As expected
265                    }
266                    _ => {
267                        assert!(false);
268                        return;
269                    }
270                }
271            }
272            Err(e) => {
273                assert_eq!(e.to_string(), "OK");
274                return;
275            }
276        }
277        let s2 = "!AIVDM,1,1,,A,H42O55lti4hhhilD3nink000?050,0*40";
278        match p.parse_sentence(s2) {
279            Ok(ps) => {
280                match ps {
281                    // The expected result
282                    ParsedMessage::VesselStaticData(vsd) => {
283                        assert_eq!(vsd.mmsi, 271041815);
284                        assert_eq!(vsd.ais_version_indicator, 0);
285                        assert_eq!(vsd.imo_number, None);
286                        assert_eq!(vsd.call_sign, Some("TC6163".into()));
287                        assert_eq!(vsd.name, Some("PROGUY".into()));
288                        assert_eq!(vsd.ship_type, ShipType::Passenger);
289                        assert_eq!(vsd.cargo_type, CargoType::Undefined);
290
291                        assert_eq!(vsd.equipment_vendor_id, Some("1D0".into()));
292                        //                                assert_eq!(vsd.equipment_model, None);
293                        //                                assert_eq!(vsd.equipment_serial_number, None);
294                        //                                assert_eq!(vsd.mothership_mmsi, None);
295                        // TODO: find the right hand side of the variables above
296
297                        assert_eq!(vsd.dimension_to_bow, Some(0));
298                        assert_eq!(vsd.dimension_to_stern, Some(15));
299                        assert_eq!(vsd.dimension_to_port, Some(0));
300                        assert_eq!(vsd.dimension_to_starboard, Some(5));
301
302                        assert_eq!(vsd.position_fix_type, None);
303                        assert_eq!(vsd.eta, None);
304                        assert_eq!(vsd.draught10, None);
305                        assert_eq!(vsd.destination, None);
306                    }
307                    ParsedMessage::Incomplete => {
308                        assert!(false);
309                    }
310                    _ => {
311                        assert!(false);
312                    }
313                }
314            }
315            Err(e) => {
316                assert_eq!(e.to_string(), "OK");
317            }
318        }
319    }
320}