Bridge an NMEA 2000 (SocketCAN) bus to TCP clients as Yacht Devices RAW (YDRAW)
Project description
n2k2ip
Bridge an NMEA 2000 (SocketCAN) bus onto the network: read raw CAN frames from a SocketCAN interface and stream them to TCP clients as Yacht Devices RAW (YDRAW) text — one line per CAN frame. A software equivalent of a Yacht Devices Wi-Fi gateway (YDWG-02) in RAW mode.
NMEA2000 bus ──(can0)──▶ n2k2ip ──(tcp/1457)──▶ many clients
(plotters, loggers, OpenCPN, …)
Why it exists / design
The data is live, and stale data is worthless. Everything here serves that:
- Drop-old, never back up. Each client has a bounded outbound queue. When a client
can't keep up (a slow or stalled Wi-Fi link), the oldest whole lines are dropped so
it always receives recent frames instead of a growing backlog. Maximum staleness is
bounded by the
MAX_QUEUEconstant (100 lines) inn2k2ip.py. - One slow client can't hurt the others. All writes are non-blocking; a stalled or dead client never blocks the CAN reader or the other clients.
- Low latency by default.
TCP_NODELAYis set on every client, so each line hits the wire immediately (no Nagle batching). Over Wi-Fi this matters — measured median latency for this kind of small-message stream is a few ms with NODELAY vs tens of ms without. - Lightweight. Single-threaded, event-driven (
selectors) — no threads, no busy-poll, no dependencies. Pure Python standard library.
Requirements
- Linux with SocketCAN (a
can0-style interface) - Python 3.9+ — no third-party packages (stdlib only)
- A synchronised host clock — YDRAW timestamps are taken from the host's UTC
clock, so make sure NTP is running and disciplined (e.g.
systemd-timesyncdorchrony). On a headless/boat box without a network, fit an RTC or expect the clock — and therefore the timestamps — to be wrong until time is acquired.
Install
On Raspberry Pi OS (and other Debian 12+ systems) the system Python is
"externally managed", so use pipx — it drops the n2k2ip command on your
PATH in its own isolated environment and sidesteps --break-system-packages:
sudo apt install pipx # once, if you don't have it
pipx install n2k2ip
There are no third-party dependencies, so plain pip install n2k2ip also works
if you'd rather (add --break-system-packages, or install into a virtualenv).
Because n2k2ip is a single stdlib-only file, you can also skip installing entirely and run it straight from a checkout:
python3 n2k2ip.py --channel can0 --port 1457
Quick start
# bring the CAN interface up at the NMEA2000 bitrate
sudo ip link set can0 up type can bitrate 250000
# serve YDRAW on tcp/1457
n2k2ip --channel can0 --port 1457
Connect and watch:
nc <host> 1457
# 09:46:57.556 R 0DF50B16 00 E6 05 00 00 FF 7F FF
# 09:46:57.556 R 00FA8C3E 4C 08 C2 24 7E E8 61 4B
# ...
Usage
n2k2ip [--channel can0] [--port 1457] [--log-level INFO]
(or python3 n2k2ip.py … when running from a checkout)
| option | default | meaning |
|---|---|---|
--channel |
can0 |
SocketCAN interface to read |
--port |
1457 |
TCP port to serve YDRAW on |
--log-level |
INFO |
DEBUG, INFO, WARNING, ERROR |
Per-client buffer depth is the MAX_QUEUE constant in n2k2ip.py (100 lines). When a
client falls behind, the oldest lines are dropped so it never lags more than ~100
frames — roughly 0.2–0.5 s of backlog on a busy bus. Raise it only if you'd rather
tolerate longer stalls than skip frames.
Output format (YDRAW)
Each line is one CAN frame:
HH:MM:SS.mmm R <8-hex CAN-ID> <space-separated hex data bytes>
Lines are terminated with \r\n. Direction is always R (received). The
timestamp is the host clock in UTC (HH:MM:SS.mmm). The YDRAW spec calls for
UTC sourced from the NMEA 2000 bus when available; we don't decode the bus, so we
use the host's UTC clock instead — accurate as long as the host clock is synced
(e.g. NTP).
Multi-frame (fast-packet) messages are passed through, not reassembled — and that
is correct for YDRAW, which is a per-CAN-frame format. Each fast-packet frame is sent
as its own line with its sequence/frame-counter byte intact; reassembling them into a
logical PGN is the consumer's job (e.g. canboat analyzer, OpenCPN, a YDRAW
decoder). Over a lossy link this means a lost frame loses its whole fast-packet PGN —
inherent to the format, same as a hardware gateway. For that reason TCP is preferred
over UDP/broadcast on Wi-Fi, where reliability and low jitter matter most.
Run as a service (systemd)
Install it globally with pipx, then drop in the shipped unit:
sudo apt install pipx # once, if you don't have it
sudo pipx install --global n2k2ip # -> /usr/local/bin/n2k2ip
sudo cp n2k2ip.service /etc/systemd/system/
# edit the unit if your channel/port differ, or if can0 is brought up elsewhere
sudo systemctl daemon-reload
sudo systemctl enable --now n2k2ip.service
journalctl -u n2k2ip.service -f
Update later with sudo pipx upgrade --global n2k2ip. If you'd rather not
pip-install at all, the single stdlib-only file can just be copied and run
directly — see the comment header in n2k2ip.service.
Behaviour notes
- CAN reconnect. If the interface goes down or the controller goes bus-off, the reader logs it and reopens the socket with backoff — appropriate for a boat bus that may power-cycle.
- Error/remote frames are skipped; only data frames are forwarded.
- No filtering. Every data frame on the bus is forwarded. Add a SocketCAN filter in
_open_canif you need to restrict PGNs.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file n2k2ip-0.1.0.tar.gz.
File metadata
- Download URL: n2k2ip-0.1.0.tar.gz
- Upload date:
- Size: 8.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c0ddb8cdaf28793bd0ab5ac7ec6f832d12cfea6d5ebf302c79c978c95e722b4
|
|
| MD5 |
80df1eb511f9f361a4035f34bcd3e880
|
|
| BLAKE2b-256 |
18d9044323cb8e393b1b16c10aaba1238d177b7c6765bcf171da843fb1eaff94
|
File details
Details for the file n2k2ip-0.1.0-py3-none-any.whl.
File metadata
- Download URL: n2k2ip-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e3e3da53d3afee39b0df95d0cfc8d2c7526ea80fd6ae659d93431ea103bf771
|
|
| MD5 |
d9a5f540ba691ca9555fac3398203deb
|
|
| BLAKE2b-256 |
676ecb2e8cfc19465614393e7b4a679f7e5409f77daea1a39238aa2deeae5197
|