Skip to main content

nmea_kit/nmea/sentences/
dse.rs

1use crate::nmea::field::{FieldReader, FieldWriter, NmeaEncodable};
2
3/// A single dataset within a DSE sentence.
4#[derive(Debug, Clone, PartialEq)]
5pub struct DseDataSet {
6    /// Data code.
7    pub code: Option<String>,
8    /// Data value.
9    pub data: Option<String>,
10}
11
12/// DSE — Expanded DSC.
13///
14/// Wire: `total,number,ack,mmsi[,code,data,…]`
15/// Field layout follows the NMEA 0183 definitions catalogued by
16/// [pynmeagps](https://github.com/semuconsulting/pynmeagps/blob/master/src/pynmeagps/nmeatypes_get.py).
17#[derive(Debug, Clone, PartialEq)]
18pub struct Dse {
19    /// Total number of sentences.
20    pub total: Option<u8>,
21    /// Sentence number.
22    pub number: Option<u8>,
23    /// Acknowledgement (A=acknowledged, B=not acknowledged).
24    pub ack: Option<char>,
25    /// MMSI of sending station.
26    pub mmsi: Option<String>,
27    /// Expanded data sets (variable length, code+data pairs).
28    pub datasets: Vec<DseDataSet>,
29}
30
31impl Dse {
32    /// Parse fields from a decoded NMEA frame.
33    /// Always returns `Some`; missing or malformed fields become `None`.
34    pub fn parse(fields: &[&str]) -> Option<Self> {
35        let mut r = FieldReader::new(fields);
36        let total = r.u8();
37        let number = r.u8();
38        let ack = r.char();
39        let mmsi = r.string();
40        let mut datasets = Vec::new();
41        loop {
42            let code = r.string();
43            if code.is_none() {
44                break;
45            }
46            datasets.push(DseDataSet {
47                code,
48                data: r.string(),
49            });
50        }
51        Some(Self {
52            total,
53            number,
54            ack,
55            mmsi,
56            datasets,
57        })
58    }
59}
60
61impl NmeaEncodable for Dse {
62    const SENTENCE_TYPE: &str = "DSE";
63
64    fn encode(&self) -> Result<Vec<String>, crate::EncodeError> {
65        let mut w = FieldWriter::new();
66        w.u8(self.total);
67        w.u8(self.number);
68        w.char(self.ack);
69        w.string(self.mmsi.as_deref());
70        for ds in &self.datasets {
71            w.string(ds.code.as_deref());
72            w.string(ds.data.as_deref());
73        }
74        w.finish()
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::parse_frame;
82
83    #[test]
84    fn dse_empty() {
85        let s = Dse {
86            total: None,
87            number: None,
88            ack: None,
89            mmsi: None,
90            datasets: vec![],
91        }
92        .to_sentence("CD")
93        .expect("encode");
94        let f = parse_frame(s.trim()).expect("valid");
95        let d = Dse::parse(&f.fields).expect("parse");
96        assert!(d.total.is_none());
97        assert!(d.mmsi.is_none());
98        assert!(d.datasets.is_empty());
99    }
100
101    #[test]
102    fn dse_encode_roundtrip() {
103        let original = Dse {
104            total: Some(1),
105            number: Some(1),
106            ack: Some('A'),
107            mmsi: Some("3380400790".to_string()),
108            datasets: vec![DseDataSet {
109                code: Some("00".to_string()),
110                data: Some("46504437".to_string()),
111            }],
112        };
113        let sentence = original.to_sentence("CD").expect("encode");
114        let frame = parse_frame(sentence.trim()).expect("re-parse");
115        let parsed = Dse::parse(&frame.fields).expect("parse");
116        assert_eq!(original, parsed);
117    }
118
119    #[test]
120    fn dse_cddse_gonmea() {
121        let f = parse_frame("$CDDSE,1,1,A,3380400790,00,46504437*15").expect("valid DSE");
122        let d = Dse::parse(&f.fields).expect("parse DSE");
123        assert_eq!(d.total, Some(1));
124        assert_eq!(d.number, Some(1));
125        assert_eq!(d.ack, Some('A'));
126        assert_eq!(d.mmsi, Some("3380400790".to_string()));
127        assert_eq!(d.datasets.len(), 1);
128        assert_eq!(d.datasets[0].code, Some("00".to_string()));
129        assert_eq!(d.datasets[0].data, Some("46504437".to_string()));
130    }
131
132    #[test]
133    fn dse_multiple_datasets_gonmea() {
134        let f =
135            parse_frame("$CDDSE,1,1,A,3380400790,00,46504437,01,16501437*17").expect("valid DSE");
136        let d = Dse::parse(&f.fields).expect("parse DSE");
137        assert_eq!(d.mmsi, Some("3380400790".to_string()));
138        assert_eq!(d.datasets.len(), 2);
139        assert_eq!(d.datasets[0].code, Some("00".to_string()));
140        assert_eq!(d.datasets[0].data, Some("46504437".to_string()));
141        assert_eq!(d.datasets[1].code, Some("01".to_string()));
142        assert_eq!(d.datasets[1].data, Some("16501437".to_string()));
143    }
144}