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.
Files changed (64) hide show
  1. {pymodes-3.3.0 → pymodes-3.5.0}/CHANGELOG.md +90 -3
  2. {pymodes-3.3.0 → pymodes-3.5.0}/PKG-INFO +5 -26
  3. {pymodes-3.3.0 → pymodes-3.5.0}/README.md +4 -25
  4. {pymodes-3.3.0 → pymodes-3.5.0}/pyproject.toml +1 -2
  5. pymodes-3.5.0/scripts/README.md +63 -0
  6. pymodes-3.5.0/scripts/data/README.md +37 -0
  7. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/__init__.py +3 -3
  8. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_pipe.py +496 -467
  9. pymodes-3.5.0/src/pyModeS/_pipe_validation.py +234 -0
  10. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/_args.py +11 -0
  11. pymodes-3.5.0/src/pyModeS/cli/_parse.py +51 -0
  12. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/_source.py +142 -72
  13. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/_tui.py +45 -42
  14. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/decode.py +15 -26
  15. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/live.py +18 -39
  16. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/core.py +4 -2
  17. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/data/airports.py +1 -1
  18. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/_infer.py +6 -1
  19. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/message.py +18 -6
  20. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/position/_cpr.py +16 -6
  21. {pymodes-3.3.0 → pymodes-3.5.0}/.gitignore +0 -0
  22. {pymodes-3.3.0 → pymodes-3.5.0}/LICENSE +0 -0
  23. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_aero.py +0 -0
  24. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_altcode.py +0 -0
  25. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_bits.py +0 -0
  26. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_callsign.py +0 -0
  27. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_idcode.py +0 -0
  28. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_schema.py +0 -0
  29. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_uncertainty.py +0 -0
  30. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/_v2_removed.py +0 -0
  31. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/__init__.py +0 -0
  32. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/__main__.py +0 -0
  33. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/cli/_sink.py +0 -0
  34. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/data/__init__.py +0 -0
  35. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/__init__.py +0 -0
  36. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/_base.py +0 -0
  37. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/acas.py +0 -0
  38. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/adsb.py +0 -0
  39. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/allcall.py +0 -0
  40. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/__init__.py +0 -0
  41. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/_helpers.py +0 -0
  42. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds05.py +0 -0
  43. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds06.py +0 -0
  44. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds08.py +0 -0
  45. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds09.py +0 -0
  46. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds10.py +0 -0
  47. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds17.py +0 -0
  48. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds20.py +0 -0
  49. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds30.py +0 -0
  50. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds40.py +0 -0
  51. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds44.py +0 -0
  52. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds45.py +0 -0
  53. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds50.py +0 -0
  54. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds60.py +0 -0
  55. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds61.py +0 -0
  56. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds62.py +0 -0
  57. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/bds/bds65.py +0 -0
  58. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/commb.py +0 -0
  59. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/decoder/surv.py +0 -0
  60. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/errors.py +0 -0
  61. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/position/__init__.py +0 -0
  62. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/position/_airports.py +0 -0
  63. {pymodes-3.3.0 → pymodes-3.5.0}/src/pyModeS/py.typed +0 -0
  64. {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 `_BOOTSTRAP_K` (=5) candidate
18
- positions agree under a motion-consistency check. Prevents an
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/smoke_test.sh` installs the freshly built wheel
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.0
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 already seen in a clean DF17/18 plaintext.
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" in decoded:
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 pair accumulation,
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 already seen in a clean DF17/18 plaintext.
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" in decoded:
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 pair accumulation,
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.0"
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` which resolves airborne positions from
22
- even/odd CPR pairs without any reference and still accepts
23
- ``surface_ref`` for surface messages.
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