Skip to main content

nmea_kit/ais/transmit/
stations.rs

1use crate::EncodeError;
2use crate::ais::encode::{
3    BitWriter, encode_cog, encode_epfd, encode_integer_sog, encode_latitude, encode_long_range_cog,
4    encode_long_range_latitude, encode_long_range_longitude, encode_long_range_sog,
5    encode_longitude, encode_position_timestamp, encode_sar_altitude, encode_utc_day,
6    encode_utc_hour, encode_utc_minute_or_second, encode_utc_month, encode_utc_year,
7};
8use crate::ais::messages::NavigationStatus;
9
10use super::{
11    AisEncodable, AisTransmitOptions, ClassBCommunicationState, PositionTimestamp, encode_payload,
12};
13
14/// AIS Type 4 base-station report.
15#[derive(Debug, Clone, PartialEq)]
16pub struct BaseStation {
17    pub repeat_indicator: u8,
18    pub mmsi: u32,
19    pub year: Option<u16>,
20    pub month: Option<u8>,
21    pub day: Option<u8>,
22    pub hour: Option<u8>,
23    pub minute: Option<u8>,
24    pub second: Option<u8>,
25    pub position_accuracy: bool,
26    pub longitude: Option<f64>,
27    pub latitude: Option<f64>,
28    pub position_fixing_device: u8,
29    pub transmission_control: bool,
30    pub raim: bool,
31    pub communication_state: u32,
32}
33
34/// AIS Type 11 UTC/date response.
35#[derive(Debug, Clone, PartialEq)]
36pub struct UtcDateResponse {
37    pub repeat_indicator: u8,
38    pub mmsi: u32,
39    pub year: Option<u16>,
40    pub month: Option<u8>,
41    pub day: Option<u8>,
42    pub hour: Option<u8>,
43    pub minute: Option<u8>,
44    pub second: Option<u8>,
45    pub position_accuracy: bool,
46    pub longitude: Option<f64>,
47    pub latitude: Option<f64>,
48    pub position_fixing_device: u8,
49    pub transmission_control: bool,
50    pub raim: bool,
51    pub communication_state: u32,
52}
53
54/// AIS Type 9 standard SAR aircraft position report.
55#[derive(Debug, Clone, PartialEq)]
56pub struct SarAircraft {
57    pub repeat_indicator: u8,
58    pub mmsi: u32,
59    pub altitude: Option<u16>,
60    pub sog: Option<u16>,
61    pub position_accuracy: bool,
62    pub longitude: Option<f64>,
63    pub latitude: Option<f64>,
64    pub cog: Option<f32>,
65    pub timestamp: PositionTimestamp,
66    pub regional_application: u8,
67    /// `false` means the DTE is available.
68    pub dte: bool,
69    pub assigned_mode: bool,
70    pub raim: bool,
71    pub communication_state: ClassBCommunicationState,
72}
73
74/// AIS Type 21 aid-to-navigation report.
75#[derive(Debug, Clone, PartialEq)]
76pub struct AidToNavigation {
77    pub repeat_indicator: u8,
78    pub mmsi: u32,
79    pub aid_type: u8,
80    pub name: String,
81    pub longitude: Option<f64>,
82    pub latitude: Option<f64>,
83    pub position_accuracy: bool,
84    pub dimension_to_bow: u16,
85    pub dimension_to_stern: u16,
86    pub dimension_to_port: u8,
87    pub dimension_to_starboard: u8,
88    pub position_fixing_device: u8,
89    pub timestamp: PositionTimestamp,
90    pub off_position: bool,
91    pub regional_application: u8,
92    pub raim: bool,
93    pub virtual_aid: bool,
94    pub assigned_mode: bool,
95    pub name_extension: Option<String>,
96}
97
98/// AIS Type 27 long-range position report.
99#[derive(Debug, Clone, PartialEq)]
100pub struct LongRangePosition {
101    pub mmsi: u32,
102    pub position_accuracy: bool,
103    pub raim: bool,
104    pub navigation_status: NavigationStatus,
105    pub longitude: Option<f64>,
106    pub latitude: Option<f64>,
107    pub sog: Option<u8>,
108    pub cog: Option<u16>,
109    pub gnss_position_status: bool,
110}
111
112struct UtcPositionInput {
113    repeat_indicator: u8,
114    mmsi: u32,
115    year: Option<u16>,
116    month: Option<u8>,
117    day: Option<u8>,
118    hour: Option<u8>,
119    minute: Option<u8>,
120    second: Option<u8>,
121    position_accuracy: bool,
122    longitude: Option<f64>,
123    latitude: Option<f64>,
124    position_fixing_device: u8,
125    transmission_control: bool,
126    raim: bool,
127    communication_state: u32,
128}
129
130impl AisEncodable for BaseStation {
131    fn to_sentences(&self, options: AisTransmitOptions) -> Result<Vec<String>, EncodeError> {
132        encode_utc_position(4, self.utc_position_input(), options)
133    }
134}
135
136impl AisEncodable for UtcDateResponse {
137    fn to_sentences(&self, options: AisTransmitOptions) -> Result<Vec<String>, EncodeError> {
138        encode_utc_position(11, self.utc_position_input(), options)
139    }
140}
141
142impl AisEncodable for SarAircraft {
143    fn to_sentences(&self, options: AisTransmitOptions) -> Result<Vec<String>, EncodeError> {
144        let mut writer = BitWriter::new();
145        let (selector, communication_state) = communication_state_parts(self.communication_state);
146        writer.push_u32(9, 6, "message_type")?;
147        writer.push_u32(u32::from(self.repeat_indicator), 2, "repeat_indicator")?;
148        writer.push_u32(self.mmsi, 30, "mmsi")?;
149        writer.push_u32(encode_sar_altitude(self.altitude)?, 12, "altitude")?;
150        writer.push_u32(encode_integer_sog(self.sog)?, 10, "sog")?;
151        writer.push_bool(self.position_accuracy);
152        writer.push_i32(encode_longitude(self.longitude)?, 28, "longitude")?;
153        writer.push_i32(encode_latitude(self.latitude)?, 27, "latitude")?;
154        writer.push_u32(encode_cog(self.cog)?, 12, "cog")?;
155        writer.push_u32(encode_position_timestamp(self.timestamp)?, 6, "timestamp")?;
156        writer.push_u32(
157            u32::from(self.regional_application),
158            8,
159            "regional_application",
160        )?;
161        writer.push_bool(self.dte);
162        writer.push_spare(3);
163        writer.push_bool(self.assigned_mode);
164        writer.push_bool(self.raim);
165        writer.push_bool(selector);
166        writer.push_u32(communication_state, 19, "communication_state")?;
167        encode_payload(&writer.finish(), options)
168    }
169}
170
171impl AisEncodable for AidToNavigation {
172    fn to_sentences(&self, options: AisTransmitOptions) -> Result<Vec<String>, EncodeError> {
173        let mut writer = BitWriter::new();
174        writer.push_u32(21, 6, "message_type")?;
175        writer.push_u32(u32::from(self.repeat_indicator), 2, "repeat_indicator")?;
176        writer.push_u32(self.mmsi, 30, "mmsi")?;
177        writer.push_u32(u32::from(self.aid_type), 5, "aid_type")?;
178        writer.push_text(&self.name, 20, "name")?;
179        writer.push_bool(self.position_accuracy);
180        writer.push_i32(encode_longitude(self.longitude)?, 28, "longitude")?;
181        writer.push_i32(encode_latitude(self.latitude)?, 27, "latitude")?;
182        writer.push_u32(u32::from(self.dimension_to_bow), 9, "dimension_to_bow")?;
183        writer.push_u32(u32::from(self.dimension_to_stern), 9, "dimension_to_stern")?;
184        writer.push_u32(u32::from(self.dimension_to_port), 6, "dimension_to_port")?;
185        writer.push_u32(
186            u32::from(self.dimension_to_starboard),
187            6,
188            "dimension_to_starboard",
189        )?;
190        writer.push_u32(
191            encode_epfd(self.position_fixing_device)?,
192            4,
193            "position_fixing_device",
194        )?;
195        writer.push_u32(encode_position_timestamp(self.timestamp)?, 6, "timestamp")?;
196        writer.push_bool(self.off_position);
197        writer.push_u32(
198            u32::from(self.regional_application),
199            8,
200            "regional_application",
201        )?;
202        writer.push_bool(self.raim);
203        writer.push_bool(self.virtual_aid);
204        writer.push_bool(self.assigned_mode);
205        writer.push_spare(1);
206        if let Some(extension) = &self.name_extension {
207            let actual_chars = extension.chars().count();
208            if actual_chars > 14 {
209                return Err(EncodeError::AisTextTooLong {
210                    field: "name_extension",
211                    max_chars: 14,
212                    actual_chars,
213                });
214            }
215            writer.push_text(extension, actual_chars, "name_extension")?;
216        }
217        encode_payload(&writer.finish(), options)
218    }
219}
220
221impl AisEncodable for LongRangePosition {
222    fn to_sentences(&self, options: AisTransmitOptions) -> Result<Vec<String>, EncodeError> {
223        let mut writer = BitWriter::new();
224        writer.push_u32(27, 6, "message_type")?;
225        writer.push_u32(3, 2, "repeat_indicator")?;
226        writer.push_u32(self.mmsi, 30, "mmsi")?;
227        writer.push_bool(self.position_accuracy);
228        writer.push_bool(self.raim);
229        writer.push_u32(
230            u32::from(u8::from(self.navigation_status)),
231            4,
232            "navigation_status",
233        )?;
234        writer.push_i32(
235            encode_long_range_longitude(self.longitude)?,
236            18,
237            "longitude",
238        )?;
239        writer.push_i32(encode_long_range_latitude(self.latitude)?, 17, "latitude")?;
240        writer.push_u32(encode_long_range_sog(self.sog)?, 6, "sog")?;
241        writer.push_u32(encode_long_range_cog(self.cog)?, 9, "cog")?;
242        writer.push_bool(self.gnss_position_status);
243        writer.push_spare(1);
244        encode_payload(&writer.finish(), options)
245    }
246}
247
248fn encode_utc_position(
249    message_type: u32,
250    input: UtcPositionInput,
251    options: AisTransmitOptions,
252) -> Result<Vec<String>, EncodeError> {
253    let mut writer = BitWriter::new();
254    writer.push_u32(message_type, 6, "message_type")?;
255    writer.push_u32(u32::from(input.repeat_indicator), 2, "repeat_indicator")?;
256    writer.push_u32(input.mmsi, 30, "mmsi")?;
257    writer.push_u32(encode_utc_year(input.year)?, 14, "year")?;
258    writer.push_u32(encode_utc_month(input.month)?, 4, "month")?;
259    writer.push_u32(encode_utc_day(input.day)?, 5, "day")?;
260    writer.push_u32(encode_utc_hour(input.hour)?, 5, "hour")?;
261    writer.push_u32(
262        encode_utc_minute_or_second(input.minute, "minute")?,
263        6,
264        "minute",
265    )?;
266    writer.push_u32(
267        encode_utc_minute_or_second(input.second, "second")?,
268        6,
269        "second",
270    )?;
271    writer.push_bool(input.position_accuracy);
272    writer.push_i32(encode_longitude(input.longitude)?, 28, "longitude")?;
273    writer.push_i32(encode_latitude(input.latitude)?, 27, "latitude")?;
274    writer.push_u32(
275        encode_epfd(input.position_fixing_device)?,
276        4,
277        "position_fixing_device",
278    )?;
279    writer.push_bool(input.transmission_control);
280    writer.push_spare(9);
281    writer.push_bool(input.raim);
282    writer.push_u32(input.communication_state, 19, "communication_state")?;
283    encode_payload(&writer.finish(), options)
284}
285
286trait UtcPositionSource {
287    fn utc_position_input(&self) -> UtcPositionInput;
288}
289
290impl UtcPositionSource for BaseStation {
291    fn utc_position_input(&self) -> UtcPositionInput {
292        UtcPositionInput {
293            repeat_indicator: self.repeat_indicator,
294            mmsi: self.mmsi,
295            year: self.year,
296            month: self.month,
297            day: self.day,
298            hour: self.hour,
299            minute: self.minute,
300            second: self.second,
301            position_accuracy: self.position_accuracy,
302            longitude: self.longitude,
303            latitude: self.latitude,
304            position_fixing_device: self.position_fixing_device,
305            transmission_control: self.transmission_control,
306            raim: self.raim,
307            communication_state: self.communication_state,
308        }
309    }
310}
311
312impl UtcPositionSource for UtcDateResponse {
313    fn utc_position_input(&self) -> UtcPositionInput {
314        UtcPositionInput {
315            repeat_indicator: self.repeat_indicator,
316            mmsi: self.mmsi,
317            year: self.year,
318            month: self.month,
319            day: self.day,
320            hour: self.hour,
321            minute: self.minute,
322            second: self.second,
323            position_accuracy: self.position_accuracy,
324            longitude: self.longitude,
325            latitude: self.latitude,
326            position_fixing_device: self.position_fixing_device,
327            transmission_control: self.transmission_control,
328            raim: self.raim,
329            communication_state: self.communication_state,
330        }
331    }
332}
333
334fn communication_state_parts(value: ClassBCommunicationState) -> (bool, u32) {
335    match value {
336        ClassBCommunicationState::Sotdma(value) => (false, value),
337        ClassBCommunicationState::Itdma(value) => (true, value),
338    }
339}