pyModeS 3.5.0__tar.gz → 3.6.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.5.0 → pymodes-3.6.0}/CHANGELOG.md +12 -0
  2. {pymodes-3.5.0 → pymodes-3.6.0}/PKG-INFO +6 -1
  3. {pymodes-3.5.0 → pymodes-3.6.0}/README.md +5 -0
  4. {pymodes-3.5.0 → pymodes-3.6.0}/pyproject.toml +1 -1
  5. {pymodes-3.5.0 → pymodes-3.6.0}/scripts/README.md +8 -4
  6. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/__init__.py +4 -0
  7. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_pipe.py +21 -5
  8. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/_args.py +10 -0
  9. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/decode.py +2 -0
  10. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/live.py +5 -1
  11. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/core.py +15 -1
  12. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/commb.py +7 -2
  13. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/message.py +17 -2
  14. {pymodes-3.5.0 → pymodes-3.6.0}/.gitignore +0 -0
  15. {pymodes-3.5.0 → pymodes-3.6.0}/LICENSE +0 -0
  16. {pymodes-3.5.0 → pymodes-3.6.0}/scripts/data/README.md +0 -0
  17. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_aero.py +0 -0
  18. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_altcode.py +0 -0
  19. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_bits.py +0 -0
  20. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_callsign.py +0 -0
  21. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_idcode.py +0 -0
  22. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_pipe_validation.py +0 -0
  23. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_schema.py +0 -0
  24. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_uncertainty.py +0 -0
  25. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/_v2_removed.py +0 -0
  26. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/__init__.py +0 -0
  27. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/__main__.py +0 -0
  28. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/_parse.py +0 -0
  29. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/_sink.py +0 -0
  30. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/_source.py +0 -0
  31. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/cli/_tui.py +0 -0
  32. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/data/__init__.py +0 -0
  33. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/data/airports.py +0 -0
  34. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/__init__.py +0 -0
  35. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/_base.py +0 -0
  36. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/acas.py +0 -0
  37. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/adsb.py +0 -0
  38. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/allcall.py +0 -0
  39. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/__init__.py +0 -0
  40. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/_helpers.py +0 -0
  41. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/_infer.py +0 -0
  42. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds05.py +0 -0
  43. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds06.py +0 -0
  44. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds08.py +0 -0
  45. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds09.py +0 -0
  46. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds10.py +0 -0
  47. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds17.py +0 -0
  48. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds20.py +0 -0
  49. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds30.py +0 -0
  50. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds40.py +0 -0
  51. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds44.py +0 -0
  52. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds45.py +0 -0
  53. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds50.py +0 -0
  54. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds60.py +0 -0
  55. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds61.py +0 -0
  56. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds62.py +0 -0
  57. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/bds/bds65.py +0 -0
  58. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/decoder/surv.py +0 -0
  59. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/errors.py +0 -0
  60. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/position/__init__.py +0 -0
  61. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/position/_airports.py +0 -0
  62. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/position/_cpr.py +0 -0
  63. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/py.typed +0 -0
  64. {pymodes-3.5.0 → pymodes-3.6.0}/src/pyModeS/util.py +0 -0
@@ -5,6 +5,18 @@ 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.6.0] — 2026-07-16
9
+
10
+ ### Added
11
+
12
+ - `decode()`, `Message.decode()`, and `PipeDecoder` accept
13
+ `include_meteo=True` to opt into heuristic Comm-B BDS 4,4 routine
14
+ meteorological reports and BDS 4,5 meteorological hazard reports. The
15
+ default remains `False` because these payload patterns can overlap other
16
+ Comm-B registers.
17
+ - The `modes decode`, `modes live`, and `scripts/stream_filtered.py` interfaces
18
+ expose the same behavior through `--include-meteo`.
19
+
8
20
  ## [3.5.0] — 2026-07-15
9
21
 
10
22
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyModeS
3
- Version: 3.5.0
3
+ Version: 3.6.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
@@ -199,6 +199,7 @@ the full command reference.
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
202
+ - `include_meteo=True` opts into heuristic Comm-B BDS 4,4/4,5 inference
202
203
  - Airport ICAO database for surface CPR resolution (`surface_ref="EHAM"`)
