diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 684b375..dfef0cb 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -30,9 +30,9 @@ jobs: pip install -U pytest codecov pytest-cov pip install . - # - name: Type checking - # run: | - # mypy pyModeS tests + - name: Type checking + run: | + mypy pyModeS - name: Run tests (without Cython) run: | diff --git a/pyModeS/__init__.py b/pyModeS/__init__.py index 60ef606..9504d45 100644 --- a/pyModeS/__init__.py +++ b/pyModeS/__init__.py @@ -5,8 +5,8 @@ try: from . import c_common as common from .c_common import * except: - from . import py_common as common - from .py_common import * + from . import py_common as common # type: ignore + from .py_common import * # type: ignore from .decoder import tell from .decoder import adsb diff --git a/pyModeS/c_common.pyi b/pyModeS/c_common.pyi new file mode 100644 index 0000000..dd28386 --- /dev/null +++ b/pyModeS/c_common.pyi @@ -0,0 +1,29 @@ +def hex2bin(hexstr: str) -> str: ... +def bin2int(binstr: str) -> int: ... +def hex2int(hexstr: str) -> int: ... +def bin2hex(binstr: str) -> str: ... + + +def df(msg: str) -> int: ... +def crc(msg: str, encode: bool = False) -> int: ... + + +def floor(x: float) -> float: ... +def icao(msg: str) -> str: ... +def is_icao_assigned(icao: str) -> bool: ... + + +def typecode(msg: str) -> int: ... +def cprNL(lat: float) -> int: ... + + +def idcode(msg: str) -> str: ... +def squawk(binstr: str) -> str: ... + + +def altcode(msg: str) -> int: ... +def altitude(binstr: str) -> int: ... + + +def data(msg: str) -> str: ... +def allzeros(msg: str) -> bool: ... diff --git a/pyModeS/decoder/__init__.py b/pyModeS/decoder/__init__.py index b6686de..aa4b901 100644 --- a/pyModeS/decoder/__init__.py +++ b/pyModeS/decoder/__init__.py @@ -71,12 +71,12 @@ def tell(msg: str) -> None: _print("CPR Longitude", cprlon) _print("Altitude", alt, "feet") - if tc == 29: # target state and status + if tc == 29: # target state and status _print("Type", "Target State and Status") subtype = common.bin2int((common.hex2bin(msg)[32:])[5:7]) _print("Subtype", subtype) tcas_operational = adsb.tcas_operational(msg) - types = {0: "Not Engaged", 1: "Engaged"} + types_29 = {0: "Not Engaged", 1: "Engaged"} tcas_operational_types = {0: "Not Operational", 1: "Operational"} if subtype == 0: emergency_types = { @@ -87,11 +87,11 @@ def tell(msg: str) -> None: 4: "No communications", 5: "Unlawful interference", 6: "Downed aircraft", - 7: "Reserved" + 7: "Reserved", } vertical_horizontal_types = { 1: "Acquiring mode", - 2: "Capturing/Maintaining mode" + 2: "Capturing/Maintaining mode", } tcas_ra_types = {0: "Not active", 1: "Active"} alt, alt_source, alt_ref = adsb.target_altitude(msg) @@ -108,7 +108,12 @@ def tell(msg: str) -> None: _print("Angle Source", angle_source) _print("Vertical mode", vertical_horizontal_types[vertical_mode]) _print("Horizontal mode", vertical_horizontal_types[horizontal_mode]) - _print("TCAS/ACAS", tcas_operational_types[tcas_operational]) + _print( + "TCAS/ACAS", + tcas_operational_types[tcas_operational] + if tcas_operational + else None, + ) _print("TCAS/ACAS RA", tcas_ra_types[tcas_ra]) _print("Emergency status", emergency_types[emergency_status]) else: @@ -124,14 +129,20 @@ def tell(msg: str) -> None: _print("Altitude source", alt_source) _print("Barometric pressure setting", baro, "millibars") _print("Selected Heading", hdg, "°") - if not(common.bin2int((common.hex2bin(msg)[32:])[46]) == 0): - _print("Autopilot", types[autopilot]) - _print("VNAV mode", types[vnav]) - _print("Altitude hold mode", types[alt_hold]) - _print("Approach mode", types[app]) - _print("TCAS/ACAS", tcas_operational_types[tcas_operational]) - _print("LNAV mode", types[lnav]) - + if not (common.bin2int((common.hex2bin(msg)[32:])[46]) == 0): + _print("Autopilot", types_29[autopilot] if autopilot else None) + _print("VNAV mode", types_29[vnav] if vnav else None) + _print( + "Altitude hold mode", types_29[alt_hold] if alt_hold else None + ) + _print("Approach mode", types_29[app] if app else None) + _print( + "TCAS/ACAS", + tcas_operational_types[tcas_operational] + if tcas_operational + else None, + ) + _print("LNAV mode", types_29[lnav] if lnav else None) if df == 20: _print("Protocol", "Mode-S Comm-B altitude reply") diff --git a/pyModeS/decoder/adsb.py b/pyModeS/decoder/adsb.py index ee41017..285db62 100644 --- a/pyModeS/decoder/adsb.py +++ b/pyModeS/decoder/adsb.py @@ -1,5 +1,3 @@ -# noqa - """ADS-B module. The ADS-B module also imports functions from the following modules: diff --git a/pyModeS/decoder/bds/bds62.py b/pyModeS/decoder/bds/bds62.py index 13acaa1..16db8f1 100644 --- a/pyModeS/decoder/bds/bds62.py +++ b/pyModeS/decoder/bds/bds62.py @@ -4,8 +4,10 @@ # Target State and Status # ------------------------------------------ +from __future__ import annotations from pyModeS import common + def selected_altitude(msg): """Decode selected altitude. @@ -19,14 +21,19 @@ def selected_altitude(msg): """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 0: - raise RuntimeError("%s: ADS-B version 1 target state and status message does not contain selected altitude, use target altitude instead" % msg) + raise RuntimeError( + "%s: ADS-B version 1 target state and status message does not contain selected altitude, use target altitude instead" + % msg + ) alt = common.bin2int(mb[9:20]) alt = None if alt == 0 else (alt - 1) * 32 @@ -35,7 +42,6 @@ def selected_altitude(msg): return alt, alt_source - def target_altitude(msg): """Decode target altitude. @@ -50,14 +56,19 @@ def target_altitude(msg): """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 1: - raise RuntimeError("%s: ADS-B version 2 target state and status message does not contain target altitude, use selected altitude instead" % msg) + raise RuntimeError( + "%s: ADS-B version 2 target state and status message does not contain target altitude, use selected altitude instead" + % msg + ) alt_avail = common.bin2int(mb[7:9]) if alt_avail == 0: @@ -94,14 +105,19 @@ def vertical_mode(msg): """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 1: - raise RuntimeError("%s: ADS-B version 2 target state and status message does not contain vertical mode, use vnav mode instead" % msg) + raise RuntimeError( + "%s: ADS-B version 2 target state and status message does not contain vertical mode, use vnav mode instead" + % msg + ) vertical_mode = common.bin2int(mb[13:15]) if vertical_mode == 0: @@ -128,14 +144,19 @@ def horizontal_mode(msg): """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 1: - raise RuntimeError("%s: ADS-B version 2 target state and status message does not contain horizontal mode, use lnav mode instead" % msg) + raise RuntimeError( + "%s: ADS-B version 2 target state and status message does not contain horizontal mode, use lnav mode instead" + % msg + ) horizontal_mode = common.bin2int(mb[25:27]) if horizontal_mode == 0: @@ -152,24 +173,29 @@ def selected_heading(msg): Returns: float: Selected heading (degree) - + """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 0: - raise RuntimeError("%s: ADS-B version 1 target state and status message does not contain selected heading, use target angle instead" % msg) + raise RuntimeError( + "%s: ADS-B version 1 target state and status message does not contain selected heading, use target angle instead" + % msg + ) if int(mb[29]) == 0: hdg = None else: hdg_sign = int(mb[30]) - hdg = (hdg_sign+1) * common.bin2int(mb[31:39]) * (180/256) + hdg = (hdg_sign + 1) * common.bin2int(mb[31:39]) * (180 / 256) hdg = round(hdg, 2) return hdg @@ -185,18 +211,23 @@ def target_angle(msg): int: Target angle (degree) string: Angle type ('Heading' or 'Track') string: Source ('MCP/FCU', 'Autopilot Mode' or 'FMS/RNAV') - + """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 1: - raise RuntimeError("%s: ADS-B version 2 target state and status message does not contain target angle, use selected heading instead" % msg) + raise RuntimeError( + "%s: ADS-B version 2 target state and status message does not contain target angle, use selected heading instead" + % msg + ) angle_avail = common.bin2int(mb[25:27]) if angle_avail == 0: @@ -210,7 +241,7 @@ def target_angle(msg): angle_source = "Autopilot mode" else: angle_source = "FMS/RNAV" - + angle_type = "Heading" if int(mb[36]) else "Track" return angle, angle_type, angle_source @@ -228,14 +259,19 @@ def baro_pressure_setting(msg): """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 0: - raise RuntimeError("%s: ADS-B version 1 target state and status message does not contain barometric pressure setting" % msg) + raise RuntimeError( + "%s: ADS-B version 1 target state and status message does not contain barometric pressure setting" + % msg + ) baro = common.bin2int(mb[20:29]) baro = None if baro == 0 else 800 + (baro - 1) * 0.8 @@ -243,7 +279,8 @@ def baro_pressure_setting(msg): return baro -def autopilot(msg) -> bool: + +def autopilot(msg) -> None | bool: """Decode autopilot engagement. Args: @@ -255,14 +292,19 @@ def autopilot(msg) -> bool: """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 0: - raise RuntimeError("%s: ADS-B version 1 target state and status message does not contain autopilot engagement" % msg) + raise RuntimeError( + "%s: ADS-B version 1 target state and status message does not contain autopilot engagement" + % msg + ) if int(mb[46]) == 0: return None @@ -271,7 +313,8 @@ def autopilot(msg) -> bool: return autopilot -def vnav_mode(msg) -> bool: + +def vnav_mode(msg) -> None | bool: """Decode VNAV mode. Args: @@ -283,14 +326,19 @@ def vnav_mode(msg) -> bool: """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 0: - raise RuntimeError("%s: ADS-B version 1 target state and status message does not contain vnav mode, use vertical mode instead" % msg) + raise RuntimeError( + "%s: ADS-B version 1 target state and status message does not contain vnav mode, use vertical mode instead" + % msg + ) if int(mb[46]) == 0: return None @@ -300,7 +348,7 @@ def vnav_mode(msg) -> bool: return vnav_mode -def altitude_hold_mode(msg) -> bool: +def altitude_hold_mode(msg) -> None | bool: """Decode altitude hold mode. Args: @@ -312,14 +360,19 @@ def altitude_hold_mode(msg) -> bool: """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 0: - raise RuntimeError("%s: ADS-B version 1 target state and status message does not contain altitude hold mode" % msg) + raise RuntimeError( + "%s: ADS-B version 1 target state and status message does not contain altitude hold mode" + % msg + ) if int(mb[46]) == 0: return None @@ -329,8 +382,7 @@ def altitude_hold_mode(msg) -> bool: return alt_hold_mode - -def approach_mode(msg) -> bool: +def approach_mode(msg) -> None | bool: """Decode approach mode. Args: @@ -342,14 +394,19 @@ def approach_mode(msg) -> bool: """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 0: - raise RuntimeError("%s: ADS-B version 1 target state and status message does not contain approach mode" % msg) + raise RuntimeError( + "%s: ADS-B version 1 target state and status message does not contain approach mode" + % msg + ) if int(mb[46]) == 0: return None @@ -359,7 +416,7 @@ def approach_mode(msg) -> bool: return app_mode -def lnav_mode(msg) -> bool: +def lnav_mode(msg) -> None | bool: """Decode LNAV mode. Args: @@ -371,14 +428,19 @@ def lnav_mode(msg) -> bool: """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 0: - raise RuntimeError("%s: ADS-B version 1 target state and status message does not contain lnav mode, use horizontal mode instead" % msg) + raise RuntimeError( + "%s: ADS-B version 1 target state and status message does not contain lnav mode, use horizontal mode instead" + % msg + ) if int(mb[46]) == 0: return None @@ -388,7 +450,7 @@ def lnav_mode(msg) -> bool: return lnav_mode -def tcas_operational(msg) -> bool: +def tcas_operational(msg) -> None | bool: """Decode TCAS/ACAS operational. Args: @@ -400,7 +462,9 @@ def tcas_operational(msg) -> bool: """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] @@ -413,6 +477,7 @@ def tcas_operational(msg) -> bool: return tcas + def tcas_ra(msg) -> bool: """Decode TCAS/ACAS Resolution advisory. @@ -425,14 +490,19 @@ def tcas_ra(msg) -> bool: """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 1: - raise RuntimeError("%s: ADS-B version 2 target state and status message does not contain TCAS/ACAS RA" % msg) + raise RuntimeError( + "%s: ADS-B version 2 target state and status message does not contain TCAS/ACAS RA" + % msg + ) tcas_ra = True if int(mb[52]) == 1 else False @@ -462,13 +532,18 @@ def emergency_status(msg) -> int: """ if common.typecode(msg) != 29: - raise RuntimeError("%s: Not a target state and status message, expecting TC=29" % msg) + raise RuntimeError( + "%s: Not a target state and status message, expecting TC=29" % msg + ) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:7]) if subtype == 1: - raise RuntimeError("%s: ADS-B version 2 target state and status message does not contain emergency status" % msg) + raise RuntimeError( + "%s: ADS-B version 2 target state and status message does not contain emergency status" + % msg + ) return common.bin2int(mb[53:56]) diff --git a/pyModeS/decoder/ehs.py b/pyModeS/decoder/ehs.py index bb91a29..8533f7f 100644 --- a/pyModeS/decoder/ehs.py +++ b/pyModeS/decoder/ehs.py @@ -30,6 +30,6 @@ def BDS(msg): def icao(msg): - from pyModeS.decoder.common import icao + from . import common - return icao(msg) + return common.icao(msg) diff --git a/pyModeS/extra/rtlreader.py b/pyModeS/extra/rtlreader.py index 5e55938..2d099a2 100644 --- a/pyModeS/extra/rtlreader.py +++ b/pyModeS/extra/rtlreader.py @@ -4,7 +4,7 @@ import numpy as np import pyModeS as pms try: - import rtlsdr + import rtlsdr # type: ignore except: print("------------------------------------------------------------------------") print("! Warning: pyrtlsdr not installed (required for using RTL-SDR devices) !") diff --git a/py.typed b/pyModeS/py.typed similarity index 100% rename from py.typed rename to pyModeS/py.typed