def callsign(msg): """Aircraft callsign Args: msg (string): 28 bytes hexadecimal message 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 surface_velocity(msg, rtn_sources=False): """Decode surface velocity from from a surface position message Args: msg (string): 28 bytes hexadecimal message string rtn_source (boolean): If the function will return the sources for direction of travel and vertical rate. This will change the return value from a four element array to a six element array. Returns: (int, float, int, string, string, None): speed (kt), ground track (degree), None for rate of climb/descend (ft/min), and speed type ('GS' for ground speed), direction source ('true_north' for ground track / true north as reference), None rate of climb/descent source. """ if common.typecode(msg) < 5 or common.typecode(msg) > 8: raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg) mb = common.hex2bin(msg)[32:] # ground track trk_status = int(mb[12]) if trk_status == 1: trk = common.bin2int(mb[13:20]) * 360.0 / 128.0 trk = round(trk, 1) else: trk = None # ground movement / speed mov = common.bin2int(mb[5:12]) if mov == 0 or mov > 124: spd = None elif mov == 1: spd = 0 elif mov == 124: spd = 175 else: movs = [2, 9, 13, 39, 94, 109, 124] kts = [0.125, 1, 2, 15, 70, 100, 175] i = next(m[0] for m in enumerate(movs) if m[1] > mov) step = (kts[i] - kts[i - 1]) * 1.0 / (movs[i] - movs[i - 1]) spd = kts[i - 1] + (mov - movs[i - 1]) * step spd = round(spd, 2) if rtn_sources: return spd, trk, 0, "GS", "true_north", None else: return spd, trk, 0, "GS"
def category(msg): """Aircraft category number Args: msg (string): 28 bytes hexadecimal message string Returns: int: category number """ if common.typecode(msg) < 1 or common.typecode(msg) > 4: raise RuntimeError("%s: Not a identification message" % msg) msgbin = common.hex2bin(msg) return common.bin2int(msgbin[5:8])
def altitude(msg): """Decode aircraft altitude Args: msg (string): 28 bytes hexadecimal message 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:] if tc < 19: # barometric altitude q = mb[15] if q: n = common.bin2int(mb[8:15] + mb[16:20]) alt = n * 25 - 1000 else: alt = None else: # GNSS altitude, meters -> feet alt = common.bin2int(mb[8:20]) * 3.28084 return alt
def infer(msg): """Estimate the most likely BDS code of an message Args: msg (String): 28 bytes hexadecimal message string Returns: String or None: BDS version, or possible versions, or None if nothing matches. """ df = common.df(msg) if common.allzeros(msg): return 'EMPTY' # For ADS-B / Mode-S extended squitter if df == 17: tc = common.typecode(msg) if 1 <= tc <= 4: return 'BDS08' # indentification and category if 5 <= tc <= 8: return 'BDS06' # surface movement if 9 <= tc <= 18: return 'BDS05' # airborne position, baro-alt if tc == 19: return 'BDS09' # airborne velocity if 20 <= tc <= 22: return 'BDS05' # airborne position, gnss-alt if tc == 28: return 'BDS61' # aircraft status if tc == 29: return 'BDS62' # target state and status if tc == 31: return 'BDS65' # operational status # For Comm-B replies, ELS + EHS only IS10 = bds10.is10(msg) IS17 = bds17.is17(msg) IS20 = bds20.is20(msg) IS30 = bds30.is30(msg) IS40 = bds40.is40(msg) IS50 = bds50.is50(msg) IS60 = bds60.is60(msg) allbds = np.array( ["BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS50", "BDS60"]) mask = [IS10, IS17, IS20, IS30, IS40, IS50, IS60] bds = ','.join(sorted(allbds[mask])) if len(bds) == 0: return None else: return bds
def surface_velocity(msg): """Decode surface velocity from from a surface position message Args: msg (string): 28 bytes hexadecimal message string Returns: (int, float, int, string): speed (kt), ground track (degree), rate of climb/descend (ft/min), and speed type ('GS' for ground speed, 'AS' for airspeed) """ if common.typecode(msg) < 5 or common.typecode(msg) > 8: raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg) mb = common.hex2bin(msg)[32:] # ground track trk_status = int(mb[12]) if trk_status == 1: trk = common.bin2int(mb[13:20]) * 360.0 / 128.0 trk = round(trk, 1) else: trk = None # ground movment / speed mov = common.bin2int(mb[5:12]) if mov == 0 or mov > 124: spd = None elif mov == 1: spd = 0 elif mov == 124: spd = 175 else: movs = [2, 9, 13, 39, 94, 109, 124] kts = [0.125, 1, 2, 15, 70, 100, 175] i = next(m[0] for m in enumerate(movs) if m[1] > mov) step = (kts[i] - kts[i - 1]) * 1.0 / (movs[i] - movs[i - 1]) spd = kts[i - 1] + (mov - movs[i - 1]) * step spd = round(spd, 2) return spd, trk, 0, 'GS'
def altitude_diff(msg): """Decode the differece between GNSS and barometric altitude Args: msg (string): 28 bytes hexadecimal message string, TC=19 Returns: int: Altitude difference in ft. 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 typecode(msg): return common.typecode(msg)
def tell(msg): def _print(label, value, unit=None): print("%20s: " % label, end="") print("%s " % value, end="") if unit: print(unit) else: print() df = common.df(msg) icao = common.icao(msg) _print("Message", msg) _print("ICAO address", icao) _print("Downlink Format", df) if df == 17: _print("Protocol", "Mode-S Extended Squitter (ADS-B)") tc = common.typecode(msg) if 1 <= tc <= 4: # callsign callsign = adsb.callsign(msg) _print("Type", "Identitification and category") _print("Callsign:", callsign) if 5 <= tc <= 8: # surface position _print("Type", "Surface position") oe = adsb.oe_flag(msg) msgbin = common.hex2bin(msg) cprlat = common.bin2int(msgbin[54:71]) / 131072.0 cprlon = common.bin2int(msgbin[71:88]) / 131072.0 v = adsb.surface_velocity(msg) _print("CPR format", "Odd" if oe else "Even") _print("CPR Latitude", cprlat) _print("CPR Longitude", cprlon) _print("Speed", v[0], "knots") _print("Track", v[1], "degrees") if 9 <= tc <= 18: # airborne position _print("Type", "Airborne position (with barometric altitude)") alt = adsb.altitude(msg) oe = adsb.oe_flag(msg) msgbin = common.hex2bin(msg) cprlat = common.bin2int(msgbin[54:71]) / 131072.0 cprlon = common.bin2int(msgbin[71:88]) / 131072.0 _print("CPR format", "Odd" if oe else "Even") _print("CPR Latitude", cprlat) _print("CPR Longitude", cprlon) _print("Altitude", alt, "feet") if tc == 19: _print("Type", "Airborne velocity") spd, trk, vr, t = adsb.velocity(msg) types = {"GS": "Ground speed", "TAS": "True airspeed"} _print("Speed", spd, "knots") _print("Track", trk, "degrees") _print("Vertical rate", vr, "feet/minute") _print("Type", types[t]) if 20 <= tc <= 22: # airborne position _print("Type", "Airborne position (with GNSS altitude)") alt = adsb.altitude(msg) oe = adsb.oe_flag(msg) msgbin = common.hex2bin(msg) cprlat = common.bin2int(msgbin[54:71]) / 131072.0 cprlon = common.bin2int(msgbin[71:88]) / 131072.0 _print("CPR format", "Odd" if oe else "Even") _print("CPR Latitude", cprlat) _print("CPR Longitude", cprlon) _print("Altitude", alt, "feet") if df == 20: _print("Protocol", "Mode-S Comm-B altitude reply") _print("Altitude", common.altcode(msg), "feet") if df == 21: _print("Protocol", "Mode-S Comm-B identity reply") _print("Squawk code", common.idcode(msg)) if df == 20 or df == 21: labels = { "BDS10": "Data link capability", "BDS17": "GICB capability", "BDS20": "Aircraft identification", "BDS30": "ACAS resolution", "BDS40": "Vertical intention report", "BDS50": "Track and turn report", "BDS60": "Heading and speed report", "BDS44": "Meteorological routine air report", "BDS45": "Meteorological hazard report", "EMPTY": "[No information available]", } BDS = bds.infer(msg, mrar=True) if BDS in labels.keys(): _print("BDS", "%s (%s)" % (BDS, labels[BDS])) else: _print("BDS", BDS) if BDS == "BDS20": callsign = commb.cs20(msg) _print("Callsign", callsign) if BDS == "BDS40": _print("MCP target alt", commb.selalt40mcp(msg), "feet") _print("FMS Target alt", commb.selalt40fms(msg), "feet") _print("Pressure", commb.p40baro(msg), "millibar") if BDS == "BDS50": _print("Roll angle", commb.roll50(msg), "degrees") _print("Track angle", commb.trk50(msg), "degrees") _print("Track rate", commb.rtrk50(msg), "degree/second") _print("Ground speed", commb.gs50(msg), "knots") _print("True airspeed", commb.tas50(msg), "knots") if BDS == "BDS60": _print("Megnatic Heading", commb.hdg60(msg), "degrees") _print("Indicated airspeed", commb.ias60(msg), "knots") _print("Mach number", commb.mach60(msg)) _print("Vertical rate (Baro)", commb.vr60baro(msg), "feet/minute") _print("Vertical rate (INS)", commb.vr60ins(msg), "feet/minute") if BDS == "BDS44": _print("Wind speed", commb.wind44(msg)[0], "knots") _print("Wind direction", commb.wind44(msg)[1], "degrees") _print("Temperature 1", commb.temp44(msg)[0], "Celsius") _print("Temperature 2", commb.temp44(msg)[1], "Celsius") _print("Pressure", commb.p44(msg), "hPa") _print("Humidity", commb.hum44(msg), "%") _print("Turbulence", commb.turb44(msg)) if BDS == "BDS45": _print("Turbulence", commb.turb45(msg)) _print("Wind shear", commb.ws45(msg)) _print("Microbust", commb.mb45(msg)) _print("Icing", commb.ic45(msg)) _print("Wake vortex", commb.wv45(msg)) _print("Temperature", commb.temp45(msg), "Celsius") _print("Pressure", commb.p45(msg), "hPa") _print("Radio height", commb.rh45(msg), "feet")
def airborne_velocity(msg, rtn_sources=False): """Calculate the speed, track (or heading), and vertical rate Args: msg (string): 28 bytes hexadecimal message string rtn_source (boolean): If the function will return the sources for direction of travel and vertical rate. This will change the return value from a four element array to a six element array. Returns: (int, float, int, string, string, string): speed (kt), ground track or heading (degree), rate of climb/descent (ft/min), speed type ('GS' for ground speed, 'AS' for airspeed), direction source ('true_north' for ground track / true north as refrence, 'mag_north' for magnetic north as reference), rate of climb/descent source ('Baro' for barometer, 'GNSS' for GNSS constellation). """ if common.typecode(msg) != 19: raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:8]) if common.bin2int(mb[14:24]) == 0 or common.bin2int(mb[25:35]) == 0: return None if subtype in (1, 2): v_ew_sign = -1 if mb[13] == "1" else 1 v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity if subtype == 2: # Supersonic v_ew *= 4 v_ns_sign = -1 if mb[24] == "1" else 1 v_ns = common.bin2int(mb[25:35]) - 1 # north-south velocity if subtype == 2: # Supersonic v_ns *= 4 v_we = v_ew_sign * v_ew v_sn = v_ns_sign * v_ns spd = math.sqrt(v_sn * v_sn + v_we * v_we) # unit in kts spd = int(spd) trk = math.atan2(v_we, v_sn) trk = math.degrees(trk) # convert to degrees trk = trk if trk >= 0 else trk + 360 # no negative val tag = "GS" trk_or_hdg = round(trk, 2) dir_type = "true_north" else: if mb[13] == "0": hdg = None else: hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0 hdg = round(hdg, 2) trk_or_hdg = hdg spd = common.bin2int(mb[25:35]) spd = None if spd == 0 else spd - 1 if subtype == 4: # Supersonic spd *= 4 if mb[24] == "0": tag = "IAS" else: tag = "TAS" dir_type = "mag_north" vr_source = "GNSS" if mb[35] == "0" else "Baro" vr_sign = -1 if mb[36] == "1" else 1 vr = common.bin2int(mb[37:46]) rocd = None if vr == 0 else int(vr_sign * (vr - 1) * 64) if rtn_sources: return spd, trk_or_hdg, rocd, tag, dir_type, vr_source else: return spd, trk_or_hdg, rocd, tag
def airborne_velocity(msg): """Calculate the speed, track (or heading), and vertical rate Args: msg (string): 28 bytes hexadecimal message string Returns: (int, float, int, string): speed (kt), ground track or heading (degree), rate of climb/descend (ft/min), and speed type ('GS' for ground speed, 'AS' for airspeed) """ if common.typecode(msg) != 19: raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg) mb = common.hex2bin(msg)[32:] subtype = common.bin2int(mb[5:8]) if common.bin2int(mb[14:24]) == 0 or common.bin2int(mb[25:35]) == 0: return None if subtype in (1, 2): v_ew_sign = -1 if mb[13]=='1' else 1 v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity v_ns_sign = -1 if mb[24]=='1' else 1 v_ns = common.bin2int(mb[25:35]) - 1 # north-south velocity v_we = v_ew_sign * v_ew v_sn = v_ns_sign * v_ns spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts spd = int(spd) trk = math.atan2(v_we, v_sn) trk = math.degrees(trk) # convert to degrees trk = trk if trk >= 0 else trk + 360 # no negative val tag = 'GS' trk_or_hdg = round(trk, 2) else: if mb[13] == '0': hdg = None else: hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0 hdg = round(hdg, 2) trk_or_hdg = hdg spd = common.bin2int(mb[25:35]) spd = None if spd==0 else spd-1 if mb[24]=='0': tag = 'IAS' else: tag = 'TAS' vr_sign = -1 if mb[36]=='1' else 1 vr = common.bin2int(mb[37:46]) rocd = None if vr==0 else int(vr_sign*(vr-1)*64) return spd, trk_or_hdg, rocd, tag
def infer(msg, mrar=False): """Estimate the most likely BDS code of an message. Args: msg (String): 28 bytes hexadecimal message string mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). Defaults to False. Returns: String or None: BDS version, or possible versions, or None if nothing matches. """ df = common.df(msg) if common.allzeros(msg): return "EMPTY" # For ADS-B / Mode-S extended squitter if df == 17: tc = common.typecode(msg) if 1 <= tc <= 4: return "BDS08" # identification and category if 5 <= tc <= 8: return "BDS06" # surface movement if 9 <= tc <= 18: return "BDS05" # airborne position, baro-alt if tc == 19: return "BDS09" # airborne velocity if 20 <= tc <= 22: return "BDS05" # airborne position, gnss-alt if tc == 28: return "BDS61" # aircraft status if tc == 29: return "BDS62" # target state and status if tc == 31: return "BDS65" # operational status # For Comm-B replies IS10 = bds10.is10(msg) IS17 = bds17.is17(msg) IS20 = bds20.is20(msg) IS30 = bds30.is30(msg) IS40 = bds40.is40(msg) IS50 = bds50.is50(msg) IS60 = bds60.is60(msg) IS44 = bds44.is44(msg) IS45 = bds45.is45(msg) if mrar: allbds = np.array([ "BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS44", "BDS45", "BDS50", "BDS60", ]) mask = [IS10, IS17, IS20, IS30, IS40, IS44, IS45, IS50, IS60] else: allbds = np.array( ["BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS50", "BDS60"]) mask = [IS10, IS17, IS20, IS30, IS40, IS50, IS60] bds = ",".join(sorted(allbds[mask])) if len(bds) == 0: return None else: return bds