203
204
  - Type-checked under mypy strict across all source files
204
205
  - Golden-file oracle regression test against `pyModeS 2.21.1`
@@ -228,6 +229,10 @@ the full command reference.
228
229
  - BDS 5,0: track and turn report
229
230
  - BDS 6,0: heading and speed report
230
231
 
232
+ BDS 4,4 and 4,5 are heuristic and disabled by default. Enable them with
233
+ `decode(..., include_meteo=True)` or `PipeDecoder(include_meteo=True)` when
234
+ these meteorological registers are expected on the input feed.
235
+
231
236
  ## Migrating from pyModeS 2.x
232
237
 
233
238
  pyModeS 3 is **not backwards-compatible** with pyModeS 2.x. The
@@ -172,6 +172,7 @@ the full command reference.
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
175
+ - `include_meteo=True` opts into heuristic Comm-B BDS 4,4/4,5 inference
175
176
  - Airport ICAO database for surface CPR resolution (`surface_ref="EHAM"`)
176
177
  - Type-checked under mypy strict across all source files
177
178
  - Golden-file oracle regression test against `pyModeS 2.21.1`
@@ -201,6 +202,10 @@ the full command reference.
201
202
  - BDS 5,0: track and turn report
202
203
  - BDS 6,0: heading and speed report
203
204
 
205
+ BDS 4,4 and 4,5 are heuristic and disabled by default. Enable them with
206
+ `decode(..., include_meteo=True)` or `PipeDecoder(include_meteo=True)` when
207
+ these meteorological registers are expected on the input feed.
208
+
204
209
  ## Migrating from pyModeS 2.x
205
210
 
206
211
  pyModeS 3 is **not backwards-compatible** with pyModeS 2.x. The
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyModeS"
3
- version = "3.5.0"
3
+ version = "3.6.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" }
@@ -13,16 +13,20 @@ through their names; generated benchmark reports live in
13
13
 
14
14
  ```bash
15
15
  uv run python scripts/stream_filtered.py \
16
- --network airsquitter.lr.tudelft.nl:10006 > decoded.jsonl
16
+ --network airsquitter.lr.tudelft.nl:10006 \
17
+ --include-meteo > decoded.jsonl
17
18
  ```
18
19
 
20
+ `--include-meteo` is optional and enables heuristic Comm-B BDS 4,4/4,5
21
+ reports when the feed is expected to contain meteorological data.
22
+
19
23
  ## Benchmarks
20
24
 
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
25
+ - `benchmark_mixed_traffic.py` — isolated v2.21.1, released v3.5.0, v3.6.0,
26
+ and prefiltered v3.6.0 comparison on mixed captured traffic. It measures
23
27
  throughput, immediate streaming position yield, and coordinate agreement.
24
28
  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
29
+ within 100 m, or when v3.6.0 yields fewer positions than v3.5.0. The two
26
30
  percentage floors are configurable through command-line options. Writes
27
31
  `benchmark_results/mixed_traffic.md` by default.
28
32
  - `benchmark_pipe.py` — synthetic high-cardinality PipeDecoder comparison.
@@ -21,6 +21,10 @@ For streaming workflows where references aren't known ahead of time,
21
21
  use :class:`PipeDecoder`. It establishes airborne positions from even/odd CPR
22
22
  pairs, then resolves later frames locally from the last validated airborne
23
23
  position. The optional ``surface_ref`` applies only to surface messages.
