pyModeS 3.3.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.3.0 → pymodes-3.5.0}/CHANGELOG.md +90 -3
- {pymodes-3.3.0 → pymodes-3.5.0}/PKG-INFO +5 -26
- {pymodes-3.3.0 → pymodes-3.5.0}/README.md +4 -25
- {pymodes-3.3.0 → pymodes-3.5.0}/pyproject.toml +1 -2
- pymodes-3.5.0/scripts/README.md +63 -0
- pymodes-3.5.0/scripts/data/README.md +37 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/__init__.py +3 -3
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_pipe.py +496 -467
- pymodes-3.5.0/src/pyModeS/_pipe_validation.py +234 -0
- {pymodes-3.3.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.3.0 → pymodes-3.5.0}/src/pyModeS/cli/_source.py +142 -72
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/_tui.py +45 -42
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/decode.py +15 -26
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/live.py +18 -39
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/core.py +4 -2
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/data/airports.py +1 -1
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/_infer.py +6 -1
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/message.py +18 -6
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/position/_cpr.py +16 -6
- {pymodes-3.3.0 → pymodes-3.5.0}/.gitignore +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/LICENSE +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_aero.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_altcode.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_bits.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_callsign.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_idcode.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_schema.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_uncertainty.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_v2_removed.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/__init__.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/__main__.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/_sink.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/data/__init__.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/__init__.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/_base.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/acas.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/adsb.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/allcall.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/__init__.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/_helpers.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds05.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds06.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds08.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds09.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds10.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds17.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds20.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds30.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds40.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds44.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds45.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds50.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds60.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds61.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds62.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds65.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/commb.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/surv.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/errors.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/position/__init__.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/position/_airports.py +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/py.typed +0 -0
- {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/util.py +0 -0
|
@@ -5,6 +5,93 @@ 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
|
+
|
|
71
|
+
## [3.4.0] — 2026-07-13
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- `PipeDecoder` no longer scans every per-ICAO cache on every timestamped
|
|
76
|
+
message. Full TTL eviction is throttled to a configurable one-second
|
|
77
|
+
interval by default, capped at the TTL itself. A bounded per-ICAO check
|
|
78
|
+
still expires state exactly before it is used, so sweep throttling cannot
|
|
79
|
+
change BDS inference at the TTL boundary. Set `eviction_interval=0` for
|
|
80
|
+
the former sweep-per-message memory-reclamation behavior.
|
|
81
|
+
- Timestamped messages that contain no fields used for stateful
|
|
82
|
+
disambiguation no longer create empty per-ICAO state entries. Existing
|
|
83
|
+
useful state is still refreshed when such a message arrives.
|
|
84
|
+
- State updates skip field extraction for downlink formats that cannot
|
|
85
|
+
contribute to stateful inference.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- `scripts/stream_filtered.py`, a configurable live/replay Beast example
|
|
90
|
+
demonstrating high-volume DF/typecode prefiltering before `PipeDecoder`.
|
|
91
|
+
- Reproducible version-isolated benchmarks for mixed live traffic and
|
|
92
|
+
high-cardinality streaming, including checked-in reports and the public
|
|
93
|
+
Beast capture used by the mixed-traffic benchmark.
|
|
94
|
+
|
|
8
95
|
## [3.1.0] — 2026-04-14
|
|
9
96
|
|
|
10
97
|
Streaming-robustness release. Adds defences against phantom positions
|
|
@@ -14,8 +101,8 @@ Comm-B reply to the wrong aircraft.
|
|
|
14
101
|
### Added
|
|
15
102
|
|
|
16
103
|
- `PipeDecoder` bootstrap cluster analysis for the first position per
|
|
17
|
-
ICAO: lat/lon emission is held until
|
|
18
|
-
|
|
104
|
+
ICAO: lat/lon emission is held until five candidate positions agree
|
|
105
|
+
under a motion-consistency check. Prevents an
|
|
19
106
|
initial phantom frame from anchoring the rolling position history.
|
|
20
107
|
On each decode the held result dicts are retro-filled once the
|
|
21
108
|
cluster locks. Scattered buffers reset and accumulate a fresh K.
|
|
@@ -98,7 +185,7 @@ the migration guide.
|
|
|
98
185
|
`bin2hex`, `crc`, `df`, `icao`, `typecode`, `altcode`, `idcode`,
|
|
99
186
|
`cprNL`. Thin wrappers over `_bits.py`, `_altcode.py`, `_idcode.py`
|
|
100
187
|
and `position/_cpr.py` with no logic duplication.
|
|
101
|
-
- `scripts/
|
|
188
|
+
- `scripts/smoke_test_wheel.sh` installs the freshly built wheel
|
|
102
189
|
into a clean Python 3.12 venv and verifies the public API end to
|
|
103
190
|
end (single-message decode, PipeDecoder baseline, batch CPR pair
|
|
104
191
|
resolution, error-dict on malformed input).
|
|
@@ -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
|
-
Measured on jet1090's
|
|
209
|
-
[`long_flight.csv`](https://github.com/xoolive/jet1090/blob/master/crates/rs1090/data/long_flight.csv)
|
|
210
|
-
(172,432 Beast-format messages, 7 runs × 1 loop, mean timings,
|
|
211
|
-
single-core only):
|
|
212
|
-
|
|
213
|
-
| Decoder | Wall time | Throughput | vs pyModeS v3 |
|
|
214
|
-
|---|---|---|---|
|
|
215
|
-
| **pyModeS v3 (pure Python)** | **2.06s ± 0.01** | **83,549 msg/s** | **1.00×** |
|
|
216
|
-
| pyModeS 2.21.1 (Python with compiled C) | 5.03s ± 0.01 | 34,303 msg/s | 0.41× |
|
|
217
|
-
| rs1090 (Rust) | 5.60s ± 0.01 | 30,798 msg/s | 0.37× |
|
|
218
|
-
| pyModeS 2.21.1 (Python) | 9.09s ± 0.02 | 18,959 msg/s | 0.23× |
|
|
219
|
-
|
|
220
|
-
pyModeS v3 is **2.44× faster** than `pyModeS 2.21.1`'s compiled C
|
|
221
|
-
extension, **4.41× faster** than `pyModeS 2.21.1` pure-Python, and
|
|
222
|
-
**2.71× faster** than rs1090's single-core Rust — all while remaining
|
|
223
|
-
pure Python with no C/Cython build.
|
|
224
|
-
|
|
225
|
-
Reproduce with `scripts/benchmark_decode.py`.
|
|
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
|
-
Measured on jet1090's
|
|
182
|
-
[`long_flight.csv`](https://github.com/xoolive/jet1090/blob/master/crates/rs1090/data/long_flight.csv)
|
|
183
|
-
(172,432 Beast-format messages, 7 runs × 1 loop, mean timings,
|
|
184
|
-
single-core only):
|
|
185
|
-
|
|
186
|
-
| Decoder | Wall time | Throughput | vs pyModeS v3 |
|
|
187
|
-
|---|---|---|---|
|
|
188
|
-
| **pyModeS v3 (pure Python)** | **2.06s ± 0.01** | **83,549 msg/s** | **1.00×** |
|
|
189
|
-
| pyModeS 2.21.1 (Python with compiled C) | 5.03s ± 0.01 | 34,303 msg/s | 0.41× |
|
|
190
|
-
| rs1090 (Rust) | 5.60s ± 0.01 | 30,798 msg/s | 0.37× |
|
|
191
|
-
| pyModeS 2.21.1 (Python) | 9.09s ± 0.02 | 18,959 msg/s | 0.23× |
|
|
192
|
-
|
|
193
|
-
pyModeS v3 is **2.44× faster** than `pyModeS 2.21.1`'s compiled C
|
|
194
|
-
extension, **4.41× faster** than `pyModeS 2.21.1` pure-Python, and
|
|
195
|
-
**2.71× faster** than rs1090's single-core Rust — all while remaining
|
|
196
|
-
pure Python with no C/Cython build.
|
|
197
|
-
|
|
198
|
-
Reproduce with `scripts/benchmark_decode.py`.
|
|
199
|
-
|
|
200
179
|
## Supported messages
|
|
201
180
|
|
|
202
181
|
- **DF4 / DF20:** altitude code (surveillance altitude reply)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyModeS"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.5.0"
|
|
4
4
|
description = "Python Mode-S and ADS-B Decoder"
|
|
5
5
|
authors = [{ name = "Junzi Sun", email = "git@junzis.com" }]
|
|
6
6
|
license = { text = "GNU GPL v3" }
|
|
@@ -57,7 +57,6 @@ dev = [
|
|
|
57
57
|
[tool.ruff]
|
|
58
58
|
target-version = "py311"
|
|
59
59
|
line-length = 88
|
|
60
|
-
extend-exclude = ["scripts/bench_pms_v2.py"]
|
|
61
60
|
|
|
62
61
|
[tool.ruff.lint]
|
|
63
62
|
select = [
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Maintainer scripts
|
|
2
|
+
|
|
3
|
+
Run these commands from the repository root. Scripts are grouped by purpose
|
|
4
|
+
through their names; generated benchmark reports live in
|
|
5
|
+
`scripts/benchmark_results/`, and committed input data lives in
|
|
6
|
+
`scripts/data/`.
|
|
7
|
+
|
|
8
|
+
## Streaming example
|
|
9
|
+
|
|
10
|
+
- `stream_filtered.py` — configurable high-throughput example using a live
|
|
11
|
+
Mode-S Beast feed or the committed replay capture. Defaults to DF17/20/21
|
|
12
|
+
while excluding ADS-B typecodes 28/29/31.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv run python scripts/stream_filtered.py \
|
|
16
|
+
--network airsquitter.lr.tudelft.nl:10006 > decoded.jsonl
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Benchmarks
|
|
20
|
+
|
|
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
|
|
27
|
+
`benchmark_results/mixed_traffic.md` by default.
|
|
28
|
+
- `benchmark_pipe.py` — synthetic high-cardinality PipeDecoder comparison.
|
|
29
|
+
Writes `benchmark_results/pipe.md` by default.
|
|
30
|
+
- `benchmark_cpr.py` — focused CPR primitive comparison against the v2 NumPy
|
|
31
|
+
implementation.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv run python scripts/benchmark_mixed_traffic.py
|
|
35
|
+
uv run python scripts/benchmark_pipe.py
|
|
36
|
+
uv run --with numpy python scripts/benchmark_cpr.py
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The version-comparison benchmarks build the working tree and resolve released
|
|
40
|
+
versions from PyPI in isolated temporary environments. They require network
|
|
41
|
+
access unless those packages are already cached.
|
|
42
|
+
|
|
43
|
+
## Generated data
|
|
44
|
+
|
|
45
|
+
- `generate_airport_db.py` — refreshes `src/pyModeS/data/airports.py` from
|
|
46
|
+
OurAirports.
|
|
47
|
+
- `generate_v2_fixture.py` — regenerates the committed v2.21.1 golden oracle.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv run python scripts/generate_airport_db.py
|
|
51
|
+
uv run --no-project --with pyModeS==2.21.1 \
|
|
52
|
+
python scripts/generate_v2_fixture.py
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Release smoke tests
|
|
56
|
+
|
|
57
|
+
- `smoke_test_wheel.sh [wheel]` — installs a built wheel in a clean virtual
|
|
58
|
+
environment and checks the public API.
|
|
59
|
+
- `smoke_test_cli.sh` — checks CLI decoding and a short live Beast connection.
|
|
60
|
+
|
|
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.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Benchmark capture data
|
|
2
|
+
|
|
3
|
+
## `airsquitter_2026-07-13_120s.csv.gz`
|
|
4
|
+
|
|
5
|
+
Two-minute capture from the public TU Delft AirSquitter Mode-S Beast feed:
|
|
6
|
+
|
|
7
|
+
- Source: `airsquitter.lr.tudelft.nl:10006` (TCP, Beast binary)
|
|
8
|
+
- Capture date: 2026-07-13
|
|
9
|
+
- Frames: 176,612
|
|
10
|
+
- Uncompressed format: `timestamp,hex_message`, one frame per line
|
|
11
|
+
- Compressed size: approximately 2 MiB
|
|
12
|
+
- SHA-256: `fa433eb8dc2ec9cf6ebd40c1bf0a095ae753960d5e8c42ae6da15cc28eb3baea`
|
|
13
|
+
|
|
14
|
+
The timestamps are reconstructed by `pyModeS.cli._source.NetworkSource`
|
|
15
|
+
from the Beast MLAT counter, anchored to wall-clock time when the TCP data
|
|
16
|
+
is received. Mode A/C and Beast status frames are omitted because
|
|
17
|
+
`NetworkSource` yields only Mode-S short and long frames.
|
|
18
|
+
|
|
19
|
+
Capture and compact conversion:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
timeout --signal=INT 120 \
|
|
23
|
+
modes live \
|
|
24
|
+
--network airsquitter.lr.tudelft.nl:10006 \
|
|
25
|
+
--quiet \
|
|
26
|
+
--dump-to /tmp/pymodes-live-mixed.jsonl
|
|
27
|
+
|
|
28
|
+
jq -r '"\(.timestamp),\(.raw_msg)"' \
|
|
29
|
+
/tmp/pymodes-live-mixed.jsonl \
|
|
30
|
+
| gzip -9 > scripts/data/airsquitter_2026-07-13_120s.csv.gz
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Run the associated isolated three-version benchmark with:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
python scripts/benchmark_mixed_traffic.py
|
|
37
|
+
```
|
|
@@ -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
|