Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:
# only; the doctest needing them is excluded).
- run: cargo test -p kinavis-wmm --no-default-features --features "libm wmm2025"
- run: cargo test -p kinavis-wmm --no-default-features --features std --lib --tests
# The demonstrations run to the end on their recorded data.
- run: for example in receivers gnss_jump traffic course_to_steer; do cargo run -p kinavis-examples --example "$example"; done

properties:
name: Property tests
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Workspace: shared lints, version policy and MSRV for every crate.
[workspace]
resolver = "2"
members = ["crates/*", "xtask"]
members = ["crates/*", "examples", "xtask"]
# Fuzz crates are separate workspaces built by cargo-fuzz with its own
# profile and flags.
exclude = [
Expand Down
67 changes: 50 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
![no_std](https://img.shields.io/badge/no__std-no%20allocator-blue.svg)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)

Marine navigation in Rust, from the bytes a sensor sends to the numbers the
officer of the watch acts on: NMEA 0183, NMEA 2000, AIS and an IMU in; a
position, a course to steer, a collision assessment and a bridge alert out —
on a microcontroller or a workstation alike.
**Marine navigation for Rust** — NMEA 0183, NMEA 2000, AIS, GNSS, INS and the
COLREGs, from a `no_std` microcontroller to a workstation.

From the bytes a sensor sends to the numbers the officer of the watch acts on:
NMEA 0183, NMEA 2000, AIS and an IMU in; a position, a course to steer, a
collision assessment and a bridge alert out.

## Why KINAVIS

Expand All @@ -23,16 +25,47 @@ on a microcontroller or a workstation alike.
- **No `unsafe`**, and no third-party dependencies in the default build.
- **Types that carry meaning.** A compass course cannot be passed where a true
one belongs, a time in GPS cannot be mixed with UTC, knots cannot be
mistaken for metres per second — the compiler refuses.
mistaken for metres per second — the compiler refuses:

```text
let course = TrueCourse::new(90.0)?;
magnetic_to_true(course, variation);
^^^^^^ expected `Direction<Magnetic>`, found `Direction<True>`
```

- **Reproducible.** A passage planned ashore and recomputed on the bridge is
the same plan: the `std` and the pure-Rust `libm` maths are held to agree
within 1e-13, and the estimator is a pure function that replays a voyage
step by step.
- **Verified.** The parsers are fuzzed; the algorithms are checked against
- **Verified.** The parsers are fuzzed and tested on real receivers' output
from the gpsd and Signal K logs; the algorithms are checked against
published reference values — NOAA's WMM test points, Vincenty's test
lines, PROJ's datum shifts; and the filters pass Monte Carlo consistency
tests.

## See it run

Four demonstrations in [`examples/`](examples/), each on data recorded from
real equipment — gpsd's receiver logs and a Signal K AIS recording:

| Demonstration | What it shows |
|---|---|
| `receivers` | seven receivers' NMEA, including an RTK receiver past 82 bytes and a line that lost bytes in transit, read or refused |
| `gnss_jump` | a yacht's track with one fix moved forty miles: `REFUSED implausible jump: 144094 kn implied` |
| `traffic` | 1459 AIS messages off Harlingen decoded; CPA and TCPA of every ship against one of them |
| `course_to_steer` | the same yacht against a passage plan: cross-track error and the cross-track alarm |

```sh
cargo run -p kinavis-examples --example traffic
```

```text
ship bearing range CPA TCPA risk
245513000 075.0°T 35.61 M 0.12 M 76:02 developing
218784000 147.9°T 1.34 M 1.33 M 0:55 DANGEROUS
246754000 062.7°T 25.80 M 2.03 M 45:33 passing clear
```

## Crates

```mermaid
Expand Down Expand Up @@ -135,6 +168,17 @@ through the rest — the sailings, fixing, deviation tables and the inverse
problem, the current triangle, errors, `serde`, and what each aggregate
weighs in memory — with examples that are compiled and run as tests.

## Help wanted: real hardware

Everything here is tested against recorded data, reference values and
simulation. What cannot be tested that way is how it behaves on a real bridge:
a GNSS receiver losing the sky, an NMEA 2000 backbone under load, an AIS
receiver in a crowded anchorage, an IMU strapped to a hull in a seaway, a
microcontroller with a real stack budget. If you have such equipment and would
run KINAVIS against it — or can share a recording of what it sends — please
[open an issue](https://github.com/KINAVIS/kinavis/issues). Logs from the sea
are worth more than any number of tests on land.

## Stability

`kinavis` and `kinavis-kernel` are at 1.x: within the major version nothing
Expand All @@ -148,17 +192,6 @@ and version on their own. The minimum supported Rust version is 1.85; raising it
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — the layers, the dependency rule and the context map.
- [`SECURITY.md`](SECURITY.md) — the threat model and how to report a vulnerability.

## Help wanted: real hardware

Everything here is tested against recorded data, reference values and
simulation. What cannot be tested that way is how it behaves on a real bridge:
a GNSS receiver losing the sky, an NMEA 2000 backbone under load, an AIS
receiver in a crowded anchorage, an IMU strapped to a hull in a seaway, a
microcontroller with a real stack budget. If you have such equipment and would
run KINAVIS against it — or can share a recording of what it sends — please
[open an issue](https://github.com/KINAVIS/kinavis/issues). Logs from the sea
are worth more than any number of tests on land.

## License

Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at your option.
Expand Down
5 changes: 5 additions & 0 deletions ci/allowed-deps.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@ allow = ["kinavis-kernel", "serde"]
# the kernel's types and matrices; the six-state estimator consumes it,
# never the other way round.
allow = ["kinavis-kernel", "serde"]

[kinavis-examples]
# Unpublished demonstrations. Every KINAVIS crate they use is a
# dev-dependency, so the shipped graph of this package is empty.
allow = []
23 changes: 23 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "kinavis-examples"
description = "Runnable demonstrations of the KINAVIS crates. Not published."
publish = false
version = "0.0.0"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true

[lib]
path = "lib.rs"

# The demonstrations are examples, so every KINAVIS crate they use is a
# dev-dependency: nothing here is part of the shipped graph.
[dev-dependencies]
kinavis = { workspace = true, features = ["std"] }
kinavis-nmea0183 = { workspace = true, features = ["std"] }
kinavis-traffic = { version = "0.1.0", path = "../crates/kinavis-traffic" }
kinavis-ais = { version = "0.1.1", path = "../crates/kinavis-ais" }

[lints]
workspace = true
77 changes: 77 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# KINAVIS demonstrations

Four short programs, each run on data recorded from real equipment. Nothing
here is simulated except where it says so.

```sh
cargo run -p kinavis-examples --example receivers
cargo run -p kinavis-examples --example gnss_jump
cargo run -p kinavis-examples --example traffic
cargo run -p kinavis-examples --example course_to_steer
```

## `receivers` — what real receivers send

One sentence from each of seven receivers: a chartplotter, an RTK receiver
writing past the standard's 82 bytes, an AIS transponder that drops a field, a
receiver that writes 999.9 for an unknown variation, a cold start, a receiver
without a fix, and a line that lost bytes in transit.

```text
u-blox ZED-F9P, high-precision NMEA, 89 bytes (ublox-f9p-hpnmea.log)
$GNGGA,014500.00,4404.1306024,N,12118.8446777,W,2,12,0.49,1129.913,M,-21.350,M,,0278*4C
-> position 44°04.1306024'N 121°18.8446777'W (Differential, HDOP 0.49)

Caterpillar MS352, variation 999.9 for "unknown" (cat-ms352.log)
$GPRMC,113938.50,A,3842.86006889,N,11705.43645510,W,0.011,46.405,231122,999.9000,E,D*2E
-> fix 38°42.8601'N 117°05.4365'W at 2022-11-23T11:39:38 UTC, 0.0 kn over the ground on 046.4°T

GPS-320FW after a GPS week rollover, bytes lost mid-sentence (gp-320fw-2019-04-07-coldboot.log)
$GPRMC,000429.00,V,0000.0000,N,00000.000VTG,,T,,M,,N,,K,N*2C
-> refused: checksum 2C claimed, body sums to 65
```

## `gnss_jump` — a forty-mile jump is refused

A yacht's real track, one fix a second, with one fix moved forty miles north.

```text
2018-08-20T09:48:50 UTC accepted 52°50.940'N 005°18.662'E
2018-08-20T09:48:51 UTC REFUSED 53°30.939'N 005°18.660'E implausible jump: 144094 kn implied; injected
2018-08-20T09:48:52 UTC accepted 52°50.938'N 005°18.657'E

145 fixes accepted, 1 refused.
```

## `traffic` — AIS in, CPA and TCPA out

1459 AIS messages from a receiver off Harlingen, reassembled and decoded; one
ship taken as own ship, every other ship assessed against it.

```text
ship bearing range CPA TCPA risk
245513000 075.0°T 35.61 M 0.12 M 76:02 developing
218784000 147.9°T 1.34 M 1.33 M 0:55 DANGEROUS
246754000 062.7°T 25.80 M 2.03 M 45:33 passing clear
```

## `course_to_steer` — guidance along a real track

The same yacht against a passage plan drawn for the example: desired track,
course to steer, cross-track error, distance to go, and the cross-track alarm.

```text
UTC position COG track steer off track to go alarm
09:47:37 52°51.009'N 005°18.817'E 230.5°T 235.7°T 235.7°T 0.19 M Port 8.82 M off track: 0.19 M > 0.10 M
```

## Data

| File | Source | Licence |
|---|---|---|
| `data/receivers.nmea` | [gpsd](https://gitlab.com/gpsd/gpsd) regression logs, `test/daemon/`, one line from each of seven | BSD-2-Clause, [`data/LICENSE-gpsd`](data/LICENSE-gpsd) |
| `data/bundg_zeus_9.nmea` | gpsd `test/daemon/bundg_zeus_9.log`, unmodified | BSD-2-Clause, [`data/LICENSE-gpsd`](data/LICENSE-gpsd) |
| `data/gofree-merrimac-ais.nmea` | [Signal K server](https://github.com/SignalK/signalk-server) `samples/gofree-merrimac.log`, the `!AIVDM` lines only | Apache-2.0, [`data/LICENSE-signalk`](data/LICENSE-signalk) |

Recorded data from other equipment is welcome: see *Help wanted* in the
[main README](../README.md).
35 changes: 35 additions & 0 deletions examples/data/LICENSE-gpsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
COPYRIGHTS

Compilation copyright is held by the GPSD project. All rights reserved.

GPSD project copyrights are assigned to the project lead, currently
Eric S. Raymond. Other portions of the GPSD code are Copyright
1997, 1998, 1999, 2000, 2001, 2002 by Remco Treffkorn, and others
Copyright 2005 by Eric S. Raymond. For other copyrights, see
individual files.

LICENSE
(SPDX short identifier: BSD-2-Clause)

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Loading
Loading