Skip to content

Commit

Permalink
Add serde::Serialize support for AisMessage
Browse files Browse the repository at this point in the history
  • Loading branch information
salsabiljb committed Nov 11, 2024
1 parent 175072f commit 2cc0521
Show file tree
Hide file tree
Showing 29 changed files with 201 additions and 90 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ default = ["std"]
[dependencies]
nom = { version = "7", default-features = false }
heapless = { version = "0.7" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"


[[bin]]
name = "aisparser"
Expand Down
48 changes: 27 additions & 21 deletions src/bin/aisparser.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
use ais::lib;
use ais::sentence::{AisFragments, AisParser, AisSentence};
use lib::std::io::{self, BufRead};

use ais::sentence::{AisFragments, AisParser};
use lib::std::io::BufRead;

use lib::std::io;

fn parse_nmea_line(parser: &mut AisParser, line: &[u8]) -> Result<(), ais::errors::Error> {
fn parse_nmea_line_to_json(parser: &mut AisParser, line: &[u8]) -> Result<(), ais::errors::Error> {
let sentence = parser.parse(line, true)?;
if let AisFragments::Complete(sentence) = sentence {
println!(
"{:?}\t{:?}",
lib::std::str::from_utf8(line).unwrap(),
sentence.message
);
match serialize_to_json(&sentence) {
Ok(json) => println!("{}", json),
Err(err) => eprintln!("Error serializing to JSON: {}", err),
}
}
Ok(())
}

pub fn serialize_to_json(sentence: &AisSentence) -> serde_json::Result<String> {
serde_json::to_string(sentence)
}

pub fn deserialize_from_json(json_data: &str) -> serde_json::Result<AisSentence> {
serde_json::from_str(json_data)
}

fn main() {
let mut parser = AisParser::new();
let stdin = io::stdin();
{
let handle = stdin.lock();
let handle = stdin.lock();

handle
.split(b'\n')
.map(|line| line.unwrap())
.for_each(|line| {
parse_nmea_line(&mut parser, &line).unwrap_or_else(|err| {
eprintln!("{:?}\t{:?}", lib::std::str::from_utf8(&line).unwrap(), err);
});
handle
.split(b'\n')
.map(|line| line.unwrap())
.for_each(|line| {
parse_nmea_line_to_json(&mut parser, &line).unwrap_or_else(|err| {
eprintln!(
"Error parsing line: {:?}\t{:?}",
lib::std::str::from_utf8(&line).unwrap(),
err
);
});
}
});
}
92 changes: 78 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
//! AIS parsing library, for reading AIS NMEA sentences
//! AIS parsing library for reading AIS NMEA sentences, with support for JSON serialization.
//!
//! Given an NMEA stream, this library can extract various AIS message types in more detail.
//! This library parses NMEA AIS (Automatic Identification System) sentences and provides
//! structured representations of the data, allowing further processing or analysis.
//!
//! # Example:
//! # Features
//! - Parses AIS NMEA sentences into structured types.
//! - Supports JSON serialization and deserialization for `AisSentence` objects.
//!
//! # Example
//! ```
//! use ais::{AisFragments, AisParser};
//! use ais::{AisFragments, AisParser, serialize_to_json, deserialize_from_json};
//! use ais::messages::AisMessage;
//!
//! // The line below is an NMEA sentence, much as you'd see coming out of an AIS decoder.
//! let line = b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01";
//!
//! let mut parser = AisParser::new();
//! if let AisFragments::Complete(sentence) = parser.parse(line, true)? {
//! // This sentence is complete, ie unfragmented
//!
//! if let AisFragments::Complete(sentence) = parser.parse(line, true).unwrap() {
//! assert_eq!(sentence.num_fragments, 1);
//! // The data was transmitted on AIS channel B
//! assert_eq!(sentence.channel, Some('B'));
//!
//! if let Some(message) = sentence.message {
//! if let Some(ref message) = sentence.message {
//! match message {
//! AisMessage::AidToNavigationReport(report) => {
//! assert_eq!(report.mmsi, 993692028);
//! assert_eq!(report.name, "SF OAK BAY BR VAIS E");
//! // There are a ton more fields available here
//! },
//! _ => panic!("Unexpected message type"),
//! }
//! }
//!
//! let json = serialize_to_json(&sentence).unwrap();
//! let deserialized_sentence = deserialize_from_json(&json).unwrap();
//! assert_eq!(sentence, deserialized_sentence);
//! }
//! # Ok::<(), ais::errors::Error>(())
//! ```
#![cfg_attr(not(feature = "std"), no_std)]

#[doc(hidden)]
/// standard library stuff available crate-wide, regardless of `no_std` state
/// Standard library items, available crate-wide regardless of `no_std` state.
pub mod lib {
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub mod std {
Expand Down Expand Up @@ -80,6 +85,19 @@ pub mod sentence;
pub use errors::Result;
pub use sentence::{AisFragments, AisParser};

use sentence::AisSentence;
use serde_json::Error as SerdeError;

/// Serializes an `AisSentence` to JSON
pub fn serialize_to_json(sentence: &AisSentence) -> std::result::Result<String, SerdeError> {
serde_json::to_string(sentence)
}

/// Deserializes an `AisSentence` from JSON
pub fn deserialize_from_json(json_data: &str) -> std::result::Result<AisSentence, SerdeError> {
serde_json::from_str(json_data)
}

#[cfg(test)]
mod test_helpers {
#[inline]
Expand All @@ -94,6 +112,7 @@ mod test_helpers {
#[cfg(test)]
mod tests {
use super::*;
use crate::sentence::{AisReportType, AisSentence, TalkerId};

const TEST_MESSAGES: [&[u8]; 8] = [
b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01",
Expand All @@ -108,9 +127,54 @@ mod tests {

#[test]
fn end_to_end() {
let mut parser = sentence::AisParser::new();
let mut parser = AisParser::new();
for line in TEST_MESSAGES.iter() {
parser.parse(line, true).unwrap();
}
}

#[test]
fn test_json_serialization() {
let mut parser = AisParser::new();
let line = b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01";

if let AisFragments::Complete(sentence) = parser.parse(line, true).unwrap() {
// Serialize the sentence to JSON
let json = serialize_to_json(&sentence).expect("Failed to serialize to JSON");
println!("Serialized JSON: {}", json);

// Deserialize back from JSON
let deserialized_sentence =
deserialize_from_json(&json).expect("Failed to deserialize from JSON");

assert_eq!(sentence, deserialized_sentence);
}
}

#[test]
fn test_serialize_deserialize() {
// Create a sample AisSentence struct
let original_sentence = AisSentence {
message: None,
talker_id: TalkerId::AI,
report_type: AisReportType::VDM,
num_fragments: 1,
fragment_number: 1,
message_id: Some(123),
channel: Some('A'),
data: vec![69, 62, 107, 98, 57, 79], // sample data; replace with real data if needed
fill_bit_count: 0,
message_type: 1,
};

// Serialize to JSON
let json_data = serialize_to_json(&original_sentence).expect("Serialization failed");

// Deserialize back to an AisSentence
let deserialized_sentence: AisSentence =
deserialize_from_json(&json_data).expect("Deserialization failed");

// Check if the deserialized struct matches the original
assert_eq!(original_sentence, deserialized_sentence);
}
}
3 changes: 2 additions & 1 deletion src/messages/addressed_safety_related.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use crate::errors::Result;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize, Eq)]
pub struct AddressedSafetyRelatedMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
5 changes: 3 additions & 2 deletions src/messages/aid_to_navigation_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ use crate::errors::Result;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum NavaidType {
ReferencePoint,
Racon,
Expand Down Expand Up @@ -84,7 +85,7 @@ impl NavaidType {
}
}

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct AidToNavigationReport {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/assignment_mode_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use super::AisMessageType;
use crate::errors::Result;
use nom::bits::{bits, complete::take as take_bits};
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssignmentModeCommand {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/base_station_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use crate::errors::Result;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct BaseStationReport {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
11 changes: 6 additions & 5 deletions src/messages/binary_acknowledge.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! Binary Acknowledge (type 7)
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
use super::nom_noalloc::many_m_n;
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
use super::nom_noalloc::many_m_n;
use super::AisMessageType;
use crate::errors::Result;
use crate::lib;
use nom::bits::{bits, complete::take as take_bits};
#[cfg(any(feature = "std", feature = "alloc"))]
#[cfg(any(feature = "std", feature = "alloc"))]
use nom::multi::many_m_n;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Acknowledgement {
pub mmsi: u32,
pub seq_num: u8,
Expand All @@ -28,7 +29,7 @@ pub type AcknowledgementList = lib::std::vec::Vec<Acknowledgement>;
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub type AcknowledgementList = lib::std::vec::Vec<Acknowledgement, 4>;

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BinaryAcknowledge {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/binary_addressed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::lib;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
const MAX_DATA_SIZE_BYTES: usize = 119;
Expand All @@ -15,7 +16,7 @@ pub type MessageData = lib::std::vec::Vec<u8>;
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub type MessageData = lib::std::vec::Vec<u8, MAX_DATA_SIZE_BYTES>;

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize, Eq)]
pub struct BinaryAddressedMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/binary_broadcast_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::errors::Result;
use crate::lib;
use nom::bits::{bits, complete::take as take_bits};
use nom::IResult;
use serde::{Deserialize, Serialize};

#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
const MAX_DATA_SIZE_BYTES: usize = 119;
Expand All @@ -13,7 +14,7 @@ pub type MessageData = lib::std::vec::Vec<u8>;
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub type MessageData = lib::std::vec::Vec<u8, MAX_DATA_SIZE_BYTES>;

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BinaryBroadcastMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
5 changes: 3 additions & 2 deletions src/messages/data_link_management_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use nom::bits::{bits, complete::take as take_bits};
#[cfg(any(feature = "std", feature = "alloc"))]
use nom::multi::many_m_n;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SlotReservation {
pub offset: u16,
pub num_slots: u8,
Expand Down Expand Up @@ -40,7 +41,7 @@ pub type SlotReservationList = lib::std::vec::Vec<SlotReservation>;
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub type SlotReservationList = lib::std::vec::Vec<SlotReservation, 4>;

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DataLinkManagementMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
5 changes: 3 additions & 2 deletions src/messages/dgnss_broadcast_binary_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::lib;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
const MAX_DATA_SIZE_BYTES: usize = 119;
Expand All @@ -15,7 +16,7 @@ pub type CorrectionData = lib::std::vec::Vec<u8>;
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub type CorrectionData = lib::std::vec::Vec<u8, MAX_DATA_SIZE_BYTES>;

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct DgnssBroadcastBinaryMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand All @@ -25,7 +26,7 @@ pub struct DgnssBroadcastBinaryMessage {
pub payload: DifferentialCorrectionData,
}

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DifferentialCorrectionData {
pub message_type: u8,
pub station_id: u16,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/extended_class_b_position_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ use crate::messages::types::ShipType;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ExtendedClassBPositionReport {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
Loading

0 comments on commit 2cc0521

Please sign in to comment.