Merge pull request #98 from TimA346/implement_tc29

Decoder for ADS-B TC=29 (target state and status message)
This commit is contained in:
Junzi Sun 2021-03-12 18:07:19 +01:00 committed by GitHub
commit 83e22892ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 568 additions and 0 deletions

View File

@ -71,6 +71,68 @@ def tell(msg: str) -> None:
_print("CPR Longitude", cprlon) _print("CPR Longitude", cprlon)
_print("Altitude", alt, "feet") _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: if df == 20:
_print("Protocol", "Mode-S Comm-B altitude reply") _print("Protocol", "Mode-S Comm-B altitude reply")
_print("Altitude", common.altcode(msg), "feet") _print("Altitude", common.altcode(msg), "feet")

View File

@ -29,6 +29,23 @@ from pyModeS.decoder.bds.bds06 import (
from pyModeS.decoder.bds.bds08 import category, callsign from pyModeS.decoder.bds.bds08 import category, callsign
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff 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.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): def df(msg):

View File

@ -38,6 +38,7 @@ from pyModeS.decoder.bds import (
bds50, bds50,
bds53, bds53,
bds60, bds60,
bds62
) )

View File

@ -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])

View File

@ -86,6 +86,20 @@ def test_adsb_emergency():
assert adsb.emergency_squawk("8DA2C1B6E112B600000000760759") == "6615" assert adsb.emergency_squawk("8DA2C1B6E112B600000000760759") == "6615"
def test_adsb_target_state_status():
sel_alt = adsb.selected_altitude("8DA05629EA21485CBF3F8CADAEEB")
assert sel_alt == (16992, "MCP/FCU")
assert adsb.baro_pressure_setting("8DA05629EA21485CBF3F8CADAEEB") == 1012.8
assert adsb.selected_heading("8DA05629EA21485CBF3F8CADAEEB")== 66.8
assert adsb.autopilot("8DA05629EA21485CBF3F8CADAEEB") == True
assert adsb.vnav_mode("8DA05629EA21485CBF3F8CADAEEB") == True
assert adsb.altitude_hold_mode("8DA05629EA21485CBF3F8CADAEEB") == False
assert adsb.approach_mode("8DA05629EA21485CBF3F8CADAEEB") == False
assert adsb.tcas_operational("8DA05629EA21485CBF3F8CADAEEB") == True
assert adsb.lnav_mode("8DA05629EA21485CBF3F8CADAEEB") == True
# def test_nic(): # def test_nic():
# assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0 # assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0
# assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1 # assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1