Skip to content

Releases: junzis/pyModeS

v3.6.0

Choose a tag to compare

@github-actions github-actions released this 16 Jul 12:26

Added

  • decode(), Message.decode(), and PipeDecoder accept include_meteo=True to opt into heuristic Comm-B BDS 4,4 routine meteorological reports and BDS 4,5 meteorological hazard reports. The default remains False because these payload patterns can overlap other Comm-B registers.
  • The modes decode, modes live, and scripts/stream_filtered.py interfaces expose the same behavior through --include-meteo.

Full changelog: v3.5.0...v3.6.0

v3.5.0

Choose a tag to compare

@github-actions github-actions released this 15 Jul 19:46

Added

  • PipeDecoder now uses the last validated airborne position as a local CPR
    reference for subsequent BDS 0,5 frames. This makes a position available on
    the current streaming decode() call when no fresh global even/odd pair is
    available, instead of relying on retroactive mutation of an earlier result.
  • local_ref_window configures the maximum reference age (30 seconds by
    default); set it to zero to disable local airborne decoding.
    PipeDecoder.stats["local_positions"] reports positions produced by this
    path.
  • Position bootstrap now locks as soon as three candidates are pairwise
    motion-consistent instead of always waiting for all five candidate slots.
    The five-candidate limit still allows two unrelated outliers to be ignored.
  • The mixed-traffic benchmark now reports immediate streaming position yield
    and frame-by-frame coordinate agreement for v2.21.1, released v3.4.0, and
    v3.5.0 in addition to throughput.

Changed

  • crc_valid is now None when an address-parity message cannot be checked
    independently. It remains True or False for frames whose parity can be
    validated without assuming the address derived from that same parity.
  • PipeDecoder uses each message's source timestamp for state validity while
    retaining a monotonic high-water mark for eviction. Reordered capture data
    therefore cannot revive expired observations or prematurely evict newer
    state.
  • Plausibility and position validation are isolated in a dedicated internal
    module, keeping the streaming pipeline and its decision comments easier to
    follow without changing the public API.
  • Release publishing now runs only after the full test matrix succeeds, checks
    that the Git tag matches the package version, and smoke-tests the exact wheel
    artifact that will be uploaded.

Fixed

  • Surface references remain optional and are used only for BDS 0,6 surface
    positions. Positions derived from surface_ref can never seed local
    airborne CPR decoding.
  • Rejected global position candidates cannot become local CPR references; a
    separate last-accepted airborne reference is maintained and TTL-evicted.
  • Bootstrap promotion, including flush(), initializes the latest accepted
    airborne reference while keeping surface-derived positions separate.
  • Globally decoded surface CPR positions now select the correct longitude zone
    across the antimeridian and other CPR zone boundaries.
  • Beast stream input resynchronizes after malformed or truncated frames instead
    of losing subsequent valid messages.
  • Beast receiver timestamps are back-projected from the current wall clock with
    rollover-safe receiver-tick deltas, avoiding dates near the Unix epoch and
    preserving correct timing across the 12 MHz counter rollover.
  • BDS inference compares headings using circular angular distance, so values
    around 0/360 degrees are scored correctly.
  • Trusted ICAO observations expire according to the configured state TTL and
    cannot be refreshed by older, reordered messages.
  • Integer messages outside the valid 56-bit and 112-bit Mode S frame ranges are
    rejected instead of being silently truncated or misclassified.
  • Network endpoint and surface-reference CLI arguments are validated before a
    stream starts, and network sources are closed deterministically on normal
    exit, errors, and interruption.
  • The live terminal UI synchronizes access to shared aircraft state, preventing
    concurrent decoder and renderer updates from racing.

v3.4.0

Choose a tag to compare

@github-actions github-actions released this 13 Jul 20:19

Highlights

  • Restores high-volume PipeDecoder throughput by replacing a full all-aircraft cache scan on every message with amortized global cleanup.
  • Preserves exact TTL behavior with a bounded per-ICAO expiry check before cached state can influence decoding, including ambiguous BDS 5,0/6,0 inference.
  • Avoids empty state entries and skips state-field extraction for downlink formats that cannot contribute to inference.
  • Adds scripts/stream_filtered.py, a configurable Beast live/replay example that filters inexpensive DF/typecode headers before full decoding.
  • Adds reproducible, version-isolated mixed-traffic and high-cardinality benchmarks with checked-in reports and the public Beast capture.

Performance

Measured on the committed 176,612-frame mixed Beast capture:

Decoder path Throughput
pyModeS 2.21.1 selective helpers 52,998 msg/s
v3.3.0 PipeDecoder 3,897 msg/s
v3.4.0 PipeDecoder 49,243 msg/s
v3.4.0 + header prefilter 85,963 msg/s

The v3.3.0 and v3.4.0 paths emitted the same 28,404 events with an identical output digest. On the 2,000-aircraft synthetic workload, v3.4.0 reached 38,838 msg/s versus v2.21.1 at 39,604 msg/s, also with identical normalized output.

Validation

  • 1,185 tests passed
  • Ruff formatting and lint passed
  • Mypy strict checks passed
  • Wheel and sdist build passed
  • Clean-wheel installation smoke test passed

Full Changelog: v3.3.0...v3.4.0

v3.3.0

Choose a tag to compare

@github-actions github-actions released this 19 Apr 18:54

Highlights

PipeDecoder: reject CRC-lucky phantoms

