From a308b9a7e0cb0d4507bb5cdd1fd2c58387e5db77 Mon Sep 17 00:00:00 2001 From: TimA346 <63811111+TimA346@users.noreply.github.com> Date: Sat, 23 Jan 2021 03:05:48 +0100 Subject: [PATCH] implemented tc29 BDS6,2 --- pyModeS/decoder/__init__.py | 62 +++++ pyModeS/decoder/adsb.py | 17 ++ pyModeS/decoder/bds/__init__.py | 1 + pyModeS/decoder/bds/bds62.py | 474 ++++++++++++++++++++++++++++++++ 4 files changed, 554 insertions(+) create mode 100644 pyModeS/decoder/bds/bds62.py diff --git a/pyModeS/decoder/__init__.py b/pyModeS/decoder/__init__.py index c961ffc..8a1277c 100644 --- a/pyModeS/decoder/__init__.py +++ b/pyModeS/decoder/__init__.py @@ -71,6 +71,68 @@ def tell(msg: str) -> None: _print("CPR Longitude", cprlon) _print("Altitude", alt, "feet") + 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"} + tcas_operational_types = {0: "Not Operational", 1: "Operational"} + if subtype == 0: + emergency_types = { + 0: "No emergency", + 1: "General emergency", + 2: "Lifeguard/medical emergency", + 3: "Minimum fuel", + 4: "No communications", + 5: "Unlawful interference", + 6: "Downed aircraft", + 7: "Reserved" + } + vertical_horizontal_types = { + 1: "Acquiring mode", + 2: "Capturing/Maintaining mode" + } + tcas_ra_types = {0: "Not active", 1: "Active"} + alt, alt_source, alt_ref = adsb.target_altitude(msg) + angle, angle_type, angle_source = adsb.target_angle(msg) + vertical_mode = adsb.vertical_mode(msg) + horizontal_mode = adsb.horizontal_mode(msg) + tcas_ra = adsb.tcas_ra(msg) + emergency_status = adsb.emergency_status(msg) + _print("Target altitude", alt, "feet") + _print("Altitude source", alt_source) + _print("Altitude reference", alt_ref) + _print("Angle", angle, "°") + _print("Angle Type", angle_type) + _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 RA", tcas_ra_types[tcas_ra]) + _print("Emergency status", emergency_types[emergency_status]) + else: + alt, alt_source = adsb.selected_altitude(msg) + baro = adsb.baro_pressure_setting(msg) + hdg = adsb.selected_heading(msg) + autopilot = adsb.autopilot(msg) + vnav = adsb.vnav_mode(msg) + alt_hold = adsb.altitude_hold_mode(msg) + app = adsb.approach_mode(msg) + lnav = adsb.lnav_mode(msg) + _print("Selected altitude", alt, "feet") + _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 df == 20: _print("Protocol", "Mode-S Comm-B altitude reply") _print("Altitude", common.altcode(msg), "feet") diff --git a/pyModeS/decoder/adsb.py b/pyModeS/decoder/adsb.py index 1d65849..2a64faf 100644 --- a/pyModeS/decoder/adsb.py +++ b/pyModeS/decoder/adsb.py @@ -29,6 +29,23 @@ from pyModeS.decoder.bds.bds06 import ( from pyModeS.decoder.bds.bds08 import category, callsign from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff from pyModeS.decoder.bds.bds61 import is_emergency, emergency_state, emergency_squawk +from pyModeS.decoder.bds.bds62 import ( + selected_altitude, + selected_heading, + target_altitude, + target_angle, + tcas_operational, + tcas_ra, + baro_pressure_setting, + vertical_mode, + horizontal_mode, + vnav_mode, + lnav_mode, + autopilot, + altitude_hold_mode, + approach_mode, + emergency_status +) def df(msg): diff --git a/pyModeS/decoder/bds/__init__.py b/pyModeS/decoder/bds/__init__.py index 84f428e..509a568 100644 --- a/pyModeS/decoder/bds/__init__.py +++ b/pyModeS/decoder/bds/__init__.py @@ -38,6 +38,7 @@ from pyModeS.decoder.bds import ( bds50, bds53, bds60, + bds62 ) diff --git a/pyModeS/decoder/bds/bds62.py b/pyModeS/decoder/bds/bds62.py new file mode 100644 index 0000000..13acaa1 --- /dev/null +++ b/pyModeS/decoder/bds/bds62.py @@ -0,0 +1,474 @@ +# ------------------------------------------ +# BDS 6,2 +# ADS-B TC=29 +# Target State and Status +# ------------------------------------------ + +from pyModeS import common + +def selected_altitude(msg): + """Decode selected altitude. + + Args: + msg (str): 28 hexdigits string + + Returns: + int: Selected altitude (ft) + string: Source ('MCP/FCU' or 'FMS') + + """ + + if common.typecode(msg) != 29: + 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) + + alt = common.bin2int(mb[9:20]) + alt = None if alt == 0 else (alt - 1) * 32 + alt_source = "MCP/FCU" if int(mb[8]) == 0 else "FMS" + + return alt, alt_source + + + +def target_altitude(msg): + """Decode target altitude. + + Args: + msg (str): 28 hexdigits string + + Returns: + int: Target altitude (ft) + string: Source ('MCP/FCU', 'Holding mode' or 'FMS/RNAV') + string: Altitude reference, either pressure altitude or barometric corrected altitude ('FL' or 'MSL') + + """ + + if common.typecode(msg) != 29: + 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) + + alt_avail = common.bin2int(mb[7:9]) + if alt_avail == 0: + return None + elif alt_avail == 1: + alt_source = "MCP/FCU" + elif alt_avail == 2: + alt_source = "Holding mode" + else: + alt_source = "FMS/RNAV" + + alt_ref = "FL" if int(mb[9]) == 0 else "MSL" + + alt = -1000 + common.bin2int(mb[15:25]) * 100 + + return alt, alt_source, alt_ref + + +def vertical_mode(msg): + """Decode vertical mode. + + Value Meaning + ----- ----------------------- + 1 "Acquiring" mode + 2 "Capturing" or "Maintaining" mode + 3 Reserved + + Args: + msg (str): 28 hexdigits string + + Returns: + int: Vertical mode + + """ + + if common.typecode(msg) != 29: + 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) + + vertical_mode = common.bin2int(mb[13:15]) + if vertical_mode == 0: + return None + + return vertical_mode + + +def horizontal_mode(msg): + """Decode horizontal mode. + + Value Meaning + ----- ----------------------- + 1 "Acquiring" mode + 2 "Capturing" or "Maintaining" mode + 3 Reserved + + Args: + msg (str): 28 hexdigits string + + Returns: + int: Horizontal mode + + """ + + if common.typecode(msg) != 29: + 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) + + horizontal_mode = common.bin2int(mb[25:27]) + if horizontal_mode == 0: + return None + + return horizontal_mode + + +def selected_heading(msg): + """Decode selected heading. + + Args: + msg (str): 28 bytes hexadecimal message string + + Returns: + float: Selected heading (degree) + + """ + + if common.typecode(msg) != 29: + 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) + + 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 = round(hdg, 2) + + return hdg + + +def target_angle(msg): + """Decode target heading/track angle. + + Args: + msg (str): 28 bytes hexadecimal message string + + Returns: + 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) + + 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) + + angle_avail = common.bin2int(mb[25:27]) + if angle_avail == 0: + angle = None + else: + angle = common.bin2int(mb[27:36]) + + if angle_avail == 1: + angle_source = "MCP/FCU" + elif angle_avail == 2: + angle_source = "Autopilot mode" + else: + angle_source = "FMS/RNAV" + + angle_type = "Heading" if int(mb[36]) else "Track" + + return angle, angle_type, angle_source + + +def baro_pressure_setting(msg): + """Decode barometric pressure setting. + + Args: + msg (str): 28 hexdigits string + + Returns: + float: Barometric pressure setting (millibars) + + """ + + if common.typecode(msg) != 29: + 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) + + baro = common.bin2int(mb[20:29]) + baro = None if baro == 0 else 800 + (baro - 1) * 0.8 + baro = round(baro, 1) + + return baro + +def autopilot(msg) -> bool: + """Decode autopilot engagement. + + Args: + msg (str): 28 hexdigits string + + Returns: + bool: Autopilot engaged + + """ + + if common.typecode(msg) != 29: + 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) + + if int(mb[46]) == 0: + return None + + autopilot = True if int(mb[47]) == 1 else False + + return autopilot + +def vnav_mode(msg) -> bool: + """Decode VNAV mode. + + Args: + msg (str): 28 hexdigits string + + Returns: + bool: VNAV mode engaged + + """ + + if common.typecode(msg) != 29: + 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) + + if int(mb[46]) == 0: + return None + + vnav_mode = True if int(mb[48]) == 1 else False + + return vnav_mode + + +def altitude_hold_mode(msg) -> bool: + """Decode altitude hold mode. + + Args: + msg (str): 28 hexdigits string + + Returns: + bool: Altitude hold mode engaged + + """ + + if common.typecode(msg) != 29: + 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) + + if int(mb[46]) == 0: + return None + + alt_hold_mode = True if int(mb[49]) == 1 else False + + return alt_hold_mode + + + +def approach_mode(msg) -> bool: + """Decode approach mode. + + Args: + msg (str): 28 hexdigits string + + Returns: + bool: Approach mode engaged + + """ + + if common.typecode(msg) != 29: + 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) + + if int(mb[46]) == 0: + return None + + app_mode = True if int(mb[51]) == 1 else False + + return app_mode + + +def lnav_mode(msg) -> bool: + """Decode LNAV mode. + + Args: + msg (str): 28 hexdigits string + + Returns: + bool: LNAV mode engaged + + """ + + if common.typecode(msg) != 29: + 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) + + if int(mb[46]) == 0: + return None + + lnav_mode = True if int(mb[53]) == 1 else False + + return lnav_mode + + +def tcas_operational(msg) -> bool: + """Decode TCAS/ACAS operational. + + Args: + msg (str): 28 bytes hexadecimal message string + + Returns: + bool: TCAS/ACAS operational + + """ + + if common.typecode(msg) != 29: + 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: + tcas = True if int(mb[51]) == 0 else False + else: + tcas = True if int(mb[52]) == 1 else False + + return tcas + +def tcas_ra(msg) -> bool: + """Decode TCAS/ACAS Resolution advisory. + + Args: + msg (str): 28 bytes hexadecimal message string + + Returns: + bool: TCAS/ACAS Resolution advisory active + + """ + + if common.typecode(msg) != 29: + 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) + + tcas_ra = True if int(mb[52]) == 1 else False + + return tcas_ra + + +def emergency_status(msg) -> int: + """Decode aircraft emergency status. + + Value Meaning + ----- ----------------------- + 0 No emergency + 1 General emergency + 2 Lifeguard/medical emergency + 3 Minimum fuel + 4 No communications + 5 Unlawful interference + 6 Downed aircraft + 7 Reserved + + Args: + msg (str): 28 bytes hexadecimal message string + + Returns: + int: Emergency status + + """ + + if common.typecode(msg) != 29: + 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) + + return common.bin2int(mb[53:56])