Skip to main content

nmea_kit/ais/transmit/
class_a.rs

1use crate::EncodeError;
2use crate::ais::encode::{
3    BitWriter, encode_cog, encode_epfd, encode_heading, encode_latitude, encode_longitude,
4    encode_rot, encode_sog, encode_timestamp,
5};
6use crate::ais::messages::NavigationStatus;
7
8use super::{AisEncodable, AisTransmitOptions, encode_payload};
9
10/// Class A dynamic position-report type.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ClassAPositionType {
13    PositionReport,
14    AssignedPositionReport,
15    SpecialPositionReport,
16}
17
18impl ClassAPositionType {
19    fn id(self) -> u32 {
20        match self {
21            Self::PositionReport => 1,
22            Self::AssignedPositionReport => 2,
23            Self::SpecialPositionReport => 3,
24        }
25    }
26}
27
28/// AIS Type 1, 2, or 3 Class A position report.
29#[derive(Debug, Clone, PartialEq)]
30pub struct ClassAPosition {
31    pub message_type: ClassAPositionType,
32    pub repeat_indicator: u8,
33    pub mmsi: u32,
34    pub navigation_status: NavigationStatus,
35    /// Raw AIS ROTAIS value. `None` encodes the not-available value.
36    pub rate_of_turn: Option<i8>,
37    pub sog: Option<f32>,
38    pub position_accuracy: bool,
39    pub longitude: Option<f64>,
40    pub latitude: Option<f64>,
41    pub cog: Option<f32>,
42    pub heading: Option<u16>,
43    pub timestamp: Option<u8>,
44    pub maneuver_indicator: u8,
45    pub raim: bool,
46    /// Raw 19-bit SOTDMA communication state.
47    pub communication_state: u32,
48}
49
50/// AIS Type 5 Class A static and voyage-related data.
51#[derive(Debug, Clone, PartialEq)]
52pub struct ClassAStaticVoyage {
53    pub repeat_indicator: u8,
54    pub mmsi: u32,
55    pub ais_version: u8,
56    pub imo: u32,
57    pub callsign: String,
58    pub vessel_name: String,
59    pub ship_type: u8,
60    pub dimension_to_bow: u16,
61    pub dimension_to_stern: u16,
62    pub dimension_to_port: u8,
63    pub dimension_to_starboard: u8,
64    pub position_fixing_device: u8,
65    pub eta_month: Option<u8>,
66    pub eta_day: Option<u8>,
67    pub eta_hour: Option<u8>,
68    pub eta_minute: Option<u8>,
69    pub draught_meters: Option<f32>,
70    pub destination: String,
71    /// `false` means data terminal equipment is available.
72    pub dte: bool,
73}
74
75impl AisEncodable for ClassAPosition {
76    fn to_sentences(&self, options: AisTransmitOptions) -> Result<Vec<String>, EncodeError> {
77        let mut writer = BitWriter::new();
78        writer.push_u32(self.message_type.id(), 6, "message_type")?;
79        writer.push_u32(u32::from(self.repeat_indicator), 2, "repeat_indicator")?;
80        writer.push_u32(self.mmsi, 30, "mmsi")?;
81        writer.push_u32(
82            u32::from(u8::from(self.navigation_status)),
83            4,
84            "navigation_status",
85        )?;
86        writer.push_i32(encode_rot(self.rate_of_turn)?, 8, "rate_of_turn")?;
87        writer.push_u32(encode_sog(self.sog)?, 10, "sog")?;
88        writer.push_bool(self.position_accuracy);
89        writer.push_i32(encode_longitude(self.longitude)?, 28, "longitude")?;
90        writer.push_i32(encode_latitude(self.latitude)?, 27, "latitude")?;
91        writer.push_u32(encode_cog(self.cog)?, 12, "cog")?;
92        writer.push_u32(encode_heading(self.heading)?, 9, "heading")?;
93        writer.push_u32(encode_timestamp(self.timestamp)?, 6, "timestamp")?;
94        writer.push_u32(u32::from(self.maneuver_indicator), 2, "maneuver_indicator")?;
95        writer.push_spare(3);
96        writer.push_bool(self.raim);
97        writer.push_u32(self.communication_state, 19, "communication_state")?;
98        encode_payload(&writer.finish(), options)
99    }
100}
101
102impl AisEncodable for ClassAStaticVoyage {
103    fn to_sentences(&self, options: AisTransmitOptions) -> Result<Vec<String>, EncodeError> {
104        let mut writer = BitWriter::new();
105        writer.push_u32(5, 6, "message_type")?;
106        writer.push_u32(u32::from(self.repeat_indicator), 2, "repeat_indicator")?;
107        writer.push_u32(self.mmsi, 30, "mmsi")?;
108        writer.push_u32(u32::from(self.ais_version), 2, "ais_version")?;
109        writer.push_u32(self.imo, 30, "imo")?;
110        writer.push_text(&self.callsign, 7, "callsign")?;
111        writer.push_text(&self.vessel_name, 20, "vessel_name")?;
112        writer.push_u32(u32::from(self.ship_type), 8, "ship_type")?;
113        writer.push_u32(u32::from(self.dimension_to_bow), 9, "dimension_to_bow")?;
114        writer.push_u32(u32::from(self.dimension_to_stern), 9, "dimension_to_stern")?;
115        writer.push_u32(u32::from(self.dimension_to_port), 6, "dimension_to_port")?;
116        writer.push_u32(
117            u32::from(self.dimension_to_starboard),
118            6,
119            "dimension_to_starboard",
120        )?;
121        writer.push_u32(
122            encode_epfd(self.position_fixing_device)?,
123            4,
124            "position_fixing_device",
125        )?;
126        writer.push_u32(
127            u32::from(encode_eta_part(self.eta_month, 1, 12, 0, "eta_month")?),
128            4,
129            "eta_month",
130        )?;
131        writer.push_u32(
132            u32::from(encode_eta_part(self.eta_day, 1, 31, 0, "eta_day")?),
133            5,
134            "eta_day",
135        )?;
136        writer.push_u32(
137            u32::from(encode_eta_part(self.eta_hour, 0, 23, 24, "eta_hour")?),
138            5,
139            "eta_hour",
140        )?;
141        writer.push_u32(
142            u32::from(encode_eta_part(self.eta_minute, 0, 59, 60, "eta_minute")?),
143            6,
144            "eta_minute",
145        )?;
146        writer.push_u32(encode_draught(self.draught_meters)?, 8, "draught_meters")?;
147        writer.push_text(&self.destination, 20, "destination")?;
148        writer.push_bool(self.dte);
149        writer.push_spare(1);
150        encode_payload(&writer.finish(), options)
151    }
152}
153
154fn encode_eta_part(
155    value: Option<u8>,
156    minimum: u8,
157    maximum: u8,
158    not_available: u8,
159    field: &'static str,
160) -> Result<u8, EncodeError> {
161    match value {
162        None => Ok(not_available),
163        Some(value) if (minimum..=maximum).contains(&value) => Ok(value),
164        Some(_) => Err(EncodeError::InvalidAisField(field)),
165    }
166}
167
168fn encode_draught(value: Option<f32>) -> Result<u32, EncodeError> {
169    match value {
170        None => Ok(0),
171        Some(value) if value.is_finite() && (0.1..=25.5).contains(&value) => {
172            Ok((value * 10.0).round() as u32)
173        }
174        Some(_) => Err(EncodeError::InvalidAisField("draught_meters")),
175    }
176}