def wind44(msg, rev=False): """reported wind speed and direction Args: msg (String): 28 bytes hexadecimal message (BDS44) string rev (bool): using revised version Returns: (int, float): speed (kt), direction (degree) """ d = hex2bin(data(msg)) if not rev: status = int(d[4]) if not status: return None speed = bin2int(d[5:14]) # knots direction = bin2int(d[14:23]) * 180.0 / 256.0 # degree else: spd_status = int(d[4]) dir_status = int(d[14]) if (not spd_status) or (not dir_status): return None speed = bin2int(d[5:14]) # knots direction = bin2int(d[15:23]) * 180.0 / 128.0 # degree return round(speed, 0), round(direction, 1)
def nac_p(msg): """Calculate NACp, Navigation Accuracy Category - Position Args: msg (string): 28 bytes hexadecimal message 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) nacp_df = pd.read_csv( '/home/josmilrom/Libraries/pyModeS/pyModeS/decoder/adsb_ua_parameters/NACp.csv', sep=',') if tc == 29: nac_p = common.bin2int(msgbin[71:75]) nacp_df_extract = nacp_df[nacp_df.NACp == nac_p] elif tc == 31: nac_p = common.bin2int(msgbin[76:80]) nacp_df_extract = nacp_df[nacp_df.NACp == nac_p] HFU = nacp_df_extract['HFU'][0] VEPU = nacp_df_extract['VEPU'][0] return HFU, VEPU
def is10(msg): """Check if a message is likely to be BDS code 1,0 Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ if allzeros(msg): return False d = hex2bin(data(msg)) # first 8 bits must be 0x10 if d[0:8] != '00010000': return False # bit 10 to 14 are reserved if bin2int(d[9:14]) != 0: return False # overlay capabilty conflict if d[14] == '1' and bin2int(d[16:23]) < 5: return False if d[14] == '0' and bin2int(d[16:23]) > 4: return False return True
def p44(msg, rev=False): """reported average static pressure Args: msg (String): 28 bytes hexadecimal message (BDS44) string rev (bool): using revised version Returns: int: static pressure in hPa """ d = hex2bin(data(msg)) if not rev: if d[34] == '0': return None p = bin2int(d[35:46]) # hPa else: if d[35] == '0': return None p = bin2int(d[36:47]) # hPa return p
def hum44(msg, rev=False): """reported humidity Args: msg (String): 28 bytes hexadecimal message (BDS44) string rev (bool): using revised version Returns: float: percentage of humidity, [0 - 100] % """ d = hex2bin(data(msg)) if not rev: if d[49] == '0': return None hm = bin2int(d[50:56]) * 100.0 / 64 # % else: if d[48] == '0': return None hm = bin2int(d[49:56]) # % return round(hm, 1)
def nac_p(msg): """Calculate NACp, Navigation Accuracy Category - Position Args: msg (string): 28 bytes hexadecimal message 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 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 airborne_position(msg0, msg1, t0, t1): """Decode airborn position from a pair of even and odd position message Args: msg0 (string): even message (28 bytes hexadecimal string) msg1 (string): odd message (28 bytes hexadecimal string) t0 (int): timestamps for the even message t1 (int): timestamps for the odd message Returns: (float, float): (latitude, longitude) of the aircraft """ mb0 = common.hex2bin(msg0)[32:] mb1 = common.hex2bin(msg1)[32:] # 131072 is 2^17, since CPR lat and lon are 17 bits each. cprlat_even = common.bin2int(mb0[22:39]) / 131072.0 cprlon_even = common.bin2int(mb0[39:56]) / 131072.0 cprlat_odd = common.bin2int(mb1[22:39]) / 131072.0 cprlon_odd = common.bin2int(mb1[39:56]) / 131072.0 air_d_lat_even = 360.0 / 60 air_d_lat_odd = 360.0 / 59 # compute latitude index 'j' j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5) lat_even = float(air_d_lat_even * (j % 60 + cprlat_even)) lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd)) if lat_even >= 270: lat_even = lat_even - 360 if lat_odd >= 270: lat_odd = lat_odd - 360 # check if both are in the same latidude zone, exit if not if common.cprNL(lat_even) != common.cprNL(lat_odd): return None # compute ni, longitude index m, and longitude if (t0 > t1): lat = lat_even nl = common.cprNL(lat) ni = max(common.cprNL(lat) - 0, 1) m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5) lon = (360.0 / ni) * (m % ni + cprlon_even) else: lat = lat_odd nl = common.cprNL(lat) ni = max(common.cprNL(lat) - 1, 1) m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5) lon = (360.0 / ni) * (m % ni + cprlon_odd) if lon > 180: lon = lon - 360 return round(lat, 5), round(lon, 5)
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 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 nac_v(msg): """Calculate NACv, Navigation Accuracy Category - Velocity Args: msg (string): 28 bytes hexadecimal message 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) nacv_df = pd.read_csv( '/home/josmilrom/Libraries/pyModeS/pyModeS/decoder/adsb_ua_parameters/NACv.csv', sep=',') msgbin = common.hex2bin(msg) nac_v = common.bin2int(msgbin[42:45]) nacv_df_extract = nacv_df[nacv_df.NACv == nac_v] HFOMr = nacv_df_extract['HFOMr'][0] VFOMr = nacv_df_extract['VFOMr'][0] return HFOMr, VFOMr
def nuc_v(msg): """Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1) Args: msg (string): 28 bytes hexadecimal message 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 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 = hex2bin(data(msg)) if d[0] == '0': return None sign = int(d[1]) # 1 -> west value = 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 is30(msg): """Check if a message is likely to be BDS code 2,0 Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ if allzeros(msg): return False d = hex2bin(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 bin2int(d[15:22]) >= 48: return False return True
def is17(msg): """Check if a message is likely to be BDS code 1,7 Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ if allzeros(msg): return False d = hex2bin(data(msg)) if bin2int(d[28: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 return True
def temp44(msg): """Static air temperature. Args: msg (String): 28 bytes hexadecimal message string Returns: float, float: temperature and alternative temperature in Celsius degree. Note: Two values returns due to what seems to be an inconsistancy error in ICAO 9871 (2008) Appendix A-67. """ d = hex2bin(data(msg)) sign = int(d[23]) value = 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 nac_v(msg): """Calculate NACv, Navigation Accuracy Category - Velocity Args: msg (string): 28 bytes hexadecimal message 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 altitude(msg): """Decode aircraft altitude Args: msg (string): 28 bytes hexadecimal message string Returns: int: altitude in feet """ tc = typecode(msg) if tc < 5 or tc == 19 or tc > 22: raise RuntimeError("%s: Not a position message" % msg) if tc >= 5 and tc <= 8: # surface position, altitude 0 return 0 msgbin = common.hex2bin(msg) q = msgbin[47] if q: n = common.bin2int(msgbin[40:47] + msgbin[48:52]) alt = n * 25 - 1000 return alt else: return None
def trk50(msg): """True track angle, BDS 5,0 message Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: float: angle in degrees to true north (from 0 to 360) """ d = hex2bin(data(msg)) if d[11] == '0': return None sign = int(d[12]) # 1 -> west value = bin2int(d[13:23]) if sign: value = value - 1024 trk = value * 90.0 / 512.0 # convert from [-180, 180] to [0, 360] if trk < 0: trk = 360 + trk return round(trk, 3)
def hdg53(msg): """Magnetic heading, BDS 5,3 message Args: msg (String): 28 bytes hexadecimal message (BDS53) string Returns: float: angle in degrees to true north (from 0 to 360) """ d = hex2bin(data(msg)) if d[0] == '0': return None sign = int(d[1]) # 1 -> west value = 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 sil(msg, version): """Calculate SIL, Surveillance Integrity Level Args: msg (string): 28 bytes hexadecimal message string with TC = 29, 31 Returns: int or string: Probability of exceeding Horizontal Radius of Containment RCu int or string: Probability of exceeding Vertical Integrity Containment Region VPL string: SIL supplement based on "per hour" or "per sample" """ tc = typecode(msg) if tc not in [29, 31]: raise RuntimeError("%s: Not a target state and status messag, \ or operation status message, expecting TC = 29 or 31" % msg) sil_df = pd.read_csv( '/home/josmilrom/Libraries/pyModeS/pyModeS/decoder/adsb_ua_parameters/SIL.csv', sep=',') msgbin = common.hex2bin(msg) if tc == 29: sil = common.bin2int(msgbin[76:78]) elif tc == 31: sil = common.bin2int(msg[82:84]) sil_df_extract = sil_df[sil_df.NACv == sil] PR_RCu = sil_df_extract['PR_RCu'][0] PE_VPL = sil_df_extract['PE_VPL'][0] if version == 1: return PR_RCu, PE_VPL if version == 2: if tc == 29: sil_sup = common.bin2int(msgbin[39]) elif tc == 31: sil_sup = common.bin2int(msgbin[86]) if sil_sup == 0: base = "per hour" elif sil_sup == 1: base = "per sample" return PR_RCu, PE_VPL, base
def sil(msg, version): """Calculate SIL, Surveillance Integrity Level Args: msg (string): 28 bytes hexadecimal message string with TC = 29, 31 Returns: int or string: Probability of exceeding Horizontal Radius of Containment RCu int or string: Probability of exceeding Vertical Integrity Containment Region VPL string: SIL supplement based on per "hour" or "sample", or 'unknown' """ tc = typecode(msg) if tc not in [29, 31]: raise RuntimeError("%s: Not a target state and status messag, \ or operation status message, expecting TC = 29 or 31" % msg) msgbin = common.hex2bin(msg) if tc == 29: SIL = common.bin2int(msgbin[76:78]) elif tc == 31: SIL = common.bin2int(msgbin[82:84]) try: PE_RCu = uncertainty.SIL[SIL]["PE_RCu"] PE_VPL = uncertainty.SIL[SIL]["PE_VPL"] except KeyError: PE_RCu, PE_VPL = uncertainty.NA, uncertainty.NA base = "unknown" if version == 2: if tc == 29: SIL_SUP = common.bin2int(msgbin[39]) elif tc == 31: SIL_SUP = common.bin2int(msgbin[86]) if SIL_SUP == 0: base = "hour" elif SIL_SUP == 1: base = "sample" return PE_RCu, PE_VPL, base
def surface_position_with_ref(msg, lat_ref, lon_ref): """Decode surface position with only one message, knowing reference nearby location, such as previously calculated location, ground station, or airport location, etc. The reference position shall be with in 45NM of the true position. Args: msg (string): even message (28 bytes hexadecimal string) lat_ref: previous known latitude lon_ref: previous known longitude Returns: (float, float): (latitude, longitude) of the aircraft """ mb = common.hex2bin(msg)[32:] cprlat = common.bin2int(mb[22:39]) / 131072.0 cprlon = common.bin2int(mb[39:56]) / 131072.0 i = int(mb[21]) d_lat = 90.0 / 59 if i else 90.0 / 60 j = common.floor(lat_ref / d_lat) + common.floor( 0.5 + ((lat_ref % d_lat) / d_lat) - cprlat ) lat = d_lat * (j + cprlat) ni = common.cprNL(lat) - i if ni > 0: d_lon = 90.0 / ni else: d_lon = 90.0 m = common.floor(lon_ref / d_lon) + common.floor( 0.5 + ((lon_ref % d_lon) / d_lon) - cprlon ) lon = d_lon * (m + cprlon) return round(lat, 5), round(lon, 5)
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 wind44(msg): """Wind speed and direction. Args: msg (String): 28 bytes hexadecimal message string Returns: (int, float): speed (kt), direction (degree) """ d = hex2bin(data(msg)) status = int(d[4]) if not status: return None, None speed = bin2int(d[5:14]) # knots direction = bin2int(d[14:23]) * 180.0 / 256.0 # degree return round(speed, 0), round(direction, 1)
def is40(msg): """Check if a message is likely to be BDS code 4,0 Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ if allzeros(msg): return False d = hex2bin(data(msg)) # status bit 1, 14, and 27 if wrongstatus(d, 1, 2, 13): return False if wrongstatus(d, 14, 15, 26): return False if wrongstatus(d, 27, 28, 39): return False if wrongstatus(d, 48, 49, 51): return False if wrongstatus(d, 54, 55, 56): return False # bits 40-47 and 52-53 shall all be zero if bin2int(d[39:47]) != 0: return False if bin2int(d[51:53]) != 0: return False return True
def is45(msg): """Check if a message is likely to be BDS code 4,5. Meteorological hazard report Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ if allzeros(msg): return False d = hex2bin(data(msg)) # status bit 1, 4, 7, 10, 13, 16, 27, 39 if wrongstatus(d, 1, 2, 3): return False if wrongstatus(d, 4, 5, 6): return False if wrongstatus(d, 7, 8, 9): return False if wrongstatus(d, 10, 11, 12): return False if wrongstatus(d, 13, 14, 15): return False if wrongstatus(d, 16, 17, 26): return False if wrongstatus(d, 27, 28, 38): return False if wrongstatus(d, 39, 40, 51): return False # reserved if bin2int(d[51:56]) != 0: return False temp = temp45(msg) if temp: if temp > 60 or temp < -80: return False return True
def temp44(msg, rev=False): """reported air temperature Args: msg (String): 28 bytes hexadecimal message (BDS44) string rev (bool): using revised version Returns: float: tmeperature in Celsius degree """ d = hex2bin(data(msg)) if not rev: # if d[22] == '0': # return None sign = int(d[23]) value = bin2int(d[24:34]) if sign: value = value - 1024 temp = value * 0.125 # celsius temp = round(temp, 1) else: # if d[23] == '0': # return None sign = int(d[24]) value = bin2int(d[25:35]) if sign: value = value - 1024 temp = value * 0.125 # celsius temp = round(temp, 1) return temp
def p45(msg): """Average static pressure. Args: msg (String): 28 bytes hexadecimal message string Returns: int: static pressure in hPa """ d = hex2bin(data(msg)) if d[26] == "0": return None p = bin2int(d[27:38]) # hPa return p
def rh45(msg): """Radio height. Args: msg (String): 28 bytes hexadecimal message string Returns: int: radio height in ft """ d = hex2bin(data(msg)) if d[38] == "0": return None rh = bin2int(d[39:51]) * 16 return rh