Nmea2000
NMEA 2000 Python encoder and decoder based on canboat database
Install / Use
/learn @tomer-w/Nmea2000README
NMEA 2000 Python Library
A Python library for encoding and decoding NMEA 2000 frames. The encoding and decoding is based on the extensive canboat database. It also supports inexpensive CANBUS USB and TCP devices as gateways between your NMEA 2000 boat network and any Python code that wants to receive or send these messages.
This package is the backend for the Home Assistant NMEA 2000 Integration.
Features
- Decode NMEA 2000 frames: Parse and interpret raw NMEA 2000 data.
- Encode NMEA 2000 frames: Convert structured data back into the NMEA 2000 frame format.
- USB client: Send and receive NMEA 2000 data over CANBUS USB devices like Waveshare USB-CAN-A
- python-can client: Send and receive NMEA 2000 data over any generic USB or SocketCAN device supported by python-can
- TCP client: Send and receive NMEA 2000 data over CANBUS TCP devices like:
- PGN-specific parsing: Handle various PGNs with specific parsing rules based on canboat.
- Stateful decoder: The decoder supports NMEA 2000 fast messages, which are split across multiple CANBUS messages.
- CLI support: Built-in command-line interface for encoding and decoding frames.
Installation
You can install the library using pip:
pip install nmea2000
Alternatively, you can clone the repository and install it locally:
git clone https://github.com/tomer-w/nmea2000.git
cd nmea2000
pip install .
Usage
Decode NMEA 2000 Frame (CLI)
To decode a frame, use the decode command followed by the frame in Actisense hex format:
nmea2000-cli decode --frame "09FF7 0FF00 3F9FDCFFFFFFFFFF"
65280 Furuno: Heave: Manufacturer Code = Furuno (bytes = "3F 07"), Reserved = 3 (bytes = "03"), Industry Code = Marine (bytes = "04"), Heave = -0.036000000000000004 (bytes = "DC"), Reserved = 65535 (bytes = "FF FF 00")
Or in JSON format:
{"PGN":65280,"id":"furunoHeave","description":"Furuno: Heave","fields":[{"id":"manufacturer_code","name":"Manufacturer Code","description":"Furuno","unit_of_measurement":"","value":"Furuno","raw_value":1855},{"id":"reserved_11","name":"Reserved","description":"","unit_of_measurement":"","value":3,"raw_value":3},{"id":"industry_code","name":"Industry Code","description":"Marine Industry","unit_of_measurement":"","value":"Marine","raw_value":4},{"id":"heave","name":"Heave","description":"","unit_of_measurement":"m","value":-0.036000000000000004,"raw_value":-36},{"id":"reserved_48","name":"Reserved","description":"","unit_of_measurement":"","value":65535,"raw_value":65535}],"source":9,"destination":255,"priority":7}
Example Code
from nmea2000.decoder import NMEA2000Decoder
# Initialize decoder
decoder = NMEA2000Decoder()
# Decode a frame
frame_str = "09FF7 0FF00 3F9FDCFFFFFFFFFF"
decoded_frame = decoder.decode(frame_str)
# Print decoded frame
print(decoded_frame)
Example reading packets using python-can
import can
from nmea2000.decoder import NMEA2000Decoder
# Initialize decoder
decoder = NMEA2000Decoder()
# Connect to CAN bus (e.g. slcan device on /dev/ttyUSB0)
bus = can.interface.Bus(interface='slcan', channel="/dev/ttyUSB0", bitrate=250000)
# Decode frames
for msg in bus:
decoded_frame = decoder.decode(msg)
# Print decoded frame when ready (fast data intermediate frames return None)
if decoded_frame is not None:
print(decoded_frame)
Simple N2KDevice example
If you want to behave like a small NMEA 2000 device instead of just reading frames, N2KDevice wraps the transport client, handles address claiming, and lets you send/receive NMEA2000Message objects directly:
import asyncio
from nmea2000.device import N2KDevice
from nmea2000.message import NMEA2000Field, NMEA2000Message
async def handle_received(message: NMEA2000Message) -> None:
print(f"received PGN {message.PGN} from source {message.source}")
async def main() -> None:
device = N2KDevice.for_python_can(
interface="socketcan",
channel="can0",
preferred_address=25,
model_id="Python demo device",
manufacturer_information="nmea2000 README example",
)
device.set_receive_callback(handle_received)
try:
await device.start()
await device.wait_ready(timeout=5)
await device.send(
NMEA2000Message(
PGN=127250,
id="vesselHeading",
priority=2,
source=0, # 0 means "use the address claimed by this device"
destination=255,
fields=[
NMEA2000Field(id="sid", raw_value=0),
NMEA2000Field(id="heading", value=1.0),
NMEA2000Field(id="deviation", raw_value=0),
NMEA2000Field(id="variation", raw_value=0),
NMEA2000Field(id="reference", raw_value=0),
NMEA2000Field(id="reserved_58", raw_value=0),
],
)
)
await asyncio.sleep(10)
finally:
await device.close()
asyncio.run(main())
Use N2KDevice.for_ebyte(...), N2KDevice.for_yacht_devices(...), N2KDevice.for_waveshare(...), or N2KDevice.for_actisense(...) if you are connecting through one of those gateways instead of python-can.
TCP Client CLI
nmea2000-cli tcp_client --server 192.168.0.46 --port 8881 --type actisense
Use --json to output received messages as JSON (one object per line), useful for piping into other tools:
nmea2000-cli tcp_client --server 192.168.0.46 --port 8881 --type actisense --json
The --json flag is also available for usb_client and can_client.
TCP Client code
async def handle_received_data(message: NMEA2000Message):
"""User-defined callback function for received data."""
print(f"Callback: Received {message}")
client = EByteNmea2000Gateway(ip, port)
client.set_receive_callback(handle_received_data) # Register callback
Encode NMEA 2000 Frame (CLI)
You can also encode data into NMEA 2000 frames using the encode command:
nmea2000-cli encode --data "your_data_to_encode"
Example:
nmea2000-cli encode --data '{"PGN":65280,"id":"furunoHeave","description":"Furuno: Heave","fields":[{"id":"manufacturer_code","name":"Manufacturer Code","description":"Furuno","unit_of_measurement":"","value":"Furuno","raw_value":1855},{"id":"reserved_11","name":"Reserved","description":"","unit_of_measurement":"","value":3,"raw_value":3},{"id":"industry_code","name":"Industry Code","description":"Marine Industry","unit_of_measurement":"","value":"Marine","raw_value":4},{"id":"heave","name":"Heave","description":"","unit_of_measurement":"m","value":-0.036000000000000004,"raw_value":-36},{"id":"reserved_48","name":"Reserved","description":"","unit_of_measurement":"","value":65535,"raw_value":65535}],"source":9,"destination":255,"priority":7}'
Encoding frame: {"PGN":65280,"id":"furunoHeave","description":"Furuno: Heave","fields":[{"id":"manufacturer_code","name":"Manufacturer Code","description":"Furuno","unit_of_measurement":"","value":"Furuno","raw_value":1855},{"id":"reserved_11","name":"Reserved","description":"","unit_of_measurement":"","value":3,"raw_value":3},{"id":"industry_code","name":"Industry Code","description":"Marine Industry","unit_of_measurement":"","value":"Marine","raw_value":4},{"id":"heave","name":"Heave","description":"","unit_of_measurement":"m","value":-0.036000000000000004,"raw_value":-36},{"id":"reserved_48","name":"Reserved","description":"","unit_of_measurement":"","value":65535,"raw_value":65535}],"source":9,"destination":255,"priority":7}'
output:
09FF7 0FF00 3F9FDCFFFFFFFFFF
Example Code
from nmea2000.encoder import NMEA2000Encoder
from nmea2000.input_formats import N2KFormat
# Initialize encoder
encoder = NMEA2000Encoder(output_format=N2KFormat.TCP)
# Data to encode: vessel heading message (PGN 127250)
message = NMEA2000Message(
PGN=127250,
priority=2,
source=1,
destination=255,
fields=[
NMEA2000Field(
id="sid",
raw_value=0,
),
NMEA2000Field(
id="heading",
value=1, # 1 radian is 57 degrees
),
NMEA2000Field(
id="deviation",
raw_value=0,
),
NMEA2000Field(
id="variation",
raw_value=0,
),
NMEA2000Field(
id="reference",
raw_value=0,
),
NMEA2000Field(
id="reserved_58",
raw_value=0,
)
]
)
msg_bytes = encoder.encode(_generate_test_message())
print(msg_bytes)
Node-RED Integration
You can stream decoded NMEA 2000 data into Node-RED using the CLI with the --json flag and a Node-RED exec node.
Setup
-
Exec node — add an
execnode and configure:- Command:
nmea2000-cli tcp_client --server 192.168.1.100 --port 8881 --type EBYTE --json - Output: select "when stdout has data" so it emits a message for each line (not on process exit)
- Use spawn mode: enable
Use spawn() instead of exec() - Timeout: leave blank (this is a long-running process)
- Append msg.payload: uncheck
- Command:
-
JSON node — c
Related Skills
feishu-drive
352.2k|
things-mac
352.2kManage Things 3 via the `things` CLI on macOS (add/update projects+todos via URL scheme; read/search/list from the local Things database)
clawhub
352.2kUse the ClawHub CLI to search, install, update, and publish agent skills from clawhub.com
codebase-memory-mcp
1.3kHigh-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 66 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.
