Skip to main content

pyModeS

Fast decoder for Mode-S and ADS-B messages in Python. Ground-up v3 rewrite of pyModeS with a single unified decode() function.

license

Install

pip install "pyModeS>=3"

Python 3.11+ required.

Usage examples

Single-message decode

The decode() function returns a Decoded dict with every decodable field populated in one pass.

from pyModeS import decode

result = decode("8D406B902015A678D4D220AA4BDA")
print(result)
# {
#     'df': 17,
#     'icao': '406B90',
#     'crc_valid': True,
#     'typecode': 4,
#     'bds': '0,8',
#     'callsign': 'EZY85MH',
#     'category': 0,
#     'wake_vortex': 'No category information',
# }

Batch decode (mixed message types)

Pass a list of hex strings and parallel timestamps. Any mix of downlink formats, typecodes, and Comm-B registers is fine — the dispatcher routes each message to the right decoder and uses the timestamps to resolve CPR pairs and disambiguate ambiguous Comm-B registers.

from pyModeS import decode

results = decode(
    [
        "8D406B902015A678D4D220AA4BDA",  # DF17 BDS 0,8 identification
        "8D485020994409940838175B284F",  # DF17 BDS 0,9 airborne velocity
        "8D40058B58C901375147EFD09357",  # DF17 BDS 0,5 airborne pos (even)
        "8D40058B58C904A87F402D3B8C59",  # DF17 BDS 0,5 airborne pos (odd)
        "A000178D10010080F50000D5893C",  # DF20 BDS 1,0 data link capability
        "A8000D9FA55A032DBFFC000D8123",  # DF21 BDS 6,0 heading & speed
    ],
    timestamps=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
)
assert results[0]["callsign"] == "EZY85MH"
assert results[1]["groundspeed"] == 159
assert results[3]["latitude"] is not None  # CPR pair resolved

Surface position with airport reference

Surface CPR needs a reference within ~45 NM. Pass an ICAO airport code (looked up in the shipped airport database) or an explicit (lat, lon) tuple.

from pyModeS import decode

# Real DF18 surface movement on LFBO (Toulouse-Blagnac).
r = decode("903a23ff426a4e65f7487a775d17", surface_ref="LFBO")
print(r["latitude"], r["longitude"])  # 43.6264..., 1.3747...

Streaming decoder

PipeDecoder is stateful — it holds per-ICAO state across calls, matches CPR pairs automatically, evicts stale aircraft after a TTL, and flags DF20/21 messages as icao_verified when their CRC-derived ICAO was seen recently in clean DF17/18 plaintext.

from pyModeS import PipeDecoder

pipe = PipeDecoder(surface_ref="EHAM")
for msg, timestamp in stream:
    decoded = pipe.decode(msg, timestamp=timestamp)
    if decoded.get("latitude") is not None:
        print(decoded["icao"], decoded["latitude"], decoded["longitude"])

See docs/quickstart.md for the full tour (full-dict mode, error handling, attribute access).

Low-level helpers

For ad-hoc message inspection that doesn't need a full decode, pyModeS.util exposes thin wrappers around the bit/hex/CRC primitives: hex2bin, bin2int, hex2int, bin2hex, crc, df, icao, typecode, altcode, idcode, cprNL.

from pyModeS.util import hex2bin, crc, icao, typecode

msg = "8D406B902015A678D4D220AA4BDA"
hex2bin(msg)[:16]   # '1000110101000000'
crc(msg)            # 0 — valid DF17
icao(msg)           # '406B90'
typecode(msg)       # 4 — ADS-B identification

CLI

pyModeS ships with a modes command-line tool for ad-hoc decoding and live streaming.

modes decode — one-shot and file mode

# Decode one hex message (pretty-printed JSON)
modes decode 8D406B902015A678D4D220AA4BDA

# Decode several messages inline — comma-separated, emits JSON lines
modes decode 8D40058B58C901375147EFD09357,8D40058B58C904A87F402D3B8C59,8D406B902015A678D4D220AA4BDA

# With airborne CPR reference (single message only)
modes decode 8D40058B58C901375147EFD09357 --reference 49.0 6.0

# Compact JSON piped to jq
modes decode 8D406B902015A678D4D220AA4BDA --compact | jq .

# Decode a file of hex messages (one per line OR timestamp,hex CSV)
modes decode --file captures/flight.log

# Stdin + surface CPR
cat taxi.log | modes decode --file - --surface-ref LFBO

modes live — streaming TCP source

# Stream decoded JSON lines from a dump1090-style beast feed
modes live --network localhost:30005

# Tee output to a file
modes live --network host:30005 --dump-to flight.jsonl

# Stream from the TU Delft public feed (live aircraft over Europe)
modes live --network airsquitter.lr.tudelft.nl:10006

# Interactive live aircraft table (requires pyModeS[tui] extra)
pip install "pyModeS[tui]"
modes live --network host:30005 --tui

Mode-S Beast binary format is supported (dump1090 port 30005 and equivalents). See docs/quickstart.md for the full command reference.

