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, source=False): """Decode surface velocity from a surface position message Args: msg (str): 28 hexdigits string source (boolean): Include direction and vertical rate sources in return. Default to False. If set to True, the function will return six value instead of four. Returns: int, float, int, string, [string], [string]: Four or six parameters, including: - Speed (kt) - Angle (degree), ground track - Vertical rate, always 0 - Speed type ('GS' for ground speed, 'AS' for airspeed) - [Optional] Direction source ('TRUE_NORTH') - [Optional] Vertical rate source (None) """ 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 source: return spd, trk, 0, "GS", "TRUE_NORTH", None else: return spd, trk, 0, "GS"
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 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 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 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 if baro is not None: baro = round(baro, 1) return baro
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 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 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 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:] 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 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:] else: altcode = altbin[0:6] + "0" + altbin[6:] alt = common.altitude(altcode) return alt
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 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) mebin = msgbin[32:87] return common.bin2int(mebin[5:8])
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 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 emergency_squawk(msg: str) -> str: """Decode squawk code. Emergency value 1: squawk 7700. Emergency value 4: squawk 7600. Emergency value 5: squawk 7500. :param msg: 28 bytes hexadecimal message string :return: aircraft squawk code """ if common.typecode(msg) != 28: raise RuntimeError( "%s: Not an airborne status message, expecting TC=28" % msg) msgbin = common.hex2bin(msg) # construct the 13 bits Mode A ID code idcode = msgbin[43:49] + "0" + msgbin[49:55] squawk = common.squawk(idcode) return squawk
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 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 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 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 typecode(msg): return common.typecode(msg)
def infer(msg, mrar=False): """Estimate the most likely BDS code of an message. Args: msg (str): 28 hexdigits 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
def tell(msg: str) -> None: from pyModeS import common, adsb, commb, bds 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 message(self, msg): # Printout of statistics if self.signal_hup == 1: self.logstats() self.signal_hup = 0 ret_dict = {} ret_dict['ret'] = 0 ret_dict['type'] = "" self.msgs_curr_total = self.msgs_curr_total + 1 if len(msg) == 26 or len(msg) == 40: # Some version of dump1090 have the 12 first characters used w/ # some date (timestamp ?). E.g. sdbr245 feeding flightradar24. # Strip 12 first characters. msg = msg[12:] if len(msg) < 28: # Message length 112 bits self.msgs_curr_short = self.msgs_curr_short + 1 else: self.msgs_curr_len28 = self.msgs_curr_len28 + 1 ret_dict['crc'] = self.check_msg(msg) if ret_dict['crc']: self.parity_check_ok = self.parity_check_ok + 1 else: self.parity_check_ko = self.parity_check_ko + 1 # Do not manage messages with bad CRC if ret_dict['crc'] is not True: raise ValueError("CrcKO") dfmt = common.df(msg) ret_dict['dfmt'] = dfmt self.df[dfmt] = self.df[dfmt] + 1 ret_dict['ic'] = common.icao(msg) if dfmt in [17, 18]: # Downlink format 17 or 18 tc = common.typecode(msg) ret_dict['tc'] = tc self.tc[tc] = self.tc[tc] + 1 lat_ref = float(self.params["lat"]) long_ref = float(self.params["long"]) if tc == 4: # Aircraft identification self.msgs_discovered = self.msgs_discovered + 1 ret_dict['type'] = "CS" ret_dict['cs'] = adsb.callsign(msg) ca = adsb_ca(msg) ret_dict['ca'] = ca_msg[ca] self.ca[ca] = self.ca[ca] + 1 elif 9 <= tc <= 18: self.msgs_discovered = self.msgs_discovered + 1 ret_dict['type'] = "LB" ret_dict['altb'] = adsb.altitude(msg) (lat, long) = adsb.position_with_ref(msg, lat_ref, long_ref) ret_dict['lat'] = lat ret_dict['long'] = long elif tc == 19: self.msgs_discovered = self.msgs_discovered + 1 ret_dict['type'] = "VH" _dict = adsb.velocity(msg) if _dict is None: raise ValueError("AdsbVelocity") (ret_dict['speed'], ret_dict['head'], ret_dict['rocd'], var) = _dict if ret_dict['head'] is None: raise ValueError("AdsbHeading") if ret_dict['rocd'] is None: raise ValueError("AdsbRocd") elif 20 <= tc <= 22: self.msgs_discovered = self.msgs_discovered + 1 ret_dict['type'] = "LG" ret_dict['altg'] = adsb.altitude(msg) (lat, long) = adsb.position_with_ref(msg, lat_ref, long_ref) ret_dict['lat'] = lat ret_dict['long'] = long elif dfmt in [5, 21]: self.msgs_discovered = self.msgs_discovered + 1 ret_dict['type'] = "SQ" ret_dict['sq'] = common.idcode(msg) if dfmt in [0, 4, 16, 20]: self.msgs_discovered = self.msgs_discovered + 1 ret_dict['type'] = "AL" _alt = common.altcode(msg) alt = _alt if _alt is not None else 0 ret_dict['alt'] = alt return ret_dict
def tell(msg: str) -> None: from pyModeS import common, adsb, commb, bds 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", "Identification 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 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") 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, source=False): """Decode airborne velocity. Args: msg (str): 28 hexdigits string source (boolean): Include direction and vertical rate sources in return. Default to False. If set to True, the function will return six value instead of four. Returns: int, float, int, string, [string], [string]: Four or six parameters, including: - Speed (kt) - Angle (degree), either ground track or heading - Vertical rate (ft/min) - Speed type ('GS' for ground speed, 'AS' for airspeed) - [Optional] Direction source ('TRUE_NORTH' or 'MAGNETIC_NORTH') - [Optional] Vertical rate source ('BARO' or 'GNSS') """ 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 subtype in (1, 2): v_ew = common.bin2int(mb[14:24]) v_ns = common.bin2int(mb[25:35]) if v_ew == 0 or v_ns == 0: spd = None trk_or_hdg = None vs = None else: v_ew_sign = -1 if mb[13] == "1" else 1 v_ew = v_ew - 1 # east-west velocity if subtype == 2: # Supersonic v_ew *= 4 v_ns_sign = -1 if mb[24] == "1" else 1 v_ns = v_ns - 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 spd_type = "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 * 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": spd_type = "IAS" else: spd_type = "TAS" dir_type = "MAGNETIC_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]) vs = None if vr == 0 else int(vr_sign * (vr - 1) * 64) if source: return spd, trk_or_hdg, vs, spd_type, dir_type, vr_source else: return spd, trk_or_hdg, vs, spd_type
def get_all(msg: str) -> dict: from pyModeS import common, adsb, commb, bds _dict = {} def push(key, data, unit=None): _dict[key] = data df = common.df(msg) icao = common.icao(msg) push("message", msg) push("icao", icao) push("downlink_format", df) if df == 17: push("protocol", "Mode-S Extended Squitter (ADS-B)") tc = common.typecode(msg) if 1 <= tc <= 4: # callsign callsign = adsb.callsign(msg) push("type", "Identitification and category") push("callsign:", callsign) if 5 <= tc <= 8: # surface position push("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) push("cpr_format", "Odd" if oe else "Even") push("cpr_latitude", cprlat) push("cpr_longitude", cprlon) push("speed", v[0] * 1.85200, "km") push("track", v[1], "degrees") if 9 <= tc <= 18: # airborne position push("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 push("cpr_format", "Odd" if oe else "Even") push("cpr_latitude", cprlat) push("cpr_longitude", cprlon) push("altitude", alt, "feet") if tc == 19: push("type", "Airborne velocity") spd, trk, vr, t = adsb.velocity(msg) types = {"GS": "Ground speed", "TAS": "True airspeed"} push("speed", spd * 1.85200, "km") push("track", trk, "degrees") push("vertical rate", vr, "feet/minute") push("type", types[t]) if 20 <= tc <= 22: # airborne position push("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 push("cpr_format", "Odd" if oe else "Even") push("cpr_latitude", cprlat) push("cpr_longitude", cprlon) push("altitude", alt, "feet") if tc == 29: # target state and status push("type", "Target State and Status") subtype = common.bin2int((common.hex2bin(msg)[32:])[5:7]) push("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"} altitude = adsb.target_altitude(msg) if altitude is not None: alt, alt_source, alt_ref = altitude 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) push("target_altitude", alt, "feet") push("altitude_source", alt_source) push("altitude_reference", alt_ref) push("angle", angle, "°") push("angle Type", angle_type) push("angle Source", angle_source) push("vertical mode", vertical_horizontal_types[vertical_mode]) push("horizontal mode", vertical_horizontal_types[horizontal_mode]) push("TCAS/ACAS", tcas_operational_types[tcas_operational]) push("TCAS/ACAS_RA", tcas_ra_types[tcas_ra]) push("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) push("selected_altitude", alt, "feet") push("altitude_source", alt_source) push("barometric_pressure_setting", baro, "millibars") push("selected_Heading", hdg, "°") if not (common.bin2int((common.hex2bin(msg)[32:])[46]) == 0): push("autopilot", types[autopilot]) push("VNAV_mode", types[vnav]) push("altitude_hold_mode", types[alt_hold]) push("approach_mode", types[app]) push("TCAS/ACAS", tcas_operational_types[tcas_operational]) push("LNAV_mode", types[lnav]) if df == 20: push("protocol", "Mode-S Comm-B altitude reply") push("altitude", common.altcode(msg), "feet") if df == 21: push("protocol", "Mode-S Comm-B identity reply") push("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(): push("BDS", "%s (%s)" % (BDS, labels[BDS])) else: push("BDS", BDS) if BDS == "BDS20": callsign = commb.cs20(msg) push("callsign", callsign) if BDS == "BDS40": push("MCP_target_alt", commb.selalt40mcp(msg), "feet") push("FMS_Target_alt", commb.selalt40fms(msg), "feet") push("pressure", commb.p40baro(msg), "millibar") if BDS == "BDS50": push("roll_angle", commb.roll50(msg), "degrees") push("track_angle", commb.trk50(msg), "degrees") push("track_rate", commb.rtrk50(msg), "degree/second") push("ground_speed", commb.gs50(msg) * 1.85200, "km") push("true_airspeed", commb.tas50(msg) * 1.85200, "km") if BDS == "BDS60": push("megnatic Heading", commb.hdg60(msg), "degrees") push("indicated airspeed", commb.ias60(msg) * 1.85200, "km") push("mach number", commb.mach60(msg)) push("vertical rate (Baro)", commb.vr60baro(msg), "feet/minute") push("vertical rate (INS)", commb.vr60ins(msg), "feet/minute") if BDS == "BDS44": push("wind_speed", commb.wind44(msg)[0] * 1.85200, "km") push("wind_direction", commb.wind44(msg)[1], "degrees") push("temperature_1", commb.temp44(msg)[0], "Celsius") push("temperature_2", commb.temp44(msg)[1], "Celsius") push("pressure", commb.p44(msg), "hPa") push("humidity", commb.hum44(msg), "%") push("turbulence", commb.turb44(msg)) if BDS == "BDS45": push("turbulence", commb.turb45(msg)) push("wind_shear", commb.ws45(msg)) push("microbust", commb.mb45(msg)) push("icing", commb.ic45(msg)) push("wake_vortex", commb.wv45(msg)) push("temperature", commb.temp45(msg), "Celsius") push("pressure", commb.p45(msg), "hPa") push("radio_height", commb.rh45(msg), "feet") return _dict