fastnet2ip
Reads raw B&G Fastnet data from a serial port, decodes it with pyfastnet, and broadcasts it over UDP in your choice of format — NMEA 0183 or NMEA 2000 (over IP) — for Signal K, OpenCPN, chart plotters, and other navigation software on your network.
| Output | Flag | Default port | Use with |
|---|---|---|---|
| NMEA 0183 | --output nmea0183 (default) |
2002 | Chart plotters, OpenCPN, most navigation software |
| NMEA 2000 | --output nmea2000 |
2000 | Actisense, Yacht Devices, Signal K server (via UDP) |
Runs on Raspberry Pi, macOS, or Linux. Requires Python 3.11+.
Quick start
Install the CLI in its own isolated environment with pipx:
pipx install fastnet2ip
Run it against your Fastnet serial port:
fastnet2ip --serial /dev/ttyUSB0 --output nmea0183 --live-data
fastnet2ip --serial /dev/ttyUSB0 --output nmea2000 --live-data
No instruments to hand? Replay one of the bundled captures from the source repo
(tests/data/):
fastnet2ip --file example1_fastnet_data.txt --output nmea0183 --live-data
--live-data prints a live channel table to the console once per second:
The app can also be invoked as a module:
python3 -m fastnet2ip ...— equivalent to thefastnet2ipcommand.
To upgrade later: pipx upgrade fastnet2ip.
Alternative installs (pip venv / from source)
# pip into a virtual environment
python3 -m venv ~/fastnet2ip-venv
source ~/fastnet2ip-venv/bin/activate
pip install fastnet2ip
# from source (development)
git clone https://github.com/ghotihook/fastnet2ip
cd fastnet2ip
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[test]"
The Fastnet toolkit
Three projects stack together — pick the one that matches where you want the data to end up:
| Project | What it does | Use it when |
|---|---|---|
| pyfastnet | Decoder library. Turns raw Fastnet bytes into Signal K paths in SI units. | You're writing your own Python and want the decoded data. |
| fastnet2ip (this app) | Serial → network. Broadcasts decoded data over UDP as NMEA 0183 or NMEA 2000 (over IP). | Feeding Signal K, OpenCPN, or a plotter over WiFi / Ethernet. |
| fastnet2n2k | Serial → physical NMEA 2000 bus. Transmits PGNs onto a CAN backbone via SocketCAN. | Wiring into a real NMEA 2000 network / chartplotter. |
┌─ fastnet2ip → UDP (NMEA 0183 / NMEA 2000 over IP) → Signal K, OpenCPN, plotters
B&G Fastnet bus ─(serial)─→ pyfastnet ─┤
└─ fastnet2n2k → SocketCAN (NMEA 2000 PGNs) → CAN backbone, chartplotter
This app builds on pyfastnet: pyfastnet handles the protocol layer (frame sync,
checksums, decoding raw bytes into named channels); fastnet2ip handles everything
above that — reading the serial port, a live data store, mapping channels to
output sentences/frames, rate limiting, and UDP broadcast. If you need the data on
a physical NMEA 2000 CAN backbone rather than over the network, use
fastnet2n2k instead.
Running as a systemd service
For an always-on bridge (e.g. a Raspberry Pi), run fastnet2ip under systemd so
it starts on boot and restarts on failure.
Install it globally, not per-user. A plain
pipx installgoes to a user's~/.local/bin, which a root-run service can't rely on. Usepipx install --globalso the command lands in/usr/local/bin.
1. Install globally with pipx
sudo apt install pipx # once, if you don't have it
sudo pipx install --global fastnet2ip
This gives you /usr/local/bin/fastnet2ip, the path the unit below uses.
(--global needs pipx ≥ 1.5.)
2. Create the unit file
Paste the following into /etc/systemd/system/fastnet2ip.service
(e.g. sudo nano /etc/systemd/system/fastnet2ip.service). The same template ships
as fastnet2ip.service in the source repository.
[Unit]
Description=fastnet2ip Service
####################################### CHANGE THIS IF PORT CHANGES
After=dev-ttyUSB0.device
BindsTo=dev-ttyUSB0.device
[Service]
Type=simple
User=root
# Uncomment ONE ExecStart line for the output mode you want:
# NMEA 2000 output:
ExecStart=/usr/local/bin/fastnet2ip --output nmea2000 --serial /dev/ttyUSB0 --udp-port 2000 --n2k-format ydwg --n2k-src 201 --n2k-pri 4 --log-level INFO
# NMEA 0183 output:
#ExecStart=/usr/local/bin/fastnet2ip --output nmea0183 --serial /dev/ttyUSB0 --udp-port 2002 --log-level INFO
Restart=always
RestartSec=10
# === RESOURCE LIMITS ===
OOMScoreAdjust=-700
OOMPolicy=continue
MemoryMax=128M
MemoryHigh=96M
TimeoutStopSec=30
# === LOGGING ===
StandardOutput=journal
StandardError=journal
SyslogIdentifier=fastnet2ip
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
Before enabling, edit it to match your setup:
After=/BindsTo=— your serial device, in systemd's escaped form (/dev/ttyUSB0→dev-ttyUSB0.device)- the active
ExecStart=— pick one line (NMEA 0183 or NMEA 2000) and set the correct--serialport
3. Enable and start it
sudo systemctl daemon-reload
sudo systemctl enable fastnet2ip
sudo systemctl start fastnet2ip
sudo journalctl -u fastnet2ip -f # follow the logs
To upgrade later:
sudo pipx upgrade --global fastnet2ip && sudo systemctl restart fastnet2ip.
Command-line options
Shared
| Argument | Default | Description |
|---|---|---|
--output FORMAT |
nmea0183 |
nmea0183 or nmea2000 |
--serial PORT |
— | Serial port (e.g. /dev/ttyUSB0) |
--file PATH |
— | Path to a recorded hex file |
--log-level LEVEL |
INFO |
DEBUG, INFO, WARNING, ERROR |
--live-data |
off | Print live channel table to console once per second |
--ignore-gps |
off | Suppress GPS channels — see below |
--ignore-heading |
off | Suppress heading channels — see below |
--host ADDR |
255.255.255.255 |
UDP destination host |
--udp-port N |
2002 / 2000 |
UDP port (default depends on output mode) |
NMEA 2000 (--output nmea2000)
| Argument | Default | Description |
|---|---|---|
--n2k-src N |
201 |
N2K source address 0–253 (accepts hex: 0xC9) |
--n2k-pri N |
4 |
Message priority 0 (highest) – 7 (lowest); the raw sensor channels always go at 7 |
--n2k-format FMT |
ydwg |
Wire format: ydwg or pcdin (see below) |
Avoiding feedback loops: --ignore-gps and --ignore-heading
Most B&G systems receive GPS (and sometimes heading) from an external source and pass it through onto the Fastnet bus alongside the instrument data. If you also connect that same source directly to your network, re-broadcasting it from this bridge creates a feedback loop — downstream software sees the same data arriving twice, which can cause jumps, conflicts, or incorrect averaging.
Use --ignore-gps and/or --ignore-heading when that source is already on
your network. Both flags work with --output nmea0183 and --output nmea2000.
If the bridge is the only source of that data on your network, omit them.
--ignore-gps suppresses:
| Fastnet channel | NMEA 0183 | NMEA 2000 |
|---|---|---|
| LatLon | GLL | PGN 129025 |
| Speed Over Ground | VTG | PGN 129026 |
| Course Over Ground (True) | VTG | PGN 129026 |
| Course Over Ground (Mag) | VTG | PGN 129026 |
--ignore-heading suppresses:
| Fastnet channel | NMEA 0183 | NMEA 2000 |
|---|---|---|
| Heading | HDM / HDT | PGN 127250 |
| Heading (Raw) | XDR RAW_HDG |
PGN 130824 (key 74) |
Hardware
Fastnet uses two-wire differential transmission. RS-485 adapters work well; the CAN Hat option includes 120 Ω termination which is recommended.
Tested hardware
- Raspberry Pi 4/5/Zero 2W
- Mac
- M5Stack Core MP135 — has RS422/485 built in; a bit more fiddly to set up but good for a permanent install
- DTECH USB RS422/RS485 dongle — works out of the box
- Waveshare RS485 CAN HAT — add to
/boot/firmware/config.txt:dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25,spimaxfrequency=2000000
Wiring
| Fastnet wire | RS-485 |
|---|---|
| White | Data + |
| Green | Data - |
Serial settings: 28,800 baud, 8 data bits, odd parity, 2 stop bits
Raspberry Pi built-in UART (
/dev/ttyAMA*): the first open of a Pi's built-in PL011 UART after a boot can leave the hardware at 9600 even though 28,800 was requested — 28,800 is non-standard, so it takes the kernel'sBOTHERpath, which has a known first-open quirk (bytes arrive but nothing decodes; a second run "fixes" it).fastnet2ipworks around it automatically when opening the port. USB RS-485 adapters (e.g. the DTECH dongle) are not affected. Full diagnosis:docs/uart_first_open_baud_fix.md.
RS485 boards with automatic direction control (e.g. the Waveshare RS485 CAN HAT): the transceiver switches itself to transmit whenever the Pi's TX pin is low, so anything that pulls that pin low reaches the Fastnet bus, even though fastnet2ip never transmits. On the HAT the pin is GPIO14, the Pi's primary UART (
serial0):
- Turn off the serial console. In
sudo raspi-config→ Interface Options → Serial Port, answer No to the login shell and Yes to the serial hardware. Otherwise Linux writes its boot messages, and a login prompt, onto the Fastnet bus.- If the instruments raise an alarm every time the Pi reboots, pin TX high from the firmware with
gpio=14=op,dhin/boot/firmware/config.txt.Why, and how to confirm it on the wire:
docs/rs485_tx_boot_glitch.md.
Output reference
NMEA 0183
| Sentence | Content |
|---|---|
| VHW | Boatspeed; heading in True or Magnetic field per instrument configuration |
| DBT | Depth below transducer |
| RSA | Rudder angle |
| HDM | Magnetic heading (emitted when instrument is configured for magnetic reference) |
| HDT | True heading (emitted when instrument is configured for true reference) |
| MWD | True wind direction and speed; direction True or Magnetic per instrument configuration |
| MWV | True wind angle/speed (ref T) |
| MWV | Apparent wind angle/speed (ref R) |
| MDA | Air temp, sea temp, barometric pressure |
| VTG | COG and SOG |
| VPW | Velocity made good |
| VDR | Tidal set and drift; set direction True or Magnetic per instrument configuration |
| GLL | Latitude/Longitude |
True vs Magnetic: The Fastnet data stream carries no magnetic variation or deviation. Whether a channel is labelled True or Magnetic reflects the reference configured in the B&G instrument — not a computed conversion. VHW, HDM/HDT, MWD, and VDR each output whichever reference the instrument is set to.
XDR transducers:
| XDR name | Content |
|---|---|
BATTV |
Battery voltage |
ROLL |
Heel angle (degrees) |
PITCH |
Fore/aft trim (degrees) |
RAW_WIND_A |
Apparent wind angle raw sensor value |
RAW_WIND_S |
Apparent wind speed raw sensor value |
RAW_BSP |
Boatspeed raw sensor value |
RAW_HDG |
Heading raw sensor value |
NMEA 2000
| PGN | Name |
|---|---|
| 127237 | Heading/Track Control (autopilot mode and target) |
| 127245 | Rudder |
| 127250 | Vessel Heading |
| 127251 | Rate of Turn |
| 127257 | Attitude (heel + trim) |
| 127508 | Battery Status |
| 128000 | Leeway |
| 128259 | Boat Speed |
| 128267 | Water Depth |
| 128275 | Distance Log |
| 129025 | Position |
| 129026 | COG & SOG |
| 129283 | Cross Track Error |
| 129291 | Set & Drift |
| 130306 | Wind Data (apparent, true boat-ref, true ground-ref) |
| 130312 | Temperature (sea + air) |
| 130314 | Pressure |
| 130824 | B&G key-value data: raw boatspeed, heading, wind speed and angle |
Autopilot (127237). The pilot's mode is sent as the steering mode, with the compass target as heading-to-steer — the same as fastnet2n2k:
| Pilot mode | Steering mode | Heading-to-steer |
|---|---|---|
| Standby | Main Steering | not available |
| Compass | Heading Control | compass target |
| Wind | Heading Control | compass target |
| Power | Non-Follow-Up Device | not available |
| NMEA WP | Track Control | compass target |
Wind and compass look the same on the wire, since the standard has no wind mode. The target is dropped in standby and Power, even though Fastnet keeps sending the last one. It is a status broadcast only; nothing here listens for commands.
Raw sensor channels (130824). The uncalibrated readings, for logging, in B&G's own
proprietary key-value PGN — byte for byte the frames fastnet2n2k sends, so one
decoder reads both. After the B&G header (7D 99) comes a 12-bit key and 4-bit
length, then the value. The keys are Fastnet channel numbers, as B&G's own are:
| Key | Fastnet channel |
|---|---|
| 66 (0x42) | Boatspeed (Raw) |
| 74 (0x4A) | Heading (Raw) |
| 78 (0x4E) | Apparent Wind Speed (Raw) |
| 82 (0x52) | Apparent Wind Angle (Raw) |
Values are signed 16-bit counts exactly as Fastnet carries them, one key per message
(a single CAN frame), sent at full rate and at priority 7. The frame byte by byte, the
reasoning and a decoder are in fastnet2n2k's
docs/bandg_130824_raw_channels.md.
(These replace the PGNs 65280–65282 of earlier versions.)
Wire formats (--n2k-format):
ydwg— Yacht Devices RAW UDP:HH:MM:SS.mmm R XXXXXXXX DD DD DD..., one line per CAN framepcdin— PCDIN sentences for Signal K server UDP input:$PCDIN,PPPPPP,TTTTTTTT,SS,DDDD...*CC, one sentence per message; fast-packet PGNs carry their whole reassembled payload
How it works
Serial port / hex file
│
▼
pyfastnet FrameBuffer ← byte sync, checksum, channel decoding
│
▼
Live data store ← latest value per channel
│
▼
Output handler ← nmea0183 or nmea2000, selected by --output
│
▼
UDP broadcast
A message is sent on every channel update — a repeated value is still live data
worth putting on the wire — rate-capped at 20 Hz per channel so a fast path can't
flood UDP. The four raw sensor channels are the exception: they are for logging, and
go out at full rate. There is no dedupe and no periodic re-broadcast: the bridge
reflects what the instruments provide, and when a source goes quiet its output stops
(consumers time it out themselves). This matches the sibling fastnet2n2k bridge's
cadence.
To add a new output format, implement OutputHandler in fastnet2ip/handlers/
and add it to _HANDLERS in fastnet2ip/__main__.py.
Bench testing tools
Two helper scripts for capturing and replaying Fastnet data live in the tools/
directory of the source repository (they are not installed by pip/pipx — clone the
repo to use them):
# record raw Fastnet data to file
python3 tools/record_fn.py --port /dev/ttyUSB0 --output my_capture.txt
# play a recording back to a serial port
python3 tools/playback_fn.py --port /dev/ttyUSB1 --input my_capture.txt
# play back at 4x wire speed
python3 tools/playback_fn.py --port /dev/ttyUSB1 --input my_capture.txt --speed 4
Playback is paced at the wire speed implied by the baud rate and framing
(28800 baud, 8O2 = 12 bits/byte = 2400 bytes/s), so a capture takes about as long
to replay as it did to record while the bus was active. Recordings carry no
timestamps, so idle gaps on the original bus are not reproduced — a replay is
therefore shorter than the original recording session, and is not a faithful
reconstruction of real-world message timing. --speed scales the rate; values
above 1.0 are capped by the hardware on a real serial port, but do take effect on
a virtual port (pty/socat).
Recordings can also be replayed through the main app with
fastnet2ip --file my_capture.txt. Note this uses a different, fixed pacing
(256-byte chunks every 50 ms ≈ 5120 B/s) and is not wire-speed accurate.
Development
git clone https://github.com/ghotihook/fastnet2ip
cd fastnet2ip
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[test]"
pytest
Build distributable artifacts:
pip install build
python -m build # writes sdist + wheel to dist/
Releasing to PyPI
The version lives in one place — __version__ in fastnet2ip/__init__.py — which
pyproject.toml reads automatically.
- Bump the version in
fastnet2ip/__init__.pyper semver (patch = bug fix, minor = new backward-compatible flag, major = breaking change). - Rebuild clean and validate (stale
dist/files would otherwise be uploaded):rm -rf dist build *.egg-info python -m build twine check dist/*
- Tag and upload:
git commit -am "Release X.Y.Z" git tag vX.Y.Z git push origin main --tags twine upload dist/* # needs a PyPI account + API token in ~/.pypirc
- Users upgrade with
pipx upgrade fastnet2ip(orpip install --upgrade fastnet2ip).
PyPI never lets you re-upload an existing version — even a deleted or "yanked" one. If a release has a bug, bump to the next patch version and re-publish; you can never overwrite a number that's already live.
Acknowledgments
- trlafleur — collected significant background research
- Oppedijk — protocol background
- timmathews — substantial C++ implementation
License
MIT — see LICENSE.
Metadata
Release files for fastnet2ip 3.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fastnet2ip-3.3.1.tar.gz | 38.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fastnet2ip-3.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 67.0 kB
Release files / fastnet2ip-3.3.1.tar.gz
| Download URL | fastnet2ip-3.3.1.tar.gz |
|---|---|
| Size | 38.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
70f88bfc1bcd1f715b6c26f09f34aca7ba09812227798c9464b220a2c76b1475
|
|
BLAKE2b-256 checksum How to use checksums |
5145e28f140a335c0fc20c4938c6b885e674d661e928a3308a50661c219a74de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Release files / fastnet2ip-3.3.1-py3-none-any.whl
| Download URL | fastnet2ip-3.3.1-py3-none-any.whl |
|---|---|
| Size | 28.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
91d4ef64e197d1fe9bc9b0a1707bd338640800989c901441736d36565639cf0c
|
|
BLAKE2b-256 checksum How to use checksums |
a18df9fee9397f6901b7ea6668ced40b95656f0a14d8b601eda91ebaad194d11
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|