canboat server

canboat server is the whole device → analyzer → n2kd pipeline in one process: it reads a gateway (see gateways.md), decodes every message, applies the device quirks you switch on, and serves the result over TCP. It replaces actisense-serial | analyzer | n2kd and its siblings.

canboat server --kind actisense --device /dev/ttyUSB0

Ports

Every port is a TCP port, listening on all interfaces unless --bind 127.0.0.1 keeps it to this host. The first five are the C n2kd's (-p and the ports after it), so its clients work unchanged. 0 turns a port off.

Option Default What it serves
--snapshot-port2597On connect, the latest JSON record of every PGN (one per PGN, source and instance), then close. AIS PGNs are on --ais-port instead, apart from 129026 and 129029, which are on both
--analyzer-port2598Decoded JSON, one record per line; read-only
--nmea0183-port2599NMEA 0183 sentences, AIVDM included; read-only
--input-port2600Write-only: PLAIN/FAST lines written here are sent onto the bus
--ais-port2601On connect, the latest record of every AIS PGN, plus 129026 and 129029, then close
--raw-port2603Raw frames as # format=FAST PLAIN lines, fast-packets already joined; read-only
--nmea0183-filter-port2605NMEA 0183 filter control (PGN 262657), for canboat tui; read and write
--overrides-port2606PGN rate override control (PGN 262658), for canboat tui; read and write

The JSON on the snapshot, analyzer and AIS ports takes convert's --id, --units and --wrap. Unlike convert, it writes a lookup field as its value and its name, {"value":381,"name":"B & G"}, as the C n2kd does (it runs analyzer -nv); --no-nv writes the name alone, "B & G", and --nv says the default, as convert takes both too. A value the lookup has no name for is {"value":N} by default and the bare number N with --no-nv. A key field whose name the lookup gives to more than one code is written as its code, 28, so records of B&G's three "Outside Temperature" keys stay apart. The snapshot and analyzer ports start with the header convert writes, whose showLookupValues says which; the AIS port has no header. The filter and override ports always write both, as canboat tui reads the codes.

The state the filter and override ports set is kept in --config-dir: /etc/default/canboat if it is writable (the systemd/root case), otherwise ~/.local/canboat. canboat server --help has the rest of the options.

Discovery

canboat server advertises itself over mDNS / DNS-SD as _canboat._tcp, so a client on the LAN can find it, see which ports it serves and what it runs, without being told a host and port. The instance is called canboat on <hostname>, with DNS-SD's (2) added when two servers share a host. --no-mdns turns it off, for a network where multicast is unwanted.

The SRV record names the analyzer port, or the first open port when that one is off. The TXT record:

Key Value
versionthe canboat version
namecanboat's own ISO NAME on the bus, 16 hex digits; only where canboat runs a node (--kind socketcan, canalyst or usb-can-a)
serialthe Model Serial Code in that node's Product Information (PGN 126996), so a client can match this server with what other devices, such as a TimeZero MFD, report about the bus
snapshot, analyzer, nmea0183, input, ais, raw, nmea0183-filter, overrides, mcu006one key per port that is open, named after its option without -port
quirksthe quirks that are on, comma-separated, by name only: gps-rollover=4 is gps-rollover
motion.name, mcu006.namethe NAME the motion or mcu006 quirk claims
mcu006.hostthe NAME of the MFD the mcu006 remote paired with, once it has; announced again when it changes

With --bind <address> the advert is only on that interface, as the ports are; otherwise it is on every interface that is up. It is withdrawn when the server stops on SIGINT or SIGTERM. canboat answers mDNS itself, so it needs neither Avahi nor Bonjour, and works alongside either. It is the mdns cargo feature, on by default; cargo build --no-default-features --features cli leaves it out of the binary.

dns-sd -B _canboat._tcp                      # macOS
avahi-browse -r _canboat._tcp                # Linux

Running it as a service

canboat server is one process with nothing else to supervise, so any init system with a restart policy will do. With systemd, as a system service in /etc/systemd/system/canboat.service:

[Unit]
Description=CANboat server
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/canboat server --kind actisense --device /dev/serial/by-id/usb-Actisense_NGT-1_XXXX-if00-port0
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

then sudo systemctl enable --now canboat, and journalctl -u canboat -f for its log. Or as a user service, in ~/.config/systemd/user/canboat.service, with ExecStart=%h/.cargo/bin/canboat … and WantedBy=default.target:

systemctl --user enable --now canboat
sudo loginctl enable-linger $USER    # start at boot, and keep running after you log out
journalctl --user -u canboat -f

From n2kd_monitor

n2kd_monitor, the perl script that restarted the C n2kd and its gateway driver, was removed in v8. What its /etc/default/n2kd set is now given to canboat server:

/etc/default/n2kd canboat server
INTERFACE_PROGRAM, INTERFACE_DEVICE--kind and --device
INTERFACE_OPTIONSthe gateway's options, such as --baud
ANALYZER_OPTIONS--id, --units; it decodes internally, so most no longer apply
N2KD_OPTIONSthe port options
MONITORyour init system's restart policy

Device quirks

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-rolloverCorrects 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 bussame three commands
gps-relayRe-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 herecanboat server --quirk gps-rollover=<device> --quirk gps-relay (needs a writable backend)
scx20Answers a PGN 59904 request for a Furuno SCX-20's Product Information on its behalf, so the Furuno Setting Tool can find itcanboat server --quirk scx20 (needs --kind socketcan, canalyst or usb-can-a)
wmmComputes magnetic variation locally with WMM 2025 and emits its own PGN 127258, asking older-WMM sources to stopcanboat server --quirk wmm
motionImpersonates a B&G H5000 Motion Sensor so a Navico Hercules accepts an SCX-20canboat server --quirk motion (needs --kind socketcan, canalyst or usb-can-a)
mcu006Presents a Furuno MCU-006 RotoKey remote to a TimeZero MFD: claims its NAME, answers discovery with its Product Information, announces itself every 5 s and pairs with the first MFD that offers. Its keys, pointer, knob and CURS/SCROLL keypad mode are driven over the --mcu006-port (2607), one JSON command a line, e.g. {"tap":"Up"}, {"key":"3","down":true}, {"pointer":{"dx":40,"dy":0}}, {"knob":-1}, {"click":true}, {"keypad":true}. Keys are keyboard keys (HID usage names or numbers); the MFD decides what each doescanboat server --quirk mcu006 (needs --kind socketcan, canalyst or usb-can-a)

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.

A capture as a live bus (--replay)

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.raw

Performance

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 s8.1 ×
canboat C9.1 s2.6 ×
canboat convert3.4 s1.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 s380 k frames / s
canboat convert | canboat n2kd (piped, 2 proc)6.5 s194 k sentences / s
canboat server (single proc)3.5 s360 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 ×.