24
+
25
+ Comm-B BDS 4,4/4,5 meteorological inference is heuristic and disabled by
26
+ default. Pass ``include_meteo=True`` to :func:`decode` or
27
+ :class:`PipeDecoder` when these reports are expected on the input feed.
24
28
  """
25
29
 
26
30
  from importlib.metadata import version as _version
@@ -104,6 +104,8 @@ class PipeDecoder(ValidationMixin):
104
104
  surface_ref: Optional surface-position CPR reference (ICAO airport
105
105
  code or (lat, lon) tuple). Used only for BDS 0,6 surface messages.
106
106
  See pyModeS.decode for details.
107
+ include_meteo: When True, Comm-B inference also considers the
108
+ heuristic BDS 4,4 and 4,5 meteorological registers. Default False.
107
109
  full_dict: When True, every decoded result is populated with
108
110
  every key from _FULL_SCHEMA.
109
111
  pair_window: Maximum age difference (seconds) between an even
@@ -129,6 +131,7 @@ class PipeDecoder(ValidationMixin):
129
131
  "_eviction_interval",
130
132
  "_eviction_ttl",
131
133
  "_full_dict",
134
+ "_include_meteo",
132
135
  "_local_ref_window",
133
136
  "_max_speed_kmps",
134
137
  "_motion_margin_km",
@@ -147,6 +150,7 @@ class PipeDecoder(ValidationMixin):
147
150
  self,
148
151
  *,
149
152
  surface_ref: str | tuple[float, float] | None = None,
153
+ include_meteo: bool = False,
150
154
  full_dict: bool = False,
151
155
  pair_window: float = 10.0,
152
156
  local_ref_window: float = 30.0,
@@ -156,6 +160,7 @@ class PipeDecoder(ValidationMixin):
156
160
  motion_margin_km: float = 2.0,
157
161
  ) -> None:
158
162
  self._surface_ref = surface_ref
163
+ self._include_meteo = include_meteo
159
164
  self._full_dict = full_dict
160
165
  self._pair_window = pair_window
161
166
  if local_ref_window < 0:
@@ -307,11 +312,22 @@ class PipeDecoder(ValidationMixin):
307
312
  else:
308
313
  known = None
309
314
 
310
- result = message.decode(
311
- surface_ref=self._surface_ref,
312
- known=known,
313
- full_dict=self._full_dict,
314
- )
315
+ # Avoid forwarding a false opt-in flag on every message. Keyword
316
+ # dispatch is measurable in this hot path; only enabled streams need
317
+ # the additional Comm-B inference branch.
318
+ if self._include_meteo:
319
+ result = message.decode(
320
+ surface_ref=self._surface_ref,
321
+ known=known,
322
+ include_meteo=True,
323
+ full_dict=self._full_dict,
324
+ )
325
+ else:
326
+ result = message.decode(
327
+ surface_ref=self._surface_ref,
328
+ known=known,
329
+ full_dict=self._full_dict,
330
+ )
315
331
 
316
332
  self._stats["decoded"] += 1
317
333
  if result.get("crc_valid") is False:
@@ -76,6 +76,11 @@ def _add_decode_parser(
76
76
  action="store_true",
77
77
  help="Populate every key in the canonical schema (missing = null).",
78
78
  )
79
+ decode_p.add_argument(
80
+ "--include-meteo",
81
+ action="store_true",
82
+ help="Include heuristic Comm-B BDS 4,4/4,5 meteorological inference.",
83
+ )
79
84
  decode_p.add_argument(
80
85
  "--reference",
81
86
  nargs=2,
@@ -132,6 +137,11 @@ def _add_live_parser(
132
137
  action="store_true",
133
138
  help="Emit every key in the canonical schema (missing = null).",
134
139
  )
140
+ live_p.add_argument(
141
+ "--include-meteo",
142
+ action="store_true",
143
+ help="Include heuristic Comm-B BDS 4,4/4,5 meteorological inference.",
144
+ )
135
145
  live_p.add_argument(
136
146
  "--dump-to",
137
147
  metavar="FILE",
@@ -50,6 +50,7 @@ def _run_single(args: argparse.Namespace) -> int:
50
50
  args.message,
51
51
  reference=reference,
52
52
  surface_ref=surface_ref,
53
+ include_meteo=args.include_meteo,
53
54
  full_dict=args.full_dict,
54
55
  )
55
56
  except Exception as e:
@@ -131,6 +132,7 @@ def _emit_batch(
131
132
  hexes,
132
133
  timestamps=timestamps,
133
134
  surface_ref=surface_ref,
135
+ include_meteo=args.include_meteo,
134
136
  full_dict=args.full_dict,
135
137
  )
136
138
  except Exception as error:
@@ -55,7 +55,11 @@ def run(args: argparse.Namespace) -> int:
55
55
  )
56
56
  return 2
57
57
 
58
- pipe = PipeDecoder(surface_ref=surface_ref, full_dict=args.full_dict)
58
+ pipe = PipeDecoder(
59
+ surface_ref=surface_ref,
60
+ include_meteo=args.include_meteo,
61
+ full_dict=args.full_dict,
62
+ )
59
63
 
60
64
  # TUI path takes its own branch — textual owns the event loop
61
65
  # and drives the source + pipe itself, so the sink pipeline
@@ -25,6 +25,7 @@ def decode(
25
25
  reference: tuple[float, float] | None = None,
26
26
  surface_ref: str | tuple[float, float] | None = None,
27
27
  known: dict[str, Any] | None = None,
28
+ include_meteo: bool = False,
28
29
  full_dict: bool = False,
29
30
  ) -> Decoded: ...
30
31
 
@@ -35,6 +36,7 @@ def decode(
35
36
  *,
36
37
  timestamps: list[float] | None = None,
37
38
  surface_ref: str | tuple[float, float] | None = None,
39
+ include_meteo: bool = False,
38
40
  full_dict: bool = False,
39
41
  ) -> list[Decoded]: ...
40
42
 
@@ -48,6 +50,7 @@ def decode(
48
50
  reference: Any = None,
49
51
  surface_ref: Any = None,
50
52
  known: Any = None,
53
+ include_meteo: bool = False,
51
54
  full_dict: bool = False,
52
55
  timestamps: Any = None,
53
56
  ) -> Any:
@@ -79,6 +82,9 @@ def decode(
79
82
  Comm-B BDS inference to disambiguate BDS 5,0 vs 6,0
80
83
  when both heuristic validators pass. Ignored for
81
84
  non-Comm-B downlink formats.
85
+ include_meteo: When True, Comm-B inference also considers BDS 4,4
86
+ and 4,5 meteorological registers. Disabled by default because
87
+ their heuristic payload patterns can overlap other registers.
82
88
  full_dict: When True, the result dict is augmented with
83
89
  every key from `_FULL_SCHEMA`, defaulting missing keys
84
90
  to `None`. Useful for pandas/parquet workflows that
@@ -102,6 +108,7 @@ def decode(
102
108
  WARNING is logged — pair matching will still function
103
109
  but the synthesized values are not wall-clock times.
104
110
  surface_ref: Same as single-message mode.
111
+ include_meteo: Same as single-message mode.
105
112
  full_dict: Same as single-message mode.
106
113
 
107
114
  Batch mode returns a ``list[Decoded]`` of the same length as
@@ -140,6 +147,7 @@ def decode(
140
147
  msg,
141
148
  timestamps=timestamps,
142
149
  surface_ref=surface_ref,
150
+ include_meteo=include_meteo,
143
151
  full_dict=full_dict,
144
152
  )
145
153
 
@@ -164,6 +172,7 @@ def decode(
164
172
  reference=reference,
165
173
  surface_ref=surface_ref,
166
174
  known=known,
175
+ include_meteo=include_meteo,
167
176
  full_dict=full_dict,
168
177
  )
169
178
 
@@ -173,6 +182,7 @@ def _decode_batch(
173
182
  *,
174
183
  timestamps: list[float] | None,
175
184
  surface_ref: str | tuple[float, float] | None,
185
+ include_meteo: bool,
176
186
  full_dict: bool,
177
187
  ) -> list[Decoded]:
178
188
  """Run ``msgs`` through a transient PipeDecoder and return results."""
@@ -193,7 +203,11 @@ def _decode_batch(
193
203
  f"messages length {len(msgs)}"
194
204
  )
195
205
 
196
- pipe = PipeDecoder(surface_ref=surface_ref, full_dict=full_dict)
206
+ pipe = PipeDecoder(
207
+ surface_ref=surface_ref,
208
+ include_meteo=include_meteo,
209
+ full_dict=full_dict,
210
+ )
197
211
  results = [
198
212
  pipe.decode(m, timestamp=t) for m, t in zip(msgs, timestamps, strict=True)
199
213
  ]
@@ -59,7 +59,12 @@ _COMMB_DISPATCH: dict[str, Callable[[int], dict[str, Any]]] = {
59
59
  class CommB(DecoderBase):
60
60
  """Decoder for DF20 Comm-B altitude replies and DF21 Comm-B identity replies."""
61
61
 
62
- def decode(self, *, known: dict[str, Any] | None = None) -> Decoded:
62
+ def decode(
63
+ self,
64
+ *,
65
+ known: dict[str, Any] | None = None,
66
+ include_meteo: bool = False,
67
+ ) -> Decoded:
63
68
  result: Decoded = Decoded()
64
69
 
65
70
  # Header field: altitude for DF20, squawk for DF21.
@@ -75,7 +80,7 @@ class CommB(DecoderBase):
75
80
  # disambiguation of BDS 5,0 / 6,0 when the caller supplies
76
81
  # aircraft state.
77
82
  candidates = _infer.infer(
78
- self._payload, self._df, include_meteo=False, known=known
83
+ self._payload, self._df, include_meteo=include_meteo, known=known
79
84
  )
80
85
  if not candidates:
81
86
  return result
@@ -17,11 +17,14 @@ This module defines two public types:
17
17
 
18
18
  import json
19
19
  from functools import cached_property
20
- from typing import Any, Self
20
+ from typing import TYPE_CHECKING, Any, Self, cast
21
21
 
22
22
  from pyModeS._bits import crc_remainder, extract_unsigned
23
23
  from pyModeS.errors import InvalidHexError, InvalidLengthError, UnknownDFError
24
24
 
25
+ if TYPE_CHECKING:
26
+ from pyModeS.decoder.commb import CommB
27
+
25
28
  _HEX_CHARS = frozenset("0123456789abcdefABCDEF")
26
29
  _VALID_LENGTHS = (56, 112)
27
30
  _HEX_LENGTHS = (14, 28)
@@ -242,6 +245,7 @@ class Message:
242
245
  reference: tuple[float, float] | None = None,
243
246
  surface_ref: str | tuple[float, float] | None = None,
244
247
  known: dict[str, Any] | None = None,
248
+ include_meteo: bool = False,
245
249
  full_dict: bool = False,
246
250
  ) -> Decoded:
247
251
  """Decode every field of this message.
