Skip to main content

Sans-I/O NMEA 0183 + AIS (AIVDM/AIVDO) parser, Rust-backed

Project description

marlin-py

Python bindings for the Marlin Rust suite — NMEA 0183 envelope parsing, typed sentence decoding, AIS (AIVDM/AIVDO) message decoding with multi-sentence reassembly, and MISB ST 0601 (KLV) encode/decode.

Status

Wraps four Rust crates: marlin-nmea-envelope (framing + checksum), marlin-nmea-0183 (GGA, GLL, HDT, RMC, VTG, PSXN, PRDID typed decoders), marlin-ais (Types 1/2/3/5/18/19/24A/24B + reassembly), and marlin-klv (MISB ST 0601 UAS Datalink Local Set encode/decode). py.typed marker and .pyi stubs ship with the package; mypy --strict is clean across the entire python/marlin/, tests/, and examples/ tree.

Install

The PyPI distribution name is marlin-py; the Python import name stays plain marlin.

# From PyPI — wheels for Linux x86_64 / aarch64, macOS universal2,
# Windows x86_64. abi3 wheel covers Python 3.9 through 3.13+.
pip install marlin-py

# Local development — build the extension in-place
cd bindings/python
maturin develop

Quickstart: envelope

from marlin.envelope import StreamingParser

