pyModeS 3.1.0__tar.gz → 3.2.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 (60) hide show
  1. {pymodes-3.1.0 → pymodes-3.2.0}/PKG-INFO +1 -1
  2. {pymodes-3.1.0 → pymodes-3.2.0}/pyproject.toml +1 -1
  3. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/_infer.py +88 -20
  4. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds45.py +10 -0
  5. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/commb.py +3 -1
  6. {pymodes-3.1.0 → pymodes-3.2.0}/.gitignore +0 -0
  7. {pymodes-3.1.0 → pymodes-3.2.0}/CHANGELOG.md +0 -0
  8. {pymodes-3.1.0 → pymodes-3.2.0}/LICENSE +0 -0
  9. {pymodes-3.1.0 → pymodes-3.2.0}/README.md +0 -0
  10. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/__init__.py +0 -0
  11. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_aero.py +0 -0
  12. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_altcode.py +0 -0
  13. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_bits.py +0 -0
  14. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_callsign.py +0 -0
  15. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_idcode.py +0 -0
  16. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_pipe.py +0 -0
  17. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_schema.py +0 -0
  18. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_uncertainty.py +0 -0
  19. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_v2_removed.py +0 -0
  20. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/__init__.py +0 -0
  21. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/__main__.py +0 -0
  22. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/_args.py +0 -0
  23. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/_sink.py +0 -0
  24. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/_source.py +0 -0
  25. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/_tui.py +0 -0
  26. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/decode.py +0 -0
  27. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/live.py +0 -0
  28. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/core.py +0 -0
  29. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/data/__init__.py +0 -0
  30. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/data/airports.py +0 -0
  31. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/__init__.py +0 -0
  32. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/_base.py +0 -0
  33. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/acas.py +0 -0
  34. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/adsb.py +0 -0
  35. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/allcall.py +0 -0
  36. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/__init__.py +0 -0
  37. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/_helpers.py +0 -0
  38. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds05.py +0 -0
  39. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds06.py +0 -0
  40. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds08.py +0 -0
  41. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds09.py +0 -0
  42. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds10.py +0 -0
  43. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds17.py +0 -0
  44. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds20.py +0 -0
  45. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds30.py +0 -0
  46. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds40.py +0 -0
  47. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds44.py +0 -0
  48. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds50.py +0 -0
  49. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds60.py +0 -0
  50. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds61.py +0 -0
  51. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds62.py +0 -0
  52. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds65.py +0 -0
  53. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/surv.py +0 -0
  54. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/errors.py +0 -0
  55. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/message.py +0 -0
  56. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/position/__init__.py +0 -0
  57. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/position/_airports.py +0 -0
  58. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/position/_cpr.py +0 -0
  59. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/py.typed +0 -0
  60. {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyModeS
3
- Version: 3.1.0
3
+ Version: 3.2.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyModeS"
3
- version = "3.1.0"
3
+ version = "3.2.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" }
@@ -1,6 +1,12 @@
1
- """Two-phase BDS inference dispatch for Comm-B (DF20/21).
1
+ """BDS inference dispatch for ADS-B ES (DF17/18) and Comm-B (DF20/21).
2
2
 
3
- Phase 1 -- format-ID fast path
3
+ ADS-B ES path (DF 17/18)
4
+ ES ME payloads carry an explicit Type Code (TC) in the top 5 bits.
5
+ ``infer()`` extracts TC and maps it to the ICAO-standard BDS-like
6
+ code via ``_infer_es()``. This path ignores ``include_meteo`` and
7
+ ``known`` (the TC is deterministic; no heuristic is needed).
8
+
9
+ Phase 1 -- format-ID fast path (Comm-B only)
4
10
  The top 8 bits of the payload (bits 0-7) contain an explicit
5
11
  BDS identifier for registers 1,0 / 2,0 / 3,0. We check these
6
12
  first: the validator is still run so reserved-bit and field-range
@@ -12,7 +18,7 @@ Phase 1 -- format-ID fast path
12
18
  map. Its entry uses ``None`` as the expected prefix and runs its
