Releases: junzis/pyModeS
Release list
v3.6.0
Added
decode(),Message.decode(), andPipeDecoderacceptinclude_meteo=Trueto opt into heuristic Comm-B BDS 4,4 routine meteorological reports and BDS 4,5 meteorological hazard reports. The default remainsFalsebecause these payload patterns can overlap other Comm-B registers.- The
modes decode,modes live, andscripts/stream_filtered.pyinterfaces expose the same behavior through--include-meteo.
Full changelog: v3.5.0...v3.6.0
v3.5.0
Added
PipeDecodernow 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 streamingdecode()call when no fresh global even/odd pair is
available, instead of relying on retroactive mutation of an earlier result.local_ref_windowconfigures 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_validis nowNonewhen an address-parity message cannot be checked
independently. It remainsTrueorFalsefor frames whose parity can be
validated without assuming the address derived from that same parity.PipeDecoderuses 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 fromsurface_refcan 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
Highlights
- Restores high-volume
PipeDecoderthroughput 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
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 fpmguard 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_oddare 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 theirlatitude/longituderetroactively
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
v3.1.0
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
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 aDecodeddict 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/21icao_verifiedpromotion 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 inPipeDecoder. - New
modesCLI with two subcommands:modes decode— one-shot, inline-batch, or file-based hex→JSONmodes 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 (requirespyModeS[tui]extra)
- Pure Python. Dropped the Cython
c_commonextension and the numpy hard dependency. Internal representation is Pythonint+ bit-shift primitives; no wheel build per Python version. - Airport database for surface CPR:
surface_ref="EHAM"orsurface_ref=(lat, lon). - Optional
full_dict=Truepopulates 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 raisesV2APIRemovedError(ImportErrorsubclass) with a migration hint.pyModeS.commonhelpers are restored aspyModeS.util(hex2bin,bin2int,hex2int,crc,df,icao,typecode,altcode,idcode,cprNL). - Python 3.11+ minimum (was 3.9+).
- numpy is no longer a dependency.
modesliveCLI entry point replaced bymodes live/modes decode. The oldmodeslivescript 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"orsurface_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
v2.21.1
What's Changed
- fix type hints for temp44() by @Job-Heersink in #188
New Contributors
- @Job-Heersink made their first contribution in #188
Full Changelog: v2.21...v2.21.1