@@ -270,6 +274,10 @@ class Message:
270
274
  Comm-B BDS inference to disambiguate BDS 5,0 vs 6,0
271
275
  when both heuristic validators pass. Ignored for
272
276
  non-Comm-B downlink formats.
277
+ include_meteo: When True, Comm-B inference also considers the
278
+ heuristic BDS 4,4 and 4,5 meteorological registers. Disabled
279
+ by default because their payloads can overlap other Comm-B
280
+ formats.
273
281
  full_dict: When True, the result dict is augmented with
274
282
  every key from `_FULL_SCHEMA`, defaulting missing
275
283
  keys to `None`. Useful for pandas/parquet workflows
@@ -291,7 +299,14 @@ class Message:
291
299
  decoder = decoder_cls(
292
300
  self._n, df=self.df, icao=self.icao, length=self._length
293
301
  )
294
- result.update(decoder.decode(known=known))
302
+ # Keep the common non-meteorological path free of an extra keyword
303
+ # dispatch. This matters for high-rate ADS-B streams, while the
304
+ # opt-in branch is relevant only to DF20/21 Comm-B decoders.
305
+ if include_meteo and self.df in (20, 21):
306
+ commb_decoder = cast("CommB", decoder)
307
+ result.update(commb_decoder.decode(known=known, include_meteo=True))
308
+ else:
309
+ result.update(decoder.decode(known=known))
295
310
 
296
311
  self._resolve_position(result, reference=reference, surface_ref=surface_ref)
297
312
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes