Skip to content

Commit

Permalink
Merge pull request squidpickles#19 from salsabiljb/SAR_type_9
Browse files Browse the repository at this point in the history
feat: add support for msg type 9
  • Loading branch information
squidpickles authored Aug 24, 2024
2 parents 292f6dd + d2d3943 commit d60f303
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/messages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod static_and_voyage_related_data;
pub mod static_data_report;
pub mod types;
pub mod utc_date_response;
pub mod standard_aircraft_position_report;

pub use parsers::message_type;

Expand All @@ -42,6 +43,7 @@ pub enum AisMessage {
AidToNavigationReport(aid_to_navigation_report::AidToNavigationReport),
StaticDataReport(static_data_report::StaticDataReport),
UtcDateResponse(utc_date_response::UtcDateResponse),
StandardAircraftPositionReport(standard_aircraft_position_report::SARPositionReport),
}

/// Trait that describes specific types of AIS messages
Expand Down Expand Up @@ -71,6 +73,9 @@ pub fn parse(unarmored: &[u8]) -> Result<AisMessage> {
8 => Ok(AisMessage::BinaryBroadcastMessage(
binary_broadcast_message::BinaryBroadcastMessage::parse(unarmored)?,
)),
9 => Ok(AisMessage::StandardAircraftPositionReport(
standard_aircraft_position_report::SARPositionReport::parse(unarmored)?,
)),
11 => Ok(AisMessage::UtcDateResponse(
utc_date_response::UtcDateResponse::parse(unarmored)?,
)),
Expand Down
2 changes: 1 addition & 1 deletion src/messages/radio_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl ItdmaMessage {

pub fn parse_radio(input: (&[u8], usize), msg_type: u8) -> IResult<(&[u8], usize), RadioStatus> {
match msg_type {
1 | 2 | 4 | 11 => SotdmaMessage::parse(input),
1 | 2 | 4 | 11 | 9 => SotdmaMessage::parse(input),
3 => ItdmaMessage::parse(input),
_ => Err(nom::Err::Failure(nom::error::Error::new(
input,
Expand Down
143 changes: 143 additions & 0 deletions src/messages/standard_aircraft_position_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//! Standard SAR Aircraft Position Report (type 9)
use super::navigation::*;
use super::parsers::*;
use super::radio_status::*;

use super::AisMessageType;
use crate::errors::Result;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use super::types::Dte;
use super::types::AssignedMode;


#[derive(Debug, PartialEq)]
pub struct SARPositionReport {
pub message_type: u8,
pub repeat_indicator: u8,
pub mmsi: u32,
pub altitude: Option<u16>,
pub speed_over_ground: Option<f32>,
pub position_accuracy: Accuracy,
pub longitude: Option<f32>,
pub latitude: Option<f32>,
pub course_over_ground: Option<f32>,
pub timestamp: u8,
pub dte: Dte,
pub assigned_mode: AssignedMode,
pub raim: bool,
pub radio_status: RadioStatus,
}


impl<'a> AisMessageType<'a> for SARPositionReport {
fn name(&self) -> &'static str {
"Standard SAR Aircraft Position Report"
}

fn parse(data: &'a [u8]) -> Result<Self> {
let (_, report) = parse_base(data)?;
Ok(report)
}
}


fn parse_base(data: &[u8]) -> IResult<&[u8], SARPositionReport> {
bits(move |data| -> IResult<_, _> {
let (data, message_type) = take_bits(6u8)(data)?;
let (data, repeat_indicator) = take_bits(2u8)(data)?;
let (data, mmsi) = take_bits(30u32)(data)?;
let (data, altitude) = map(take_bits(12u16), parse_altitude)(data)?;
let (data, speed_over_ground) = map(take_bits(10u16), parse_speed_over_ground_sar)(data)?;
let (data, position_accuracy) = map(take_bits(1u8), Accuracy::parse)(data)?;
let (data, longitude) = map(|data| signed_i32(data, 28), parse_longitude)(data)?;
let (data, latitude) = map(|data| signed_i32(data, 27), parse_latitude)(data)?;
let (data, course_over_ground) = map(take_bits(12u16), parse_cog)(data)?;
let (data, timestamp) = take_bits(6u8)(data)?;
let (data, _regional_reserved) = take_bits::<_, u8, _, _>(8u8)(data)?;
let (data, dte) = map(take_bits::<_, u8, _, _>(1u8), Into::into)(data)?;
let (data, _spare) = take_bits::<_, u8, _, _>(3u8)(data)?;
let (data, assigned_mode) = map(take_bits(1u8), AssignedMode::parse)(data)?;
let (data, raim) = map(take_bits(1u8), u8_to_bool)(data)?;
let (data, radio_status) = parse_radio(data, message_type)?;

Ok((
data,
SARPositionReport {
message_type,
repeat_indicator,
mmsi,
altitude,
speed_over_ground,
position_accuracy,
longitude,
latitude,
course_over_ground,
timestamp,
dte,
assigned_mode,
raim,
radio_status,
},
))
})(data)
}

/// Parse the altitude field
fn parse_altitude(data: u16) -> Option<u16> {
match data {
4095 => None, // Altitude not available
4094 => Some(4094), // 4094 meters or higher
_ => Some(data),
}
}

/// Parse the speed over ground for SAR Position Report (type 9)
fn parse_speed_over_ground_sar(data: u16) -> Option<f32> {
match data {
1023 => None, // Speed not available
1022 => Some(1022.0), // 1022 knots or higher
_ => Some(data as f32),
}
}

#[cfg(test)]
mod tests {
#![allow(clippy::unreadable_literal)]
use super::*;
use crate::messages::radio_status::{SubMessage, SyncState};
use crate::test_helpers::*;

#[test]
fn test_type9_example() {
let bytestream = b"91b55wi;hbOS@OdQAC062Ch2089h";
let bitstream = crate::messages::unarmor(bytestream, 0).unwrap();
let report = SARPositionReport::parse(bitstream.as_ref()).unwrap();

assert_eq!(report.message_type, 9);
assert_eq!(report.repeat_indicator, 0);
assert_eq!(report.mmsi, 111232511);
assert_eq!(report.altitude, Some(303));
assert_eq!(report.speed_over_ground, Some(42.0));
assert_eq!(report.position_accuracy, Accuracy::Unaugmented);
f32_equal_naive(report.longitude.unwrap(), -6.2788434);
f32_equal_naive(report.latitude.unwrap(), 58.144);
assert_eq!(report.course_over_ground, Some(154.5));
assert_eq!(report.timestamp, 15);
assert_eq!(report.dte, Dte::NotReady);
if let RadioStatus::Sotdma(radio_status) = report.radio_status {
assert_eq!(radio_status.sync_state, SyncState::UtcDirect);
assert_eq!(radio_status.slot_timeout, 1);
if let SubMessage::UtcHourAndMinute(hour, minute) = radio_status.sub_message {
assert_eq!(hour, 0);
assert_eq!(minute, 14);
} else {
panic!("Expected UTC Hour and Minute submessage");
}
} else {
panic!("Expected SOTDMA message");
}
assert!(!report.raim);
}
}

0 comments on commit d60f303

Please sign in to comment.