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.4.tar.gz (205.5 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.4-cp39-abi3-win_amd64.whl (402.8 kB view details)

Uploaded CPython 3.9+Windows x86-64

marlin_py-0.1.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (477.0 kB view details)

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

marlin_py-0.1.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (464.7 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

marlin_py-0.1.4-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (877.2 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.4.tar.gz.

File metadata

  • Download URL: marlin_py-0.1.4.tar.gz
  • Upload date:
  • Size: 205.5 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.4.tar.gz
Algorithm Hash digest
SHA256 4c1a96b48b9407e01332870306db73909382fdf09ba1995ac6fd0623868eeb4b
MD5 2a2ae39fe50ec4c043107edfd671f594
BLAKE2b-256 15c7f2c4bbac59a8ab6d3aa19870c7faaf4364e2a319f4366356abf005d1dd9f

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.4.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.4-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: marlin_py-0.1.4-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 402.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.4-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 b85fa735cc16e132548ed931109b6b21125f443e325f10daeb42ddffc0b5c0fe
MD5 31ea9944553a36689a2bd192d6a59973
BLAKE2b-256 83317ede220c29bbf990883a9e14c102d91282b87ff59004f1d9c463281052a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.4-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.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for marlin_py-0.1.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b5b50b555ab69fab7619e2925335838f1ad442d7d864d70ec2272e4378cf9e77
MD5 1c6fbe6734eb4f39b498d071a58c0d3e
BLAKE2b-256 7b99dda2b25ccb533ae1071b3424549a2011f20f54c5aadfafc0a09cc0db8ce7

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.4-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.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for marlin_py-0.1.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 0232b640fc26834bad6a62de1777fedd9866ccdacbebabda93f9af5b074dc98c
MD5 ad7a2dd2eeb78c642344829e4eccf632
BLAKE2b-256 f327859d70e2f8da23b08735cdd221aacbe1e9e7a67078ffdfd3df1503096d35

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.4-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.4-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.4-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 3cf838b60cd7a74d67cba4d092817b95c7cdd747e1fb7c8d6a46d0d028b57e18
MD5 5aeb20800bcf63aff84e5023a68f8364
BLAKE2b-256 cdc45ae3a7da8f7f9bde47ef4c40af2089d27ef29fbfe1b0cd822d3abb001549

See more details on using hashes here.

Provenance

The following attestation bundles were made for marlin_py-0.1.4-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