Features

  • Unified decode() returns every decodable field in one dict
  • Batch mode preserves list length (errors become error-dicts, not exceptions)
  • PipeDecoder for streams: per-ICAO state, global and local CPR position decoding, TTL eviction, DF20/21 ICAO verification via trusted-set promotion
  • full_dict=True populates every key in the canonical 123-field schema for pandas / parquet workflows
  • known= aircraft state disambiguates Comm-B BDS 5,0/6,0 ambiguity
  • include_meteo=True opts into heuristic Comm-B BDS 4,4/4,5 inference
  • Airport ICAO database for surface CPR resolution (surface_ref="EHAM")
  • Type-checked under mypy strict across all source files
  • Golden-file oracle regression test against pyModeS 2.21.1

Supported messages

  • DF4 / DF20: altitude code (surveillance altitude reply)
  • DF5 / DF21: identity code (squawk)
  • DF11: all-call reply (partial — II/SI decoding deferred)
  • DF17 / DF18 ADS-B:
    • TC 1-4 (BDS 0,8): identification + category
    • TC 5-8 (BDS 0,6): surface position
    • TC 9-18 (BDS 0,5): airborne position (barometric altitude)
    • TC 19 (BDS 0,9): airborne velocity (all 4 subtypes)
    • TC 20-22 (BDS 0,5): airborne position (GNSS altitude)
    • TC 28 (BDS 6,1): aircraft status
    • TC 29 (BDS 6,2): target state and status
    • TC 31 (BDS 6,5): operational status
  • DF20 / DF21 Comm-B:
    • BDS 1,0: data link capability
    • BDS 1,7: common-usage GICB capability
    • BDS 2,0: aircraft identification
    • BDS 3,0: ACAS active resolution advisory
    • BDS 4,0: selected vertical intention
    • BDS 4,4: meteorological routine air report
    • BDS 4,5: meteorological hazard report
    • BDS 5,0: track and turn report
    • BDS 6,0: heading and speed report

BDS 4,4 and 4,5 are heuristic and disabled by default. Enable them with decode(..., include_meteo=True) or PipeDecoder(include_meteo=True) when these meteorological registers are expected on the input feed.

Migrating from pyModeS 2.x

pyModeS 3 is not backwards-compatible with pyModeS 2.x. The function-per-field API (pms.adsb.callsign(msg), ...) is replaced by a single decode() returning a dict. See the migration guide for the full equivalence table.

If you aren't ready to migrate:

pip install "pyModeS<3"

v3 replaces v2 on the same PyPI slot and import name, so pip install -U pyModeS upgrades existing v2 users to v3 (and triggers the v2-API removal shims — see the migration guide). Pinning <3 keeps you on the 2.x line.

Documentation

The full docs live under docs/ and are published via MkDocs + Material. To rebuild locally:

# One-shot build (strict mode fails on warnings). Output → site/
uv run --with mkdocs-material --with mkdocs-include-markdown-plugin --with "mkdocstrings[python]" mkdocs build --clean --strict

# Live-reload dev server at http://127.0.0.1:8000
uv run --with mkdocs-material --with mkdocs-include-markdown-plugin --with "mkdocstrings[python]" mkdocs serve

Links

Attribution

pyModeS is a project created by Junzi Sun, who works at TU Delft, Aerospace Engineering Faculty. It is supported by many contributors from different institutions.

If you use pyModeS in academic work, please cite:

@article{sun2019pyModeS,
    author={J. {Sun} and H. {V\^u} and J. {Ellerbroek} and J. M. {Hoekstra}},
    journal={IEEE Transactions on Intelligent Transportation Systems},
    title={pyModeS: Decoding Mode-S Surveillance Data for Open Air Transportation Research},
    year={2019},
    doi={10.1109/TITS.2019.2914770},
    ISSN={1524-9050},
}

Release files for pyModeS 3.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyModeS 3.6.0
File Size Uploaded
pymodes-3.6.0.tar.gz 176.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyModeS 3.6.0
File Interpreter ABI Platform
pymodes-3.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 368.2 kB

Release files / pymodes-3.6.0.tar.gz

Download URL pymodes-3.6.0.tar.gz
Size 176.2 kB
Tags Source
SHA-256 checksum
How to use checksums
416c8ac4a77300c07ca761947b6f68efb135e25512e7a996d876561dfb3bd480
BLAKE2b-256 checksum
How to use checksums
eba105b79de7ea850455d203f4b07c59396baea170e137af31fe96a3cea6eaf1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 16, 2026.

Transparency log

Release files / pymodes-3.6.0-py3-none-any.whl

Download URL pymodes-3.6.0-py3-none-any.whl
Size 192.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c508f4731c7aa39b7ab9eb34d5d56fe853f8d425be85638f528bac6e02ec23f5
BLAKE2b-256 checksum
How to use checksums
12db78361a889148e04644699cfaa619049f614029dde10eb950889aafebe88c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.6.0 This release

2 release files

3.5.1

2 release files

3.5.0

2 release files

3.4.0

2 release files

3.3.0

2 release files

3.2.0

2 release files

3.1.0

2 release files

3.0.0

2 release files

2.21

25 release files

2.20

25 release files

2.19

2 release files

2.18

2 release files

2.17

13 release files

2.16

13 release files

2.15

13 release files

2.14

5 release files

2.11

2 release files

2.9

1 release file

2.8

1 release file

2.5

2 release files

2.4

2 release files

2.2

2 release files

2.1

2 release files

2.0

2 release files

1.2.2

2 release files

1.2.0

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.5

2 release files

1.0.4

1 release file

1.0.3

1 release file

1.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page