parser = StreamingParser()
parser.feed(b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n")
for sentence in parser:
    talker = sentence.talker.decode() if sentence.talker else ""
    print(talker, sentence.sentence_type, sentence.checksum_ok)
    # GP GGA True

OneShotParser handles UDP datagrams (no \r\n required). parse() is a one-call convenience for a single complete sentence.

Quickstart: NMEA typed decode

from marlin.envelope import parse
from marlin.nmea import Nmea0183Parser, decode_gga, DecodeOptions, PrdidDialect

# Single-sentence convenience.
raw = parse(b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47")  # raises EnvelopeError on framing/checksum failure
gga = decode_gga(raw)                # raises DecodeError if not GGA or fields are malformed
print(gga.latitude_deg, gga.longitude_deg, gga.fix_quality)

# Streaming parser — same feed/iterate API as the envelope layer.
parser = Nmea0183Parser.streaming()
parser.feed(b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n")
for msg in parser:
    print(type(msg).__name__, msg)

DecodeOptions configures dialect-sensitive sentences:

opts = DecodeOptions().with_prdid_dialect(PrdidDialect.PITCH_ROLL_HEADING)
parser = Nmea0183Parser.streaming(options=opts)

Per-sentence extension functions (decode_gga, decode_vtg, decode_hdt, decode_psxn, decode_prdid) are public so downstream code can build its own message enum and delegate to Marlin for the standard types.

Quickstart: AIS

from marlin.ais import AisParser, PositionReportA

parser = AisParser.streaming()
parser.feed(b"!AIVDM,1,1,,A,13aGmP0P00PD;88MD5MTDww@2<0L,0*23\r\n")
for msg in parser:
    if isinstance(msg.body, PositionReportA):
        print(msg.body.mmsi, msg.body.latitude_deg, msg.body.longitude_deg)

Multi-sentence reassembly is automatic — feed fragments in order and the parser yields a complete AisMessage only when the last fragment arrives.

Quickstart: KLV

from marlin.klv import St0601, decode, encode

s = St0601(timestamp_us=1_700_000_000_000_000)
s.sensor_latitude_degrees = 60.1768      # engineering-unit property
s.platform_heading_degrees = 159.97
wire = encode(s)                          # bytes
got = decode(wire)
print(got.sensor_latitude_degrees, got.timestamp_us)

St0601 fields are properties, not setter methods: assign s.sensor_latitude_degrees = ..., don't call s.set_sensor_latitude_degrees(...). decode raises KlvError on a malformed local set (bad key, truncated bytes, checksum mismatch). precision_timestamp(data) reads Tag 2 alone, without verifying the checksum, for callers that only need a cheap timestamp peek.

AIS clock modes

Fragment reassembly has three timeout behaviours, selected at construction.

No timeout — fragments are buffered until the 16-slot eviction cap is reached. The oldest incomplete group is discarded when a 17th arrives. No clock reads occur:

parser = AisParser.streaming()          # timeout_ms omitted → no timeout

Auto clock — the binding calls time.monotonic_ns() internally to drive the timeout. Use this when system time is reliable and you do not need deterministic replay:

parser = AisParser.streaming(timeout_ms=60_000)          # clock="auto" implied
# or explicitly:
parser = AisParser.streaming(timeout_ms=60_000, clock="auto")

Manual clock — the caller drives the clock via tick(now_ms=...). No clock reads occur inside the binding. This is the correct mode for unit tests, simulators, and any environment where time is patched:

parser = AisParser.streaming(timeout_ms=60_000, clock="manual")

parser.feed(fragment_bytes)
parser.tick(now_ms=current_time_ms)     # expire stale groups at this instant
for msg in parser:
    ...

Calling tick() on a parser that wasn't built with clock="manual" raises ValueError — including parsers built with no timeout_ms (which default to clock="auto").

Errors

Every exception is a MarlinError subclass (importable from marlin):

  • EnvelopeError — framing or checksum failure
  • DecodeError — field-level decode failure in a typed NMEA sentence
  • AisError — AIS armor or bit-level decode failure
  • ReassemblyError — fragment reassembly violation (out-of-order, channel mismatch, or timeout eviction)
  • KlvError — malformed KLV input (bad local-set key, truncated bytes, checksum mismatch)

Property tests (via Hypothesis) verify panic-freedom on arbitrary byte inputs — no input can cause the binding to crash the interpreter.

Examples

Six runnable scripts live in bindings/python/examples/:

Script What it shows
one_shot_no_crlf.py Single-datagram framing without \r\n (OneShotParser)
parse_log_file.py Disk-backed .nmea log replay (StreamingParser)
streaming_tcp_style.py TCP-style chunked feed; sentences straddle feed() boundaries
decode_aivdm_log.py Multi-fragment AIS reassembly: Type 1 + Type 5
parse_stdin.py Stdin pipe reader for live NMEA/AIS capture
live_ais_dashboard.py Live ship tracker: envelope + AIS in one script, periodic refresh

Type stubs

The py.typed marker is included in the package. Every public class, function, and constant has a .pyi stub. Downstream projects that run mypy --strict get full type-check coverage without extra configuration.

Rust crates

The Python bindings wrap four Rust crates: marlin-nmea-envelope, marlin-nmea-0183, marlin-ais, marlin-klv.

MSRV / Python version

Rust 1.82. Python 3.9+.

License

Dual-licensed under MIT OR Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

marlin_py-0.1.2.tar.gz (191.7 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

marlin_py-0.1.2-cp39-abi3-win_amd64.whl (377.8 kB view details)

Uploaded CPython 3.9+Windows x86-64

marlin_py-0.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (455.0 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

marlin_py-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (442.6 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

marlin_py-0.1.2-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (835.4 kB view details)

Uploaded CPython 3.9+macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file marlin_py-0.1.2.tar.gz.

File metadata

  • Download URL: marlin_py-0.1.2.tar.gz
  • Upload date:
  • Size: 191.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for marlin_py-0.1.2.tar.gz
Algorithm Hash digest
SHA256 406c9029ab0fce0474e0cdc29d9bff5d672cde97b3b46530ba23d10992e0e66c
MD5 d54b186b9a6027e4fdfdc8084469fab4
BLAKE2b-256 0debef2f0094bccae7da1a6cdc4cef99e287f9cf6146ea91356cbfeca6d86097

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.2.tar.gz:

Publisher: python-bindings.yml on jkr78/marlin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file marlin_py-0.1.2-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: marlin_py-0.1.2-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 377.8 kB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for marlin_py-0.1.2-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 f7e9d06e7fbb0cbcf80cf4d44c2e18896e4e9f48da01fcd6427335b8c9bdd336
MD5 9bcd5965053dc12806bf5ac1fbb98b88
BLAKE2b-256 a9c1500a9357ce64567141951fc449a58880989aee7cf935e67239b1c4089975

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.2-cp39-abi3-win_amd64.whl:

Publisher: python-bindings.yml on jkr78/marlin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file marlin_py-0.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for marlin_py-0.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 5d1c5a7f52478e35a2cef5646cebc7049a6cfb79c930125c69fe36b3b386f09e
MD5 50ebcf275849863157e0d5a810e55e5a
BLAKE2b-256 e6e161296d076a1ed0fb6ff3434b4fd7f880604cddd8f9281da18d32dd5558cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: python-bindings.yml on jkr78/marlin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file marlin_py-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for marlin_py-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 15e189cf1370c71c8ab72e6a33fe66f241a248ba37a34e09bc01d7c5736c66f7
MD5 545d7f8c60c651a00de76e17b83b7328
BLAKE2b-256 99ef9a4f1f37fef8c1b9f69fdefbdb289894e7c7f2980908185ca690c9e65fe8

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: python-bindings.yml on jkr78/marlin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file marlin_py-0.1.2-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for marlin_py-0.1.2-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 0dce22394fa94606ee669edd2568ef752c5983f05dd72c6f717ce54bc7a9cdec
MD5 d538f10c9d5677490af0e6cda5db4a31
BLAKE2b-256 ea589b6203c770fbbce3839fecf2b3db7c3c6a14e6c3eb5aac615d09b382f123

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.2-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: python-bindings.yml on jkr78/marlin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page