Skip to main content

kinavis_nmea2000/
lib.rs

1//! NMEA 2000 parameter groups from CAN frames to the KINAVIS types.
2//!
3//! Every NMEA 2000 device (GNSS, compass, sounder, AIS) sends *parameter
4//! groups*: fixed layouts of little-endian fields, carried in one 8-byte frame
5//! or, as a *fast packet*, in up to 32. Starting from frames delivered by the
6//! bus interface, this crate:
7//!
8//! 1. **Parses the identifier** — [`CanId`] — into the [`Pgn`] and source
9//!    address.
10//! 2. **Reassembles fast packets** in an [`Assembler`] with fixed slots and a
11//!    timeout, so a lost frame costs one group, not a slot.
12//! 3. **Decodes** the group into a [`Message`] with kernel field types
13//!    ([`Position`], [`Speed`], [`TrueCourse`], [`Distance`], [`Instant`]).
14//!    "Not available" is `None`, not the raw all-ones value.
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. The bus (CAN controller,
18//! driver, address claim) is the caller's.
19//!
20//! ```rust
21//! use kinavis_nmea2000::{Assembler, CanId, Frame, Message, Pgn};
22//! use kinavis_kernel::{Instant, Utc};
23//!
24//! let mut assembler = Assembler::new();
25//! let now = Instant::<Utc>::from_unix_seconds(1_789_000_000);
26//!
27//! // Position, rapid update, from address 35: 50.755°N 1.333°W in 1e-7°.
28//! let id = CanId::new(0x09F8_0123)?;
29//! assert_eq!(id.pgn(), Pgn::POSITION_RAPID_UPDATE);
30//! let frame = Frame::new(id, &[0x30, 0x99, 0x40, 0x1E, 0xB0, 0x99, 0x34, 0xFF])?;
31//!
32//! let Some(payload) = assembler.push(&frame, now)? else { panic!("in frames") };
33//! let Message::PositionRapidUpdate(update) = Message::decode(&payload)? else {
34//!     panic!("not a position");
35//! };
36//! assert_eq!(format!("{:.3}", update.position.unwrap()), "50°45.300'N 001°19.980'W");
37//! # Ok::<(), Box<dyn std::error::Error>>(())
38//! ```
39//!
40//! [`Position`]: kinavis_kernel::Position
41//! [`Speed`]: kinavis_kernel::Speed
42//! [`TrueCourse`]: kinavis_kernel::TrueCourse
43//! [`Distance`]: kinavis_kernel::Distance
44//! [`Instant`]: kinavis_kernel::Instant
45//!
46//! # Supported PGNs
47//!
48//! | Group | Decoded as |
49//! |---|---|
50//! | 129025 — position, rapid update | [`PositionRapidUpdate`] |
51//! | 129026 — COG and SOG, rapid update | [`CourseAndSpeed`] |
52//! | 129029 — GNSS position data | [`GnssPosition`], and a kernel `GnssFix` from it |
53//! | 127250 — vessel heading | [`VesselHeading`] |
54//! | 128267 — water depth | [`WaterDepth`] |
55//! | 129038 — AIS class A position report | [`AisPositionReport`] |
56//! | 129039 — AIS class B position report | [`AisPositionReport`] |
57//!
58//! Other PGNs pass the assembler only with an explicit transport
59//! ([`Assembler::push_as`]) and decode as [`Message::Unsupported`] with the PGN
60//! and the raw [`Payload`].
61//!
62//! # Feature flags
63//!
64//! - `std` *(default)* — standard library maths in the kernel.
65//! - `libm` — for `no_std` targets: `--no-default-features --features libm`.
66//! - `serde` — serialisation of the messages.
67
68#![cfg_attr(not(feature = "std"), no_std)]
69
70#[cfg(test)]
71extern crate std;
72
73mod ais;
74mod assembler;
75mod error;
76mod fields;
77mod frame;
78mod gnss;
79mod id;
80mod message;
81mod navigation;
82
83pub use ais::{AisPositionReport, StationClass};
84pub use assembler::{Assembler, DEFAULT_TIMEOUT, MAX_ASSEMBLIES};
85pub use error::Nmea2000Error;
86pub use frame::{Frame, Payload, MAX_PAYLOAD_BYTES};
87pub use gnss::{GnssPosition, GnssSystem, Integrity};
88pub use id::{CanId, Pgn, Transport};
89pub use message::Message;
90pub use navigation::{CourseAndSpeed, PositionRapidUpdate, Referenced, VesselHeading, WaterDepth};
91
92/// Runs the `README.md` example as a doctest.
93#[cfg(doctest)]
94#[doc = include_str!("../README.md")]
95pub struct ReadmeExamples;