Skip to content

NMEA 2000 Python encoder and decoder based on canboat database

License

Notifications You must be signed in to change notification settings

tomer-w/nmea2000

Repository files navigation

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
  • 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_actisense_string(frame_str)

# Print decoded frame
print(decoded_frame)

TCP Client CLI

nmea2000-cli tcp_client --server 192.168.0.46 --port 8881 --type actisense

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

TBD

from nmea2000.encoder import NMEA2000Encoder

# Initialize encoder
encoder = NMEA2000Encoder()

# 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_ebyte(_generate_test_message())
print(msg_bytes)

Development

Contributions, feedback, and suggestions to improve this project are welcome. If you have ideas for new features, bug fixes, or improvements, feel free to open an issue or create a pull request. I’m always happy to collaborate and learn from the community!

Please don't hesitate to reach out with any questions, comments, or suggestions.

Setup for Development

To contribute to this library, clone the repository and install the required dependencies:

git clone https://github.com/tomer-w/nmea2000.git
cd nmea2000
pip install -e .[dev]

Running Tests

To run the tests, use:

pytest

Running the CLI Locally

To test the CLI locally, you can use the following command:

python -m nmea2000.cli decode --frame "your_hex_encoded_frame"

License

This project is licensed under the Apache 2.0 license - see the LICENSE file for details.

Acknowledgements

  • This library leverages the canboat as the source for all PGN data.
  • Special thanks to Rob from Smart Boat Innovations. His code was the initial inspiration for this project. Some of the code here may still be based on his latest open-source version.

About

NMEA 2000 Python encoder and decoder based on canboat database

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •