pyModeS 3.4.0__tar.gz → 3.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pymodes-3.4.0 → pymodes-3.5.0}/CHANGELOG.md +65 -2
- {pymodes-3.4.0 → pymodes-3.5.0}/PKG-INFO +5 -26
- {pymodes-3.4.0 → pymodes-3.5.0}/README.md +4 -25
- {pymodes-3.4.0 → pymodes-3.5.0}/pyproject.toml +1 -1
- {pymodes-3.4.0 → pymodes-3.5.0}/scripts/README.md +9 -4
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/__init__.py +3 -3
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_pipe.py +360 -465
- pymodes-3.5.0/src/pyModeS/_pipe_validation.py +234 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/cli/_args.py +11 -0
- pymodes-3.5.0/src/pyModeS/cli/_parse.py +51 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/cli/_source.py +142 -72
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/cli/_tui.py +45 -42
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/cli/decode.py +15 -26
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/cli/live.py +18 -39
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/core.py +4 -2
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/_infer.py +6 -1
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/message.py +18 -6
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/position/_cpr.py +16 -6
- {pymodes-3.4.0 → pymodes-3.5.0}/.gitignore +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/LICENSE +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/scripts/data/README.md +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_aero.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_altcode.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_bits.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_callsign.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_idcode.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_schema.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_uncertainty.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/_v2_removed.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/cli/__init__.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/cli/__main__.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/cli/_sink.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/data/__init__.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/data/airports.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/__init__.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/_base.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/acas.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/adsb.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/allcall.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/__init__.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/_helpers.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds05.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds06.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds08.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds09.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds10.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds17.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds20.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds30.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds40.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds44.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds45.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds50.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds60.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds61.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds62.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds65.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/commb.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/decoder/surv.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/errors.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/position/__init__.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/position/_airports.py +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/py.typed +0 -0
- {pymodes-3.4.0 → pymodes-3.5.0}/src/pyModeS/util.py +0 -0
|
@@ -5,6 +5,69 @@ All notable changes to pyModeS are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [3.5.0] — 2026-07-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `PipeDecoder` now uses the last validated airborne position as a local CPR
|
|
13
|
+
reference for subsequent BDS 0,5 frames. This makes a position available on
|
|
14
|
+
the current streaming `decode()` call when no fresh global even/odd pair is
|
|
15
|
+
available, instead of relying on retroactive mutation of an earlier result.
|
|
16
|
+
- `local_ref_window` configures the maximum reference age (30 seconds by
|
|
17
|
+
default); set it to zero to disable local airborne decoding.
|
|
18
|
+
`PipeDecoder.stats["local_positions"]` reports positions produced by this
|
|
19
|
+
path.
|
|
20
|
+
- Position bootstrap now locks as soon as three candidates are pairwise
|
|
21
|
+
motion-consistent instead of always waiting for all five candidate slots.
|
|
22
|
+
The five-candidate limit still allows two unrelated outliers to be ignored.
|
|
23
|
+
- The mixed-traffic benchmark now reports immediate streaming position yield
|
|
24
|
+
and frame-by-frame coordinate agreement for v2.21.1, released v3.4.0, and
|
|
25
|
+
v3.5.0 in addition to throughput.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `crc_valid` is now `None` when an address-parity message cannot be checked
|
|
30
|
+
independently. It remains `True` or `False` for frames whose parity can be
|
|
31
|
+
validated without assuming the address derived from that same parity.
|
|
32
|
+
- `PipeDecoder` uses each message's source timestamp for state validity while
|
|
33
|
+
retaining a monotonic high-water mark for eviction. Reordered capture data
|
|
34
|
+
therefore cannot revive expired observations or prematurely evict newer
|
|
35
|
+
state.
|
|
36
|
+
- Plausibility and position validation are isolated in a dedicated internal
|
|
37
|
+
module, keeping the streaming pipeline and its decision comments easier to
|
|
38
|
+
follow without changing the public API.
|
|
39
|
+
- Release publishing now runs only after the full test matrix succeeds, checks
|
|
40
|
+
that the Git tag matches the package version, and smoke-tests the exact wheel
|
|
41
|
+
artifact that will be uploaded.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- Surface references remain optional and are used only for BDS 0,6 surface
|
|
46
|
+
positions. Positions derived from `surface_ref` can never seed local
|
|
47
|
+
airborne CPR decoding.
|
|
48
|
+
- Rejected global position candidates cannot become local CPR references; a
|
|
49
|
+
separate last-accepted airborne reference is maintained and TTL-evicted.
|
|
50
|
+
- Bootstrap promotion, including `flush()`, initializes the latest accepted
|
|
51
|
+
airborne reference while keeping surface-derived positions separate.
|
|
52
|
+
- Globally decoded surface CPR positions now select the correct longitude zone
|
|
53
|
+
across the antimeridian and other CPR zone boundaries.
|
|
54
|
+
- Beast stream input resynchronizes after malformed or truncated frames instead
|
|
55
|
+
of losing subsequent valid messages.
|
|
56
|
+
- Beast receiver timestamps are back-projected from the current wall clock with
|
|
57
|
+
rollover-safe receiver-tick deltas, avoiding dates near the Unix epoch and
|
|
58
|
+
preserving correct timing across the 12 MHz counter rollover.
|
|
59
|
+
- BDS inference compares headings using circular angular distance, so values
|
|
60
|
+
around 0/360 degrees are scored correctly.
|
|
61
|
+
- Trusted ICAO observations expire according to the configured state TTL and
|
|
62
|
+
cannot be refreshed by older, reordered messages.
|
|
63
|
+
- Integer messages outside the valid 56-bit and 112-bit Mode S frame ranges are
|
|
64
|
+
rejected instead of being silently truncated or misclassified.
|
|
65
|
+
- Network endpoint and surface-reference CLI arguments are validated before a
|
|
66
|
+
stream starts, and network sources are closed deterministically on normal
|
|
67
|
+
exit, errors, and interruption.
|
|
68
|
+
- The live terminal UI synchronizes access to shared aircraft state, preventing
|
|
69
|
+
concurrent decoder and renderer updates from racing.
|
|
70
|
+
|
|
8
71
|
## [3.4.0] — 2026-07-13
|
|
9
72
|
|
|
10
73
|
### Fixed
|
|
@@ -38,8 +101,8 @@ Comm-B reply to the wrong aircraft.
|
|
|
38
101
|
### Added
|
|
39
102
|
|
|
40
103
|
- `PipeDecoder` bootstrap cluster analysis for the first position per
|
|
41
|
-
ICAO: lat/lon emission is held until
|
|
42
|
-
|
|
104
|
+
ICAO: lat/lon emission is held until five candidate positions agree
|
|
105
|
+
under a motion-consistency check. Prevents an
|
|
43
106
|
initial phantom frame from anchoring the rolling position history.
|
|
44
107
|
On each decode the held result dicts are retro-filled once the
|
|
45
108
|
cluster locks. Scattered buffers reset and accumulate a fresh K.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyModeS
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: Python Mode-S and ADS-B Decoder
|
|
5
5
|
Project-URL: homepage, https://mode-s.org
|
|
6
6
|
Project-URL: repository, https://github.com/junzis/pyModeS
|
|
@@ -109,7 +109,7 @@ print(r["latitude"], r["longitude"]) # 43.6264..., 1.3747...
|
|
|
109
109
|
`PipeDecoder` is stateful — it holds per-ICAO state across calls,
|
|
110
110
|
matches CPR pairs automatically, evicts stale aircraft after a
|
|
111
111
|
TTL, and flags DF20/21 messages as `icao_verified` when their
|
|
112
|
-
CRC-derived ICAO was
|
|
112
|
+
CRC-derived ICAO was seen recently in clean DF17/18 plaintext.
|
|
113
113
|
|
|
114
114
|
```python
|
|
115
115
|
from pyModeS import PipeDecoder
|
|
@@ -117,7 +117,7 @@ from pyModeS import PipeDecoder
|
|
|
117
117
|
pipe = PipeDecoder(surface_ref="EHAM")
|
|
118
118
|
for msg, timestamp in stream:
|
|
119
119
|
decoded = pipe.decode(msg, timestamp=timestamp)
|
|
120
|
-
if "latitude"
|
|
120
|
+
if decoded.get("latitude") is not None:
|
|
121
121
|
print(decoded["icao"], decoded["latitude"], decoded["longitude"])
|
|
122
122
|
```
|
|
123
123
|
|
|
@@ -194,8 +194,8 @@ the full command reference.
|
|
|
194
194
|
- Unified `decode()` returns every decodable field in one dict
|
|
195
195
|
- Batch mode preserves list length (errors become error-dicts, not
|
|
196
196
|
exceptions)
|
|
197
|
-
- `PipeDecoder` for streams: per-ICAO state, CPR
|
|
198
|
-
TTL eviction, DF20/21 ICAO verification via trusted-set promotion
|
|
197
|
+
- `PipeDecoder` for streams: per-ICAO state, global and local CPR position
|
|
198
|
+
decoding, TTL eviction, DF20/21 ICAO verification via trusted-set promotion
|
|
199
199
|
- `full_dict=True` populates every key in the canonical 123-field
|
|
200
200
|
schema for pandas / parquet workflows
|
|
201
201
|
- `known=` aircraft state disambiguates Comm-B BDS 5,0/6,0 ambiguity
|
|
@@ -203,27 +203,6 @@ the full command reference.
|
|
|
203
203
|
- Type-checked under mypy strict across all source files
|
|
204
204
|
- Golden-file oracle regression test against `pyModeS 2.21.1`
|
|
205
205
|
|
|
206
|
-
## Performance
|
|
207
|
-
|
|
208
|
-
The committed benchmarks run each released/working-tree version in an
|
|
209
|
-
isolated environment and reject output mismatches before reporting timings.
|
|
210
|
-
On the captured 176,612-frame mixed Beast feed, the header-prefiltered v3 path
|
|
211
|
-
processes 85,963 incoming messages/s:
|
|
212
|
-
|
|
213
|
-
| Decoder path | Throughput |
|
|
214
|
-
|---|---:|
|
|
215
|
-
| pyModeS 2.21.1 selective helpers | 52,998 msg/s |
|
|
216
|
-
| released v3.3.0 `PipeDecoder` | 3,897 msg/s |
|
|
217
|
-
| v3.4.0 `PipeDecoder` | 49,243 msg/s |
|
|
218
|
-
| v3.4.0 + header prefilter | **85,963 msg/s** |
|
|
219
|
-
|
|
220
|
-
On the 2,000-aircraft synthetic PipeDecoder workload, v3.4.0 processes
|
|
221
|
-
38,838 msg/s versus v2's 39,604 msg/s, with identical normalized output.
|
|
222
|
-
|
|
223
|
-
See [`scripts/README.md`](./scripts/README.md) for the reproducible commands
|
|
224
|
-
and [`scripts/benchmark_results/`](./scripts/benchmark_results/) for the full
|
|
225
|
-
methodology, timings, and output digests.
|
|
226
|
-
|
|
227
206
|
## Supported messages
|
|
228
207
|
|
|
229
208
|
- **DF4 / DF20:** altitude code (surveillance altitude reply)
|
|
@@ -82,7 +82,7 @@ print(r["latitude"], r["longitude"]) # 43.6264..., 1.3747...
|
|
|
82
82
|
`PipeDecoder` is stateful — it holds per-ICAO state across calls,
|
|
83
83
|
matches CPR pairs automatically, evicts stale aircraft after a
|
|
84
84
|
TTL, and flags DF20/21 messages as `icao_verified` when their
|
|
85
|
-
CRC-derived ICAO was
|
|
85
|
+
CRC-derived ICAO was seen recently in clean DF17/18 plaintext.
|
|
86
86
|
|
|
87
87
|
```python
|
|
88
88
|
from pyModeS import PipeDecoder
|
|
@@ -90,7 +90,7 @@ from pyModeS import PipeDecoder
|
|
|
90
90
|
pipe = PipeDecoder(surface_ref="EHAM")
|
|
91
91
|
for msg, timestamp in stream:
|
|
92
92
|
decoded = pipe.decode(msg, timestamp=timestamp)
|
|
93
|
-
if "latitude"
|
|
93
|
+
if decoded.get("latitude") is not None:
|
|
94
94
|
print(decoded["icao"], decoded["latitude"], decoded["longitude"])
|
|
95
95
|
```
|
|
96
96
|
|
|
@@ -167,8 +167,8 @@ the full command reference.
|
|
|
167
167
|
- Unified `decode()` returns every decodable field in one dict
|
|
168
168
|
- Batch mode preserves list length (errors become error-dicts, not
|
|
169
169
|
exceptions)
|
|
170
|
-
- `PipeDecoder` for streams: per-ICAO state, CPR
|
|
171
|
-
TTL eviction, DF20/21 ICAO verification via trusted-set promotion
|
|
170
|
+
- `PipeDecoder` for streams: per-ICAO state, global and local CPR position
|
|
171
|
+
decoding, TTL eviction, DF20/21 ICAO verification via trusted-set promotion
|
|
172
172
|
- `full_dict=True` populates every key in the canonical 123-field
|
|
173
173
|
schema for pandas / parquet workflows
|
|
174
174
|
- `known=` aircraft state disambiguates Comm-B BDS 5,0/6,0 ambiguity
|
|
@@ -176,27 +176,6 @@ the full command reference.
|
|
|
176
176
|
- Type-checked under mypy strict across all source files
|
|
177
177
|
- Golden-file oracle regression test against `pyModeS 2.21.1`
|
|
178
178
|
|
|
179
|
-
## Performance
|
|
180
|
-
|
|
181
|
-
The committed benchmarks run each released/working-tree version in an
|
|
182
|
-
isolated environment and reject output mismatches before reporting timings.
|
|
183
|
-
On the captured 176,612-frame mixed Beast feed, the header-prefiltered v3 path
|
|
184
|
-
processes 85,963 incoming messages/s:
|
|
185
|
-
|
|
186
|
-
| Decoder path | Throughput |
|
|
187
|
-
|---|---:|
|
|
188
|
-
| pyModeS 2.21.1 selective helpers | 52,998 msg/s |
|
|
189
|
-
| released v3.3.0 `PipeDecoder` | 3,897 msg/s |
|
|
190
|
-
| v3.4.0 `PipeDecoder` | 49,243 msg/s |
|
|
191
|
-
| v3.4.0 + header prefilter | **85,963 msg/s** |
|
|
192
|
-
|
|
193
|
-
On the 2,000-aircraft synthetic PipeDecoder workload, v3.4.0 processes
|
|
194
|
-
38,838 msg/s versus v2's 39,604 msg/s, with identical normalized output.
|
|
195
|
-
|
|
196
|
-
See [`scripts/README.md`](./scripts/README.md) for the reproducible commands
|
|
197
|
-
and [`scripts/benchmark_results/`](./scripts/benchmark_results/) for the full
|
|
198
|
-
methodology, timings, and output digests.
|
|
199
|
-
|
|
200
179
|
## Supported messages
|
|
201
180
|
|
|
202
181
|
- **DF4 / DF20:** altitude code (surveillance altitude reply)
|
|
@@ -18,8 +18,12 @@ through their names; generated benchmark reports live in
|
|
|
18
18
|
|
|
19
19
|
## Benchmarks
|
|
20
20
|
|
|
21
|
-
- `benchmark_mixed_traffic.py` — isolated v2, released
|
|
22
|
-
prefiltered
|
|
21
|
+
- `benchmark_mixed_traffic.py` — isolated v2.21.1, released v3.4.0, v3.5.0,
|
|
22
|
+
and prefiltered v3.5.0 comparison on mixed captured traffic. It measures
|
|
23
|
+
throughput, immediate streaming position yield, and coordinate agreement.
|
|
24
|
+
By default it fails below 80% of v2 position yield, below 99% agreement
|
|
25
|
+
within 100 m, or when v3.5.0 yields fewer positions than v3.4.0. The two
|
|
26
|
+
percentage floors are configurable through command-line options. Writes
|
|
23
27
|
`benchmark_results/mixed_traffic.md` by default.
|
|
24
28
|
- `benchmark_pipe.py` — synthetic high-cardinality PipeDecoder comparison.
|
|
25
29
|
Writes `benchmark_results/pipe.md` by default.
|
|
@@ -54,5 +58,6 @@ access unless those packages are already cached.
|
|
|
54
58
|
environment and checks the public API.
|
|
55
59
|
- `smoke_test_cli.sh` — checks CLI decoding and a short live Beast connection.
|
|
56
60
|
|
|
57
|
-
The CLI smoke test requires network access. The wheel smoke test
|
|
58
|
-
|
|
61
|
+
The CLI smoke test requires network access. The wheel smoke test accepts an
|
|
62
|
+
explicit wheel path; without one, it builds a fresh working-tree wheel in a
|
|
63
|
+
temporary directory so stale files under `dist/` cannot be selected.
|
|
@@ -18,9 +18,9 @@ and are NOT interchangeable:
|
|
|
18
18
|
45 NM of the true position — tighter than the airborne tolerance.
|
|
19
19
|
|
|
20
20
|
For streaming workflows where references aren't known ahead of time,
|
|
21
|
-
use :class:`PipeDecoder
|
|
22
|
-
|
|
23
|
-
``surface_ref``
|
|
21
|
+
use :class:`PipeDecoder`. It establishes airborne positions from even/odd CPR
|
|
22
|
+
pairs, then resolves later frames locally from the last validated airborne
|
|
23
|
+
position. The optional ``surface_ref`` applies only to surface messages.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
from importlib.metadata import version as _version
|