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.
- {pymodes-3.1.0 → pymodes-3.2.0}/PKG-INFO +1 -1
- {pymodes-3.1.0 → pymodes-3.2.0}/pyproject.toml +1 -1
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/_infer.py +88 -20
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds45.py +10 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/commb.py +3 -1
- {pymodes-3.1.0 → pymodes-3.2.0}/.gitignore +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/CHANGELOG.md +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/LICENSE +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/README.md +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/__init__.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_aero.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_altcode.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_bits.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_callsign.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_idcode.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_pipe.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_schema.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_uncertainty.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/_v2_removed.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/__init__.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/__main__.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/_args.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/_sink.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/_source.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/_tui.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/decode.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/cli/live.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/core.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/data/__init__.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/data/airports.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/__init__.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/_base.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/acas.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/adsb.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/allcall.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/__init__.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/_helpers.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds05.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds06.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds08.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds09.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds10.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds17.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds20.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds30.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds40.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds44.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds50.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds60.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds61.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds62.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/bds/bds65.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/decoder/surv.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/errors.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/message.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/position/__init__.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/position/_airports.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/position/_cpr.py +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/py.typed +0 -0
- {pymodes-3.1.0 → pymodes-3.2.0}/src/pyModeS/util.py +0 -0
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""BDS inference dispatch for ADS-B ES (DF17/18) and Comm-B (DF20/21).
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
178
|
+
"""Return plausible BDS codes for a payload, routing on DF.
|
|
145
179
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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"
|
|
163
|
-
nothing plausible matched or
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
File without changes
|