13
19
  validator directly.
14
20
 
15
- Phase 2 -- heuristic slow path
21
+ Phase 2 -- heuristic slow path (Comm-B only)
16
22
  Registers 4,0 / 5,0 / 6,0 have no format identifier and must be
17
23
  detected by status-bit and range heuristics. Each heuristic
18
24
  validator is run unconditionally and every match is added to the
@@ -23,7 +29,7 @@ Phase 2 -- heuristic slow path
23
29
  and produce false positives unless the caller opts in via
24
30
  ``include_meteo=True``.
25
31
 
26
- Phase 3 -- reference-assisted disambiguation
32
+ Phase 3 -- reference-assisted disambiguation (Comm-B only)
27
33
  When multiple heuristic candidates (5,0 / 6,0) survive Phase 2
28
34
  and the caller passes ``known=`` aircraft state (groundspeed,
29
35
  track, heading, IAS, mach), each candidate is decoded and
@@ -135,33 +141,93 @@ def matches(bds_code: str, payload: int) -> bool:
135
141
  return validator(payload)
136
142
 
137
143
 
144
+ def _infer_es(payload: int) -> list[str]:
145
+ """Classify an ADS-B ES (DF 17/18) payload by Type Code.
146
+
147
+ TC is the top 5 bits of the 56-bit ME field: ``(payload >> 51) & 0x1F``.
148
+
149
+ Returns:
150
+ A single-element list with the ICAO-standard BDS-like code, or
151
+ an empty list for reserved / unused Type Codes (0, 23-27, 30).
152
+ """
153
+ tc = (payload >> 51) & 0x1F
154
+ if 1 <= tc <= 4:
155
+ return ["0,8"]
156
+ if 5 <= tc <= 8:
157
+ return ["0,6"]
158
+ if 9 <= tc <= 18 or 20 <= tc <= 22:
159
+ return ["0,5"]
160
+ if tc == 19:
161
+ return ["0,9"]
162
+ if tc == 28:
163
+ return ["6,1"]
164
+ if tc == 29:
165
+ return ["6,2"]
166
+ if tc == 31:
167
+ return ["6,5"]
168
+ return []
169
+
170
+
138
171
  def infer(
139
172
  payload: int,
173
+ df: int,
140
174
  *,
141
175
  include_meteo: bool = False,
142
176
  known: dict[str, Any] | None = None,
143
177
  ) -> list[str]:
144
- """Return plausible BDS codes for a Comm-B payload.
178
+ """Return plausible BDS codes for a payload, routing on DF.
145
179
 
146
- The first element (when non-empty) is the best candidate -- either
147
- the format-ID'd register (Phase 1) or the first heuristic match
148
- (Phase 2). Any additional heuristic matches follow.
180
+ **ADS-B ES path (DF 17 or 18)**
181
+ The Type Code in the top 5 bits of the 56-bit ME field
182
+ unambiguously identifies the message type. ``include_meteo``
183
+ and ``known`` are ignored on this path -- the TC is
184
+ deterministic and no heuristic scoring is needed.
185
+
186
+ **Comm-B path (DF 20 or 21)**
187
+ Three-phase scan:
188
+
189
+ Phase 1 -- format-ID fast path.
190
+ The first element (when non-empty) is the best candidate --
191
+ either the format-ID'd register (Phase 1) or the first
192
+ heuristic match (Phase 2). Any additional heuristic matches
193
+ follow.
194
+
195
+ Phase 2 -- heuristic slow path.
196
+ BDS 4,0 / 5,0 / 6,0 validators run unconditionally.
197
+ BDS 4,4 / 4,5 are tried only when ``include_meteo=True``.
198
+
199
+ Phase 3 -- known-state disambiguation.
200
+ When multiple heuristic candidates (BDS 5,0 / 6,0) survive
201
+ Phase 2, each is scored against the known aircraft state and
202
+ the best match is moved to the front.
149
203
 
150
204
  Args:
