Skip to main content

kinavis_ais/
lib.rs

1//! AIS messages from their NMEA sentences to the KINAVIS types.
2//!
3//! A receiver delivers an AIS message as one or more `!AIVDM` sentences, 6 bits
4//! per character. [`kinavis-nmea0183`] validates each sentence and yields a
5//! [`Vdm`] with the payload still armoured; this crate:
6//!
7//! 1. **Unarmours** the payload into [`Bits`], a fixed-capacity buffer read by
8//!    bit offset and width as in the standard's tables.
9//! 2. **Reassembles** multi-sentence messages in an [`Assembler`] with fixed
10//!    slots and a timeout, so a lost fragment costs one message, not a slot.
11//! 3. **Decodes** into a [`Message`] with kernel field types: [`Position`],
12//!    [`Speed`], [`TrueCourse`], [`TargetId`] for the MMSI, [`Distance`] for
13//!    draught, [`InlineStr`] for names. "Not available" is `None`, never a zero
14//!    or a string of `@`.
15//!
16//! No allocation, no panics on any input; builds for bare-metal targets and CI
17//! checks the strict-profile build for panic paths.
18//!
19//! ```rust
20//! use kinavis_ais::{Assembler, Message};
21//! use kinavis_kernel::{Instant, Utc};
22//! use kinavis_nmea0183::{parse, Sentence};
23//!
24//! let mut assembler = Assembler::new();
25//! let now = Instant::<Utc>::from_unix_seconds(1_789_000_000);
26//!
27//! let line = b"!AIVDM,1,1,,A,13aEOK?P00PD2wVMdLDRhgvL289?,0*26\r\n";
28//! let Sentence::Vdm(vdm) = parse(line)? else { panic!("not an AIS sentence") };
29//! let Some(bits) = assembler.push(&vdm, now)? else { panic!("in fragments") };
30//! let Message::PositionReport(report) = Message::decode(&bits)? else { panic!("not a position") };
31//!
32//! assert_eq!(report.mmsi.number(), 244_670_316);
33//! assert_eq!(format!("{:.3}", report.position.unwrap()), "51°53.685'N 004°22.757'E");
34//! assert_eq!(report.course.map(|c| c.degrees()), Some(70.6));
35//! assert_eq!(report.speed.map(|s| s.knots()), Some(0.0));
36//! assert_eq!(report.heading, None, "not available, not north");
37//! # Ok::<(), Box<dyn std::error::Error>>(())
38//! ```
39//!
40//! The caller builds the `TargetObservation` for the traffic picture from these
41//! fields; this crate depends only on the kernel and the sentence crate.
42//!
43//! [`kinavis-nmea0183`]: kinavis_nmea0183
44//! [`Position`]: kinavis_kernel::Position
45//! [`Speed`]: kinavis_kernel::Speed
46//! [`TrueCourse`]: kinavis_kernel::TrueCourse
47//! [`TargetId`]: kinavis_kernel::TargetId
48//! [`Distance`]: kinavis_kernel::Distance
49//!
50//! # Supported messages
51//!
52//! | Message | Decoded as |
53//! |---|---|
54//! | 1, 2, 3 — class A position report | [`PositionReport`] |
55//! | 5 — class A static and voyage data | [`StaticAndVoyageData`] |
56//! | 18 — class B position report | [`PositionReport`] |
57//! | 19 — class B extended position report | [`PositionReport`], with name, type and dimensions |
58//! | 21 — aid to navigation | [`AidToNavigation`] |
59//! | 24 — class B static data report, part A or B | [`StaticDataReport`] |
60//!
61//! Other well-formed messages decode as [`Message::Unsupported`] with the
62//! message type and the raw [`Bits`].
63//!
64//! # Feature flags
65//!
66//! - `std` *(default)* — standard library maths in the kernel.
67//! - `libm` — for `no_std` targets: `--no-default-features --features libm`.
68//! - `serde` — serialisation of the messages.
69
70#![cfg_attr(not(feature = "std"), no_std)]
71
72mod assembler;
73mod aton;
74mod bits;
75mod error;
76mod fields;
77mod message;
78mod static_data;
79mod vessel;
80
81pub use assembler::{Assembler, DEFAULT_TIMEOUT, MAX_ASSEMBLIES};
82pub use aton::{AidToNavigation, AidType, Mark, Quadrant};
83pub use bits::{Bits, MAX_MESSAGE_BITS};
84pub use error::AisError;
85pub use message::{Message, NavigationStatus, PositionReport, StationClass, Turn};
86pub use static_data::{
87    ClassBStaticData, Eta, StaticAndVoyageData, StaticDataPart, StaticDataReport,
88};
89pub use vessel::{Dimensions, HazardCategory, PositionFixingDevice, ShipCategory, ShipType};
90
91// Re-exported so callers need not depend on the NMEA crate for the sentence
92// type or on the kernel for the string type.
93pub use kinavis_kernel::InlineStr;
94pub use kinavis_nmea0183::{Channel, Vdm};
95
96/// Runs the `README.md` example as a doctest.
97#[cfg(doctest)]
98#[doc = include_str!("../README.md")]
99pub struct ReadmeExamples;