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#[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#[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 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 pub communication_state: u32,
48}
49
50#[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 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}