CRC-24 alone isn't enough — ~1-in-16M random 1090 MHz FRUIT survives
CRC with a plausible ICAO but garbage payload. This release adds four
plausibility cross-checks layered on top of CRC, each reusing a
per-ICAO anchor updated only from CRC-valid frames that pass their
own check:

  • DF17/18 BDS 0,5 (airborne position) — altitude vs ADS-B anchor.
  • DF17/18 TC=19 (airborne velocity) — groundspeed/track vs anchor,
    plus a |VR| > 10 000 fpm guard that fires even without an anchor.
  • DF20/21 BDS 5,0 (track & turn) — groundspeed/true-track vs the
    ADS-B velocity anchor.
  • DF20/21 BDS 6,0 (heading & speed) — magnetic_heading vs ADS-B
    track (wider tolerance to absorb magnetic variation + wind-correction
    angle).

Rejected frames keep their header fields so callers can see what
happened, but the velocity / position fields are scrubbed and the
anchor isn't updated. A new velocity_mismatch stats counter joins
the existing altitude_mismatch.

PipeDecoder: preserve paired frames

Two structural fixes to stop legitimate positions from being dropped:

  • _pending_even / _pending_odd are now per-parity deques so two
    same-parity frames arriving before an opposite don't silently
    discard the earlier one — orphan halves pair against the arriving
    frame and each gets its own resolved position.
  • Bootstrap entries hold a list of result dicts so both halves
    of a resolved CPR pair get their latitude/longitude retroactively
    filled when the bootstrap cluster locks.

Docs

  • New Interesting messages
    reference page — a growing catalogue of real-world messages
    exhibiting edge cases (CRC-lucky phantoms, FRUIT, BDS ambiguity,
    malformed payloads) to use when debugging or writing new checks.

Dev

  • Repo now ships a pre-commit config running ruff-format + ruff-check.
    After cloning: uv sync && uv run pre-commit install.

Full Changelog: v3.2.0...v3.3.0

v3.2.0

Choose a tag to compare

@github-actions github-actions released this 17 Apr 12:03

Full Changelog: v3.1.0...v3.2.0

v3.1.0

Choose a tag to compare

@github-actions github-actions released this 16 Apr 16:50

What's Changed

  • build(deps-dev): bump pytest from 9.0.2 to 9.0.3 by @dependabot[bot] in #198

Full Changelog: v3.0.0...v3.1.0

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 13 Apr 08:19

First release of the v3 ground-up rewrite. Not backwards-compatible with pyModeS 2.x. See the migration guide before upgrading.

pip install "pyModeS>=3"

Python 3.11+ required.

Highlights

  • One unified decode() function. Returns a Decoded dict with every decodable field populated in a single call. Handles single hex strings or batched lists with per-message timestamps; errors become error-dicts instead of exceptions.
  • Stateful streaming via PipeDecoder. Per-ICAO state, automatic even/odd CPR pair matching, TTL eviction, and DF20/21 icao_verified promotion from a trusted-address set populated off clean DF17/18 frames.
  • Reference-assisted Comm-B disambiguation. BDS 5,0 vs 6,0 ambiguity is resolved by scoring candidates against cached groundspeed / track / heading / mach when passed via known=, or automatically in PipeDecoder.
  • New modes CLI with two subcommands:
    • modes decode — one-shot, inline-batch, or file-based hex→JSON
    • modes live --network HOST:PORT — streaming TCP → JSON lines, with sub-microsecond timestamps derived from the Mode-S Beast MLAT counter (tick rate auto-calibrated, so dump1090 and radarcape/AirSquitter both work without config)
    • modes live --tui — interactive textual-based aircraft table with keyboard navigation, live incremental search, sort cycling, and diff-update cell redraws (requires pyModeS[tui] extra)
  • Pure Python. Dropped the Cython c_common extension and the numpy hard dependency. Internal representation is Python int + bit-shift primitives; no wheel build per Python version.
  • Airport database for surface CPR: surface_ref="EHAM" or surface_ref=(lat, lon).
  • Optional full_dict=True populates every key in the 123-field canonical schema for pandas / parquet workflows.

Breaking changes

  • Legacy function-per-field API removed (pms.adsb.callsign, pms.commb.bds, pms.common.hex2bin, etc.). The old submodule paths are intercepted by a meta-path finder that raises V2APIRemovedError (ImportError subclass) with a migration hint. pyModeS.common helpers are restored as pyModeS.util (hex2bin, bin2int, hex2int, crc, df, icao, typecode, altcode, idcode, cprNL).
  • Python 3.11+ minimum (was 3.9+).
  • numpy is no longer a dependency.
  • modeslive CLI entry point replaced by modes live / modes decode. The old modeslive script prints a migration hint and exits code 2 instead of vanishing.
  • CPR resolution API: reference=(lat, lon) for airborne (180 NM tolerance), surface_ref="EHAM" or surface_ref=(lat, lon) for surface (45 NM tolerance).

Staying on v2

pip install "pyModeS<3"

v2 and v3 share the same PyPI distribution slot; pinning <3 keeps you on the 2.x line indefinitely.

Full changelog

See CHANGELOG.md for the complete list of added / fixed / changed / removed items.

Full diff: v2.22.0...v3.0.0

v2.22.0

Choose a tag to compare

@github-actions github-actions released this 09 Apr 09:55

What's Changed

New Contributors

Full Changelog: v2.21.1...v2.22.0

v2.21.1

Choose a tag to compare

@junzis junzis released this 09 Jul 15:52
0a1c403

What's Changed

New Contributors

Full Changelog: v2.21...v2.21.1

v2.21

Choose a tag to compare

@junzis junzis released this 03 Jul 15:52

What's Changed

Full Changelog: v2.20...v2.21