A small but effective set of command-line utilities to work with CAN networks on BOATs. The most common version of CAN networks on board, and in fact at the moment the only ones that this suite can analyse, is NMEA 2000.
The NMEA 2000 database and implementation is copyrighted by the NMEA (National Marine Electronics Association). Access is restricted to members and parties that pay for it. If they do so they are not able to divulge the content of the database, thus making it impossible for open source developers to get access to it.
For this reason we have reverse engineered the NMEA 2000 database by network observation and assembling data from public sources.
If you just want to know how the NMEA 2000 protocol works, with an explanation of all reverse engineered data, please go to the documentation page.
To use the programs included in this project you may need a supported CAN interface. This can be a marketed-as-such NMEA 2000 Gateway or a non NMEA specific CAN interface.
For more information go to the CANBoat Wiki.
CANboat ships two implementations. Both are tier 1 — supported, tested and released together — and they are interchangeable for the jobs they both do.
C tools. analyzer and the *-serial gateway drivers are the originals and
stay tier 1. They are small, dependency-light and build with nothing but make
and a C compiler.
analyzer |
decode NMEA 2000 into text or JSON |
actisense-serial |
Actisense NGT-1 / W2K-1 gateways |
ikonvert-serial |
Digital Yacht iKonvert |
socketcan-serial |
Linux SocketCAN |
maretron-ipg |
Maretron IPG100 |
candump2analyzer, replay, iptee, nmea0183-serial, analyzer-j1939 |
converters and helpers |
canboat, the Rust binary. One executable with subcommands. convert and
the gateway support are tier 1 alongside their C counterparts; everything with
no C equivalent is tier 1 as the only implementation.
canboat convert |
decode a capture between any supported formats |
canboat interface |
bridge a live gateway (NGT-1 / iKonvert / Maretron / SocketCAN) |
canboat n2kd |
multiplex an analyzer-JSON stream to TCP clients |
canboat server |
the whole device → analyzer → n2kd pipeline in one process |
canboat tui |
interactive terminal browser for a live stream or a capture |
canboat format-message |
build a single frame from field values, for any PGN |
canboat replay |
pace a capture at its original wall-clock rhythm |
canboat install-shims drops symlinks so the subcommands answer to the old
names (analyzer, n2kd, actisense-serial, …). It never takes a name that
another program already holds, so it is safe to run in a directory that has the
C tools installed — it reports what it left alone.
The remaining C converters, the helper scripts in util/ (PHP, perl and ksh)
and the Python tooling will most likely move to Rust in time; the C n2kd and
n2kd_monitor already have, and were removed in v8.
Some devices misbehave in ways CANboat can cover for. Each workaround is a named quirk, and every one of them is off by default — they all rewrite or invent data, so you switch on the ones you need:
| Quirk | What it does | Where |
|---|---|---|
gps-rollover |
Corrects GNSS dates from a receiver that never learned about the GPS 1024-week rollover and reports one or two epochs in the past (PGN 129029, 129033, and 126992 when its source is GPS) | analyzer -quirk gps-rollover, canboat convert --quirk gps-rollover, canboat server --quirk gps-rollover |
gps-rollover=<device>,… |
The same, plus every date the listed devices stamp from that clock — a DSC radio's Date of Receipt on PGN 129808, a converter's System Time, and so on. A device is a source address (4), its manufacturer code and unique number (1851:491603), or its ISO NAME in hex (0x…); all means every device on the bus |
same three commands |
gps-relay |
Re-sends the data PGNs the devices listed in gps-rollover=… broadcast (not their network management, and not anything addressed), from canboat's own address with the dates corrected, so other devices on the bus can select canboat as their GPS and time source instead of the rolled-over one. The original keeps transmitting; you pick the source in each display. Needs a real device list — gps-rollover=all is refused here |
canboat server --quirk gps-rollover=<device> --quirk gps-relay (needs a writable backend) |
scx20 |
Answers a PGN 59904 request for a Furuno SCX-20's Product Information on its behalf, so the Furuno Setting Tool can find it | canboat server --quirk scx20 (needs --socketcan) |
wmm |
Computes magnetic variation locally with WMM 2025 and emits its own PGN 127258, asking older-WMM sources to stop | canboat server --quirk wmm |
motion |
Impersonates a B&G H5000 Motion Sensor so a Navico Hercules accepts an SCX-20 | canboat server --quirk motion (needs --socketcan) |
gps-rollover is the odd one out: it corrects a decoded value rather than
putting a frame on the bus, which is why it is available when decoding a
capture file too. Do not use it on a capture made before April 2019 — it
cannot tell a rolled-over date from a genuinely old one, and will move the
whole log forward twenty years.
Without a device list it only touches the dates a GNSS receiver produced,
because those are the only ones whose origin the PGN itself tells us. Any
other device that takes its clock from the bus repeats the wrong date, and
nothing in its PGNs says so; that is what the device list is for. Naming a
device by manufacturer and unique number, or by NAME, needs its PGN 60928
Address Claim to have gone by — canboat server asks for those at startup,
a capture has to contain one. The AIS reports (129793, 129794) relay another
station's clock and 127258 carries the variation model's date, so those are
never corrected.
None of that helps a display that reads the broken GPS directly. gps-relay
is the nearest thing to a fix short of replacing the receiver: canboat server re-sends every broadcast PGN from the listed devices — position,
COG/SOG, satellites, not just the dated ones, or no display would accept it
as a GPS — from its own address, byte for byte except for the corrected
dates. The other devices then see a second GPS source, canboat, and you
select it in each of them. That needs devices with real source selection,
which not every brand gets right, and it leaves the old receiver on the bus,
since it is where the data comes from.
The decoder is also a Rust crate, canboat, so you can embed NMEA 2000
handling instead of parsing another program's output. One crate, a small
public API, and features that gate the weight you pay for:
| feature | |
|---|---|
decode |
sans-I/O: PGN database, parsers, reassembly, decode, encode, output formatters. No threads, no sockets. The database is compiled in — nothing to load at runtime |
io |
readers for captures and bus::open_* for a live NGT-1 / iKonvert / SocketCAN link |
node |
be a compliant NMEA 2000 node: ISO NAME, address claim, the standard responses |
bridge |
the whole canboat server pipeline as a library: device → decode → quirks → TCP serving |
cli |
the canboat binary itself (the default; library users turn it off) |
The crate's own README has the quick start and
is what crates.io shows; the API reference
is on docs.rs and its design in
crates/doc/library-api-plan.md.
Same decode work — -json -nv over 1.26 M PGN frames (canboat's
dirona-actisense-serial.raw × 50) on an M4 Pro, release build:
| Implementation | Wall time | vs canboat |
|---|---|---|
| canboatjs (Node 25) | 27.8 s | 8.1 × |
| canboat C | 9.1 s | 2.6 × |
canboat convert |
3.4 s | 1.0 × |
canboat server goes one step further and collapses the
canboat convert | canboat n2kd pipeline into a single process with no
JSON text serialisation between stages:
| Pipeline | Wall time | Throughput |
|---|---|---|
canboat convert alone (PGN decode only) |
3.3 s | 380 k frames / s |
canboat convert | canboat n2kd (piped, 2 proc) |
6.5 s | 194 k sentences / s |
canboat server (single proc) |
3.5 s | 360 k sentences / s |
That's 46 % less wall time than the piped setup while doing strictly more work (it's a long-running service with TCP fan-out); on CPU time the ratio is closer to 3.4 ×.
canboat server --replay <capture> plays a capture as if it were a gateway,
so you can test a consumer such as Signal K without a boat. It reads any
format canboat reads, keeps the capture's pacing but stamps every frame with
the current time, and starts over at the end. The TCP ports are the same as
for a real gateway, the input port included: frames written to it go to
--replay-sent <file> as PLAIN/FAST lines, or are dropped, and are not
echoed back.
canboat server --replay samples/dirona-actisense-serial.raw --replay-sent sent.rawOther devices ask a node which PGNs it sends and reads (PGN 126464).
canboat server --tx-pgn <pgn>[,…] --rx-pgn <pgn>[,…] (or
BridgeConfig::pgn_lists in the library) names the PGNs your application
sends and reads, so the gateway can answer truthfully. What happens with
them depends on the gateway:
| Gateway | Transmit list | Receive list |
|---|---|---|
| SocketCAN | Advertised by canboat itself. Nothing is filtered. | Advertised |
| iKonvert | Written into the gateway, which only transmits what is on it | Written into the gateway; filters only in --ikonvert-rx (NORMAL) mode |
| NGT-1 | Missing PGNs enabled in the gateway's Transmit PGN Enable list (EEPROM, only when something is missing); it only transmits what is on it | Not supported |
| Others | Ignored, with a warning | Ignored, with a warning |
Warning
Once you name any transmit PGN (--tx-pgn, --ikonvert-tx, or
pgn_lists.tx), canboat REFUSES TO SEND EVERY PGN THAT IS NOT ON THE
LIST. The gateway only transmits the PGNs in its list, and canboat
sets that list once, when it starts, so canboat drops the rest up front,
logging each refused PGN once, instead of handing the gateway frames it
will reject.
Decide the complete list before you start. Only the ISO/NMEA
network-management PGNs, and the PGNs of the quirks you enabled (such as
wmm's 127258), are allowed without being named. With no transmit PGN
named, nothing is refused and the gateway's own list decides.
analyzer reads NMEA 2000 data from a number of text formats on stdin and turns
each message into human readable text or JSON. Some formats it understands
directly; capture formats from specific gateways or CAN tools are first turned
into the analyzer's PLAIN format by a small helper program that you pipe into
analyzer:
- Directly by
analyzer— text logs that already contain decoded CAN identifiers. The format is auto-detected, or can be forced with-format <NAME>. - Via
actisense-serial— Actisense NGT-1 binary streams and log files.actisense-serial -r <file> | analyzer - Via
candump2analyzer— Linux can-utils dumps and other raw CAN captures.candump2analyzer <file> | analyzer
The full list of understood formats:
| Format | Origin | Example line | How to read |
|---|---|---|---|
PLAIN |
canboat native, one CAN frame per line | 2016-02-28T19:57:41.000Z,3,126208,40,72,8,01,0d,f2,01,f8,01,03,01 |
analyzer (auto, or -format PLAIN) |
FAST |
canboat native, a whole fast-packet message per line | 2021-01-18T01:23:06.960Z,6,129540,26,255,216,ec,ff,… |
analyzer (auto, or -format FAST) |
AIRMAR |
Airmar weather/depth devices | 0123 -8 09F80 … |
analyzer (auto) |
CHETCO |
Chetco devices | $PCDIN,01F119,00000000,0F,FFFFFFFFFFFFFFFF*21 |
analyzer (auto) |
GARMIN_CSV1 / GARMIN_CSV2 |
Garmin CSV export | 0,486942,127508,Battery Status,Garmin,6,255,2,1,8,0x017505FF7FFFFFFF |
analyzer (auto) |
YDWG02 |
Yacht Devices RAW (YDWG-02 / YDNU-02) | 19:07:21.014 R 09F8017F 50 C3 … |
analyzer (auto) |
ACTISENSE_N2K_ASCII |
Actisense N2K ASCII | A173321.107 23FF7 1F513 012F… |
analyzer (auto) |
| Actisense NGT-1 binary | Actisense NGT-1 USB / serial / TCP gateway | (binary) | actisense-serial -r <dev> | analyzer |
| Actisense EBL | Actisense .ebl log files |
(binary) | actisense-serial -r <file.ebl> | analyzer |
| Actisense W2K-1 | W2K-1 gateway capture | {"pgn":60928,"payload":[147,19,6,0,238,…]} |
actisense-serial -r <file> | analyzer |
| candump (Angstrom) | Linux can-utils | <0x18eeff01> [8] 05 a0 be 1c 00 a0 a0 c0 |
candump2analyzer <file> | analyzer |
| candump (Debian) | Linux can-utils | can0 09F8027F [8] 00 FC FF FF 00 00 FF FF |
candump2analyzer <file> | analyzer |
| candump log | Linux can-utils (candump -l) |
(1502979132.106111) slcan0 09F50374#000A00FFFF00FFFF |
candump2analyzer <file> | analyzer |
| tshark / pcap | Wireshark text export of a CAN pcap | … CAN 16 XTD: 0x09fd0223 00 49 02 1c a7 fa ff ff |
candump2analyzer <file> | analyzer |
| Navico (TCP port 8086) | Navico raw dump | 0021200 0e 1d ff 9d 08 00 00 00 80 df 3f 9f 34 12 ff 0d |
candump2analyzer <file> | analyzer |
| PCAN-View | PEAK PCAN-View v1.1 trace | 1) 2.7 Rx 09F11324 8 53 84 9E 01 00 FF FF FF |
candump2analyzer <file> | analyzer |
pcap2candump can additionally turn a raw .pcap capture into the candump log
format for candump2analyzer. Example captures for many of these formats live in
the samples/ directory.
make builds the C tools into rel/<platform>/; make rust builds canboat
and keel into target/release/. Neither needs the other — you can build
just the side you care about.
Use make rust rather than a bare cargo build if you have edited
database/: the Rust PGN tables are generated by keel and committed, so
cargo alone will compile against the previous ones.
Full instructions are in BUILDING.md, and the Wiki covers per-platform detail and how to start extending the PGN database.
PGN definitions live as YAML under database/ (one file per PGN
variant in pgns/, one per enumeration in lookups/). The easiest way to add
a new message or correct an existing one is the built-in, evidence-first
editor:
./keel/keel editPaste one or more real captures, and the local web editor stacks the bytes, decodes them live as you define fields, suggests an existing definition to clone when your frame nearly matches one, and lets you save your captures as regression tests — all rule-checked before it will write. See AGENTS.md §9 for the full workflow (editor and by-hand) and CONTRIBUTING.md for the evidence and commit rules.
Pull requests are welcome! See CONTRIBUTING.md for how to write your PR title (it becomes the release changelog entry), the source/generated lockstep rule, and what evidence a database change needs.
If you just want to use the definitions in XML or JSON format, use the versions in the docs directory, e.g. (./docs/canboat.xml) or (./docs/canboat.json).
We recommend that you pin your retrieval to a particular release. Semantic versioning is used to warn you of major (x.0.0) or minor (0.x.0) changes to the
definitions.
See Changelog.
- canboatjs — pure JavaScript NMEA 2000 decoder and encoder
(The Rust library used to live separately as canboat-rs; it was merged into
this repository in v8 and is the canboat-* crates described above.)
Snapshot of early October 2026. The CANboat version each project uses and when it was last updated were taken from GitHub on 7 October 2026 and will drift. The current CANboat release is v8.3.0. The survey behind this table, with more projects, is in research/downstream-survey-2026-10.md. Maintain one of these, or another project built on CANboat? Open an issue to correct your entry or to be notified of new major and minor releases.
| Project | What it is | Uses CANboat as | CANboat version | Project last updated |
|---|---|---|---|---|
| AvNav | Chart plotter | NMEA 2000 data from the n2kd server |
binaries | 2026-10 |
| boatkit n2k | Go NMEA 2000 library | Go types generated from canboat.json at a pinned release | v8.1.0 | 2026-10 |
| CanLab | CAN, J1939 and NMEA 2000 analysis workbench (Python) | PGN table built from canboat.json | v8.2.1 | 2026-09 |
| canboat_vendor | ROS 2 package | builds the CANboat tools | 2026-08 (pre-v8.0.0-beta3) | 2026-08 |
| go-nmea-client | Go NMEA 2000 library | canboat.json loaded at runtime | whichever file is supplied | 2023-07 |
| korri-n2k | NMEA 2000 stack for embedded Rust | code generated from canboat.json | v7.1.0 | 2026-09 |
| maritime-dissector | Wireshark dissectors for maritime protocols | NMEA 2000 dissector generated from canboat.json | latest, at generation time | 2026-03 |
| n2k | Erlang NMEA 2000 library | vendored CANboat definitions | v4.12.0 | 2026-04 |
| nmea2000 | Python NMEA 2000 decoder and encoder | vendored canboat.json, synced weekly | v8.1.0 | 2026-10 |
| nmea2000 Home Assistant integration | NMEA 2000 PGNs as Home Assistant devices and entities | through nmea2000 | v8.1.0 | 2026-10 |
| NMEA2000-Analyzer | Windows GUI | canboat.json downloaded from master at runtime | latest | 2026-07 |
| nmea2000-rs | Rust NMEA 2000 decoder and encoder | code generated from canboat.json at build time | v6.1.3 | 2026-02 |
| open-ships n2k | Go NMEA 2000 library and bus node | decoders generated from a vendored canboat.json | v7.1.0 | 2026-09 |
| pgntui | Terminal UI for NMEA 2000 (Python) | vendored canboat.json | v6.2.0 | 2026-06 |
| signalk-server-go | Signal K server in Go | decodes NMEA 2000 with a vendored canboat.xml | v6.2.2 | 2026-09 |
| Smart Boat Innovations integrations | ha-smart2000usb and ha-smart2000esp: NMEA 2000 into Home Assistant over USB-CAN or an ESP32 | decoders generated from canboat.json | v5.0.3 | 2026-04 |
CANboat and its sibling canboatjs are command-line tools. If you just want to use your NMEA 2000 network, these projects build on them and provide a friendlier experience:
If you find CANboat useful, please consider supporting its development via GitHub Sponsors.
(C) 2009-2026, Kees Verruijt, Harlingen, The Netherlands.
This file is part of CANboat.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.