kinavis_signalk/lib.rs
1//! Signal K deltas in and out of the KINAVIS navigation crates.
2//!
3//! A [Signal K](https://signalk.org) server merges a vessel's NMEA 0183,
4//! NMEA 2000 and AIS into one JSON model and streams changes as deltas. This
5//! crate reads those deltas into a [`Picture`] of own vessel and every
6//! target, and a [`Watch`] assesses the picture: CPA and TCPA of each target
7//! (`kinavis-traffic`), and for a dangerous or developing approach the
8//! COLREGs ruling (`kinavis-colregs`): the encounter, who gives way and what
9//! each may do. The results go back to the server as
10//! `navigation.closestApproach` deltas in each target's context and as
11//! notifications on own vessel.
12//!
13//! Signal K units are SI: radians, metres, metres per second. The crate
14//! converts at the boundary; everything inside is KINAVIS types.
15//!
16//! ```rust
17//! use kinavis_signalk::{parse_timestamp, Emission, Level, Watch, WatchConfig};
18//!
19//! let mut watch = Watch::new(WatchConfig::default());
20//! // Own vessel steering north at 6 knots (3.087 m/s)...
21//! watch.ingest(r#"{"context":"vessels.self","updates":[{"timestamp":"2026-09-25T12:00:00Z","values":[
22//! {"path":"navigation.position","value":{"latitude":53.0,"longitude":5.0}},
23//! {"path":"navigation.courseOverGroundTrue","value":0.0},
24//! {"path":"navigation.speedOverGround","value":3.087}]}]}"#)?;
25//! // ...and a motor vessel a mile and a half north and east, steering west at 6 knots.
26//! watch.ingest(r#"{"context":"vessels.urn:mrn:imo:mmsi:244000001","updates":[{"timestamp":"2026-09-25T12:00:00Z","values":[
27//! {"path":"","value":{"name":"ANNA"}},
28//! {"path":"navigation.position","value":{"latitude":53.025,"longitude":5.04155}},
29//! {"path":"navigation.courseOverGroundTrue","value":4.712389},
30//! {"path":"navigation.speedOverGround","value":3.087},
31//! {"path":"navigation.state","value":"motoring"}]}]}"#)?;
32//!
33//! let now = parse_timestamp("2026-09-25T12:00:01Z").unwrap();
34//! let (reports, emissions) = watch.assess(now);
35//! assert_eq!(reports[0].level, Level::Alarm);
36//! let Some(Emission::Notification { path, value }) = emissions.last() else { panic!() };
37//! assert_eq!(path, "notifications.navigation.closestApproach.urn:mrn:imo:mmsi:244000001");
38//! assert_eq!(value["state"], "alarm");
39//! assert!(value["message"].as_str().unwrap().contains("give way (Rule 15)"));
40//! # Ok::<(), serde_json::Error>(())
41//! ```
42//!
43//! Requires the standard library: JSON, and a picture as large as the server's.
44
45mod delta;
46mod picture;
47mod time;
48mod watch;
49
50/// Runs the `README.md` example as a doctest.
51#[cfg(doctest)]
52#[doc = include_str!("../README.md")]
53pub struct ReadmeExamples;
54
55pub use delta::{Delta, PathValue, Update};
56/// The clock [`Watch::assess`] takes.
57pub use kinavis_kernel::time::{Instant, Utc};
58pub use picture::{Category, Picture, VesselState, SELF_CONTEXT};
59pub use time::parse_timestamp;
60pub use watch::{message, Emission, Level, TargetReport, Watch, WatchConfig, NOTIFICATION_PATH};