def is20(msg): """Check if a message is likely to be BDS code 2,0 Args: msg (str): 28 hexdigits string Returns: bool: True or False """ if common.allzeros(msg): return False d = common.hex2bin(common.data(msg)) if d[0:8] != "00100000": return False # allow empty callsign if common.bin2int(d[8:56]) == 0 return True if "#" in cs20(msg): return False return True
def nac_v(msg): """Calculate NACv, Navigation Accuracy Category - Velocity Args: msg (str): 28 hexdigits string, TC = 19 Returns: int or string: 95% horizontal accuracy bounds for velocity, Horizontal Figure of Merit int or string: 95% vertical accuracy bounds for velocity, Vertical Figure of Merit """ tc = typecode(msg) if tc != 19: raise RuntimeError( "%s: Not an airborne velocity message, expecting TC = 19" % msg) msgbin = common.hex2bin(msg) NACv = common.bin2int(msgbin[42:45]) try: HFOMr = uncertainty.NACv[NACv]["HFOMr"] VFOMr = uncertainty.NACv[NACv]["VFOMr"] except KeyError: HFOMr, VFOMr = uncertainty.NA, uncertainty.NA return HFOMr, VFOMr
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 fs(msg): """Decode flight status. Args: msg (str): 14 hexdigits string Returns: int, str: flight status, description """ msgbin = common.hex2bin(msg) fs = common.bin2int(msgbin[5:8]) text = None if fs == 0: text = "no alert, no SPI, aircraft is airborne" elif fs == 1: text = "no alert, no SPI, aircraft is on-ground" elif fs == 2: text = "alert, no SPI, aircraft is airborne" elif fs == 3: text = "alert, no SPI, aircraft is on-ground" elif fs == 4: text = "alert, SPI, aircraft is airborne or on-ground" elif fs == 5: text = "no alert, SPI, aircraft is airborne or on-ground" return fs, text
def nuc_v(msg): """Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1) Args: msg (str): 28 hexdigits string, Returns: int or string: 95% Horizontal Velocity Error int or string: 95% Vertical Velocity Error """ tc = typecode(msg) if tc != 19: raise RuntimeError( "%s: Not an airborne velocity message, expecting TC = 19" % msg) msgbin = common.hex2bin(msg) NUCv = common.bin2int(msgbin[42:45]) try: HVE = uncertainty.NUCv[NUCv]["HVE"] VVE = uncertainty.NUCv[NUCv]["VVE"] except KeyError: HVE, VVE = uncertainty.NA, uncertainty.NA return HVE, VVE
def altitude(msg): """Decode aircraft altitude Args: msg (str): 28 hexdigits string Returns: int: altitude in feet """ tc = common.typecode(msg) if tc < 9 or tc == 19 or tc > 22: raise RuntimeError("%s: Not a airborn position message" % msg) mb = common.hex2bin(msg)[32:] altbin = mb[8:20] if tc < 19: altcode = altbin[0:6] + "0" + altbin[6:] alt = common.altitude(altcode) else: alt = common.bin2int(altbin) * 3.28084 return alt
def callsign(msg): """Aircraft callsign Args: msg (str): 28 hexdigits string Returns: string: callsign """ if common.typecode(msg) < 1 or common.typecode(msg) > 4: raise RuntimeError("%s: Not a identification message" % msg) chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######" msgbin = common.hex2bin(msg) csbin = msgbin[40:96] cs = "" cs += chars[common.bin2int(csbin[0:6])] cs += chars[common.bin2int(csbin[6:12])] cs += chars[common.bin2int(csbin[12:18])] cs += chars[common.bin2int(csbin[18:24])] cs += chars[common.bin2int(csbin[24:30])] cs += chars[common.bin2int(csbin[30:36])] cs += chars[common.bin2int(csbin[36:42])] cs += chars[common.bin2int(csbin[42:48])] # clean string, remove spaces and marks, if any. # cs = cs.replace('_', '') cs = cs.replace("#", "") return cs
def emergency_state(msg: str) -> int: """Decode aircraft emergency state. Value Meaning ----- ----------------------- 0 No emergency 1 General emergency 2 Lifeguard/Medical 3 Minimum fuel 4 No communications 5 Unlawful communications 6-7 Reserved :param msg: 28 bytes hexadecimal message string :return: emergency state """ mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:8]) if subtype == 2: raise RuntimeError("%s: Emergency message is ACAS-RA, not implemented") emergency_state = common.bin2int(mb[8:11]) return emergency_state
def is_emergency(msg: str) -> bool: """Check if the aircraft is reporting an emergency. Non-emergencies are either a subtype of zero (no information) or subtype of one and a value of zero (no emergency). Subtype = 2 indicates an ACAS RA broadcast, look in BDS 3,0 :param msg: 28 bytes hexadecimal message string :return: if the aircraft has declared an emergency """ if common.typecode(msg) != 28: raise RuntimeError( "%s: Not an airborne status message, expecting TC=28" % msg) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:8]) if subtype == 2: raise RuntimeError("%s: Emergency message is ACAS-RA, not implemented") emergency_state = common.bin2int(mb[8:11]) if subtype == 1 and emergency_state == 1: return True else: return False
def altitude_diff(msg): """Decode the difference between GNSS and barometric altitude. Args: msg (str): 28 hexdigits string, TC=19 Returns: int: Altitude difference in feet. Negative value indicates GNSS altitude below barometric altitude. """ tc = common.typecode(msg) if tc != 19: raise RuntimeError( "%s: Not a airborne velocity message, expecting TC=19" % msg) msgbin = common.hex2bin(msg) sign = -1 if int(msgbin[80]) else 1 value = common.bin2int(msgbin[81:88]) if value == 0 or value == 127: return None else: return sign * (value - 1) * 25 # in ft.
def trk50(msg): """True track angle, BDS 5,0 message Args: msg (str): 28 hexdigits string Returns: float: angle in degrees to true north (from 0 to 360) """ d = common.hex2bin(common.data(msg)) if d[11] == "0": return None sign = int(d[12]) # 1 -> west value = common.bin2int(d[13:23]) if sign: value = value - 1024 trk = value * 90 / 512.0 # convert from [-180, 180] to [0, 360] if trk < 0: trk = 360 + trk return round(trk, 3)
def is30(msg): """Check if a message is likely to be BDS code 2,0 Args: msg (str): 28 hexdigits string Returns: bool: True or False """ if common.allzeros(msg): return False d = common.hex2bin(common.data(msg)) if d[0:8] != "00110000": return False # threat type 3 not assigned if d[28:30] == "11": return False # reserved for ACAS III, in far future if common.bin2int(d[15:22]) >= 48: return False return True
def temp44(msg): """Static air temperature. Args: msg (str): 28 hexdigits string Returns: float, float: temperature and alternative temperature in Celsius degree. Note: Two values returns due to what seems to be an inconsistency error in ICAO 9871 (2008) Appendix A-67. """ d = common.hex2bin(common.data(msg)) sign = int(d[23]) value = common.bin2int(d[24:34]) if sign: value = value - 1024 temp = value * 0.25 # celsius temp = round(temp, 2) temp_alternative = value * 0.125 # celsius temp_alternative = round(temp_alternative, 3) return temp, temp_alternative
def capability(msg): """Decode transponder capability. Args: msg (str): 14 hexdigits string Returns: int, str: transponder capability, description """ msgbin = common.hex2bin(msg) ca = common.bin2int(msgbin[5:8]) if ca == 0: text = "level 1 transponder" elif ca == 4: text = "level 2 transponder, ability to set CA to 7, on ground" elif ca == 5: text = "level 2 transponder, ability to set CA to 7, airborne" elif ca == 6: text = "evel 2 transponder, ability to set CA to 7, either airborne or ground" elif ca == 7: text = "Downlink Request value is 0,or the Flight Status is 2, 3, 4 or 5, either airborne or on the ground" else: text = None return ca, text
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 is10(msg): """Check if a message is likely to be BDS code 1,0 Args: msg (str): 28 hexdigits string Returns: bool: True or False """ if common.allzeros(msg): return False d = common.hex2bin(common.data(msg)) # first 8 bits must be 0x10 if d[0:8] != "00010000": return False # bit 10 to 14 are reserved if common.bin2int(d[9:14]) != 0: return False # overlay capability conflict if d[14] == "1" and common.bin2int(d[16:23]) < 5: return False if d[14] == "0" and common.bin2int(d[16:23]) > 4: return False return True
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 is17(msg): """Check if a message is likely to be BDS code 1,7 Args: msg (str): 28 hexdigits string Returns: bool: True or False """ if common.allzeros(msg): return False d = common.hex2bin(common.data(msg)) if common.bin2int(d[24:56]) != 0: return False caps = cap17(msg) # basic BDS codes for ADS-B shall be supported # assuming ADS-B out is installed (2017EU/2020US mandate) # if not set(['BDS05', 'BDS06', 'BDS08', 'BDS09', 'BDS20']).issubset(caps): # return False # at least you can respond who you are if "BDS20" not in caps: return False return True
def hdg53(msg): """Magnetic heading, BDS 5,3 message Args: msg (str): 28 hexdigits string Returns: float: angle in degrees to true north (from 0 to 360) """ d = common.hex2bin(common.data(msg)) if d[0] == "0": return None sign = int(d[1]) # 1 -> west value = common.bin2int(d[2:12]) if sign: value = value - 1024 hdg = value * 90.0 / 512.0 # degree # convert from [-180, 180] to [0, 360] if hdg < 0: hdg = 360 + hdg return round(hdg, 3)
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 hdg60(msg): """Megnetic heading of aircraft Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: float: heading in degrees to megnetic north (from 0 to 360) """ d = common.hex2bin(common.data(msg)) if d[0] == "0": return None sign = int(d[1]) # 1 -> west value = common.bin2int(d[2:12]) if sign: value = value - 1024 hdg = value * 90 / 512.0 # degree # convert from [-180, 180] to [0, 360] if hdg < 0: hdg = 360 + hdg return round(hdg, 3)
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 dr(msg): """Decode downlink request. Args: msg (str): 14 hexdigits string Returns: int, str: downlink request, description """ msgbin = common.hex2bin(msg) dr = common.bin2int(msgbin[8:13]) text = None if dr == 0: text = "no downlink request" elif dr == 1: text = "request to send Comm-B message" elif dr == 4: text = "Comm-B broadcast 1 available" elif dr == 5: text = "Comm-B broadcast 2 available" elif dr >= 16: text = "ELM downlink segments available: {}".format(dr - 15) return dr, text
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 nac_p(msg): """Calculate NACp, Navigation Accuracy Category - Position Args: msg (str): 28 hexdigits string, TC = 29 or 31 Returns: int or string: 95% horizontal accuracy bounds, Estimated Position Uncertainty int or string: 95% vertical accuracy bounds, Vertical Estimated Position Uncertainty """ tc = typecode(msg) if tc not in [29, 31]: raise RuntimeError("%s: Not a target state and status message, \ or operation status message, expecting TC = 29 or 31" % msg) msgbin = common.hex2bin(msg) if tc == 29: NACp = common.bin2int(msgbin[71:75]) elif tc == 31: NACp = common.bin2int(msgbin[76:80]) try: EPU = uncertainty.NACp[NACp]["EPU"] VEPU = uncertainty.NACp[NACp]["VEPU"] except KeyError: EPU, VEPU = uncertainty.NA, uncertainty.NA return EPU, VEPU
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])
def bds(msg): """Decode requested BDS register from selective (Roll Call) interrogation.""" UF = uf(msg) msgbin = common.hex2bin(msg) msgbin_split = wrap(msgbin, 8) mbytes = list(map(common.bin2int, msgbin_split)) if uf(msg) in {4, 5, 20, 21}: di = mbytes[1] & 0x7 # DI - Designator Identification RR = mbytes[1] >> 3 & 0x1F if RR > 15: BDS1 = RR - 16 if di == 7: RRS = mbytes[2] & 0x0F BDS2 = RRS elif di == 3: RRS = ((mbytes[2] & 0x1) << 4) | ((mbytes[3] & 0xE0) >> 5) BDS2 = RRS else: BDS2 = 0 # for other values of DI, the BDS2 is assumed 0 (as per ICAO Annex 10 Vol IV) return str(BDS1) + str(BDS2) else: return None else: return None
def oe_flag(msg): """Check the odd/even flag. Bit 54, 0 for even, 1 for odd. Args: msg (str): 28 hexdigits string Returns: int: 0 or 1, for even or odd frame """ msgbin = common.hex2bin(msg) return int(msgbin[53])
def is60(msg): """Check if a message is likely to be BDS code 6,0 Args: msg (str): 28 hexdigits string Returns: bool: True or False """ if common.allzeros(msg): return False d = common.hex2bin(common.data(msg)) # status bit 1, 13, 24, 35, 46 if common.wrongstatus(d, 1, 2, 12): return False if common.wrongstatus(d, 13, 14, 23): return False if common.wrongstatus(d, 24, 25, 34): return False if common.wrongstatus(d, 35, 36, 45): return False if common.wrongstatus(d, 46, 47, 56): return False ias = ias60(msg) if ias is not None and ias > 500: return False mach = mach60(msg) if mach is not None and mach > 1: return False vr_baro = vr60baro(msg) if vr_baro is not None and abs(vr_baro) > 6000: return False vr_ins = vr60ins(msg) if vr_ins is not None and abs(vr_ins) > 6000: return False # additional check knowing altitude if (mach is not None) and (ias is not None) and (common.df(msg) == 20): alt = common.altcode(msg) if alt is not None: ias_ = aero.mach2cas(mach, alt * aero.ft) / aero.kts if abs(ias - ias_) > 20: return False return True
def oe_flag(msg): """Check the odd/even flag. Bit 54, 0 for even, 1 for odd. Args: msg (string): 28 bytes hexadecimal message string Returns: int: 0 or 1, for even or odd frame """ msgbin = common.hex2bin(msg) return int(msgbin[53])