151
- payload: The 56-bit Comm-B payload as a Python int.
152
- include_meteo: When True, also try BDS 4,4 and 4,5 heuristic
153
- validators. Defaults to False because these share bit
154
- patterns with non-meteorological traffic.
155
- known: Optional aircraft state for reference-assisted
156
- disambiguation. When multiple heuristic candidates
157
- (BDS 5,0 / 6,0) survive Phase 2, each is scored against
158
- the known state and the best match is moved to the front
159
- of the heuristic block.
205
+ payload: The 56-bit payload (ME field for ES, MB field for
206
+ Comm-B) as a Python int.
207
+ df: Downlink Format. Must be 17, 18 (ADS-B ES) or 20, 21
208
+ (Comm-B). Any other value raises ``ValueError``.
209
+ include_meteo: Comm-B only. When True, also try BDS 4,4 and
210
+ 4,5 heuristic validators. Ignored for DF 17/18.
211
+ known: Comm-B only. Optional aircraft state for Phase 3
212
+ disambiguation of BDS 5,0 / 6,0. Ignored for DF 17/18.
160
213
 
161
214
  Returns:
162
- A list of BDS code strings (e.g. "1,0", "5,0"). Empty list if
163
- nothing plausible matched or `payload == 0`.
215
+ A list of BDS code strings (e.g. ``"1,0"``, ``"5,0"``,
216
+ ``"0,5"``). Empty list if nothing plausible matched or
217
+ ``payload == 0``.
218
+
219
+ Raises:
220
+ ValueError: If ``df`` is not 17, 18, 20, or 21.
164
221
  """
222
+ if df == 17 or df == 18:
223
+ return _infer_es(payload)
224
+
225
+ if df != 20 and df != 21:
226
+ raise ValueError(
227
+ "infer() only supports DF 17/18 (ADS-B) or DF 20/21 (Comm-B),"
228
+ f" got df={df}"
229
+ )
230
+
165
231
  if payload == 0:
166
232
  return []
167
233
 
@@ -210,7 +276,9 @@ def infer(
210
276
  )
211
277
  pre = candidates[:first_heuristic_idx]
212
278
  tail = [
213
- c for c in candidates[first_heuristic_idx:] if c not in ("5,0", "6,0")
279
+ c
280
+ for c in candidates[first_heuristic_idx:]
281
+ if c not in ("5,0", "6,0")
214
282
  ]
215
283
  candidates = pre + scored + tail
216
284
 
@@ -33,6 +33,7 @@ where it decoded temperature unconditionally.
33
33
  from typing import Any
34
34
 
35
35
  from pyModeS.decoder.bds._helpers import signed, wrong_status
36
+ from pyModeS.decoder.bds.bds17 import is_bds17
36
37
 
37
38
 
38
39
  def is_bds45(payload: int) -> bool:
@@ -40,6 +41,15 @@ def is_bds45(payload: int) -> bool:
40
41
  if payload == 0:
41
42
  return False
42
43
 
44
+ # Disambiguate from BDS 1,7 (GICB Capability Report). A BDS 1,7
45
+ # payload has 32 trailing zero bits, which trivially satisfies
46
+ # 4,5's 5-bit reserved check; its top-24 capability map then
47
+ # passes 4,5's status/range heuristics often enough to cause
48
+ # ~97% of real BDS 1,7 traffic to match here. The 1,7 pattern
49
+ # (bit 6 set + 32 trailing zeros) is strictly more specific.
50
+ if is_bds17(payload):
51
+ return False
52
+
43
53
  # Reserved bits 51-55 must all be zero.
44
54
  if (payload & 0x1F) != 0:
45
55
  return False
@@ -74,7 +74,9 @@ class CommB(DecoderBase):
74
74
  # BDS inference. `known` is threaded through for Phase 3
75
75
  # disambiguation of BDS 5,0 / 6,0 when the caller supplies
76
76
  # aircraft state.
77
- candidates = _infer.infer(self._payload, include_meteo=False, known=known)
77
+ candidates = _infer.infer(
78
+ self._payload, self._df, include_meteo=False, known=known
79
+ )
78
80
  if not candidates:
79
81
  return result
80
82
 
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes