def callsign(msg): """Aircraft callsign Args: msg (string): 28 bytes hexadecimal message string Returns: string: callsign """ if typecode(msg) < 1 or typecode(msg) > 4: raise RuntimeError("%s: Not a identification message" % msg) chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######' msgbin = util.hex2bin(msg) csbin = msgbin[40:96] cs = '' cs += chars[util.bin2int(csbin[0:6])] cs += chars[util.bin2int(csbin[6:12])] cs += chars[util.bin2int(csbin[12:18])] cs += chars[util.bin2int(csbin[18:24])] cs += chars[util.bin2int(csbin[24:30])] cs += chars[util.bin2int(csbin[30:36])] cs += chars[util.bin2int(csbin[36:42])] cs += chars[util.bin2int(csbin[42:48])] # clean string, remove spaces and marks, if any. # cs = cs.replace('_', '') cs = cs.replace('#', '') return cs
def isBDS60(msg): """Check if a message is likely to be BDS code 6,0 Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ # status bit 1, 13, 24, 35, 46 d = util.hex2bin(data(msg)) result = True result = result & checkbits(d, 1, 2, 12) & checkbits(d, 13, 14, 23) \ & checkbits(d, 24, 25, 34) & checkbits(d, 35, 36, 45) \ & checkbits(d, 46, 47, 56) if not (1 < ias(msg) < 500): result &= False if not (0.0 < mach(msg) < 1.0): result &= False if abs(baro_vr(msg)) > 5000: result &= False if abs(ins_vr(msg)) > 5000: result &= False return result
def isBDS50(msg): """Check if a message is likely to be BDS code 5,0 Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ # status bit 1, 12, 24, 35, 46 d = util.hex2bin(data(msg)) result = True result = result & checkbits(d, 1, 3, 11) & checkbits(d, 12, 13, 23) \ & checkbits(d, 24, 25, 34) & checkbits(d, 35, 36, 45) \ & checkbits(d, 46, 47, 56) if d[2:11] == "000000000": result &= True else: if abs(roll(msg)) > 30: result &= False if gs(msg) > 500: result &= False if tas(msg) > 500: result &= False if abs(tas(msg) - gs(msg)) > 100: result &= False return result
def test_crc(): # crc decoder checksum = util.crc("8D406B902015A678D4D220AA4BDA") assert checksum == "000000000000000000000000" # crc encoder parity = util.crc("8D406B902015A678D4D220AA4BDA", encode=True) assert util.hex2bin("AA4BDA") == parity
def typecode(msg): """Type code of ADS-B message Args: msg (string): 28 bytes hexadecimal message string Returns: int: type code number """ msgbin = util.hex2bin(msg) return util.bin2int(msgbin[32:37])
def ias(msg): """Indicated airspeed Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: int: indicated airspeed in knots """ d = util.hex2bin(data(msg)) ias = util.bin2int(d[13:23]) # kts return ias
def tas(msg): """Aircraft true airspeed Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: int: true airspeed in knots """ d = util.hex2bin(data(msg)) spd = util.bin2int(d[46:56]) * 2 # kts return spd
def gs(msg): """Aircraft ground speed Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: int: ground speed in knots """ d = util.hex2bin(data(msg)) spd = util.bin2int(d[24:34]) * 2 # kts return spd
def pbaro(msg): """Barometric pressure setting Args: msg (String): 28 bytes hexadecimal message (BDS40) string Returns: float: pressure in millibar """ d = util.hex2bin(data(msg)) p = util.bin2int(d[27:39]) * 0.1 + 800 # millibar return p
def mach(msg): """Aircraft MACH number Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: float: MACH number """ d = util.hex2bin(data(msg)) mach = util.bin2int(d[24:34]) * 2.048 / 512.0 return round(mach, 3)
def alt_fms(msg): """Selected altitude, FMS Args: msg (String): 28 bytes hexadecimal message (BDS40) string Returns: int: altitude in feet """ d = util.hex2bin(data(msg)) alt = util.bin2int(d[14:26]) * 16 # ft return alt
def track(msg): """True track angle Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: float: angle in degrees to true north (from 0 to 360) """ d = util.hex2bin(data(msg)) sign = int(d[12]) # 1 -> west value = util.bin2int(d[13:23]) * 90 / 512.0 # degree angle = 360 - value if sign else value return round(angle, 1)
def rtrack(msg): """Track angle rate Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: float: angle rate in degrees/second """ d = util.hex2bin(data(msg)) sign = int(d[35]) # 1 -> minus value = util.bin2int(d[36:45]) * 8 / 256.0 # degree / sec angle = -1 * value if sign else value return round(angle, 3)
def heading(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 = util.hex2bin(data(msg)) sign = int(d[1]) # 1 -> west value = util.bin2int(d[2:12]) * 90 / 512.0 # degree hdg = 360 - value if sign else value return round(hdg, 1)
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 """ if typecode(msg) < 5 or typecode(msg) > 18: raise RuntimeError("%s: Not a position message" % msg) msgbin = util.hex2bin(msg) return int(msgbin[53])
def cprlon(msg): """CPR encoded longitude Args: msg (string): 28 bytes hexadecimal message string Returns: int: encoded longitude """ if typecode(msg) < 5 or typecode(msg) > 18: raise RuntimeError("%s: Not a position message" % msg) msgbin = util.hex2bin(msg) return util.bin2int(msgbin[71:88])
def baro_vr(msg): """Vertical rate from barometric measurement Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: int: vertical rate in feet/minutes """ d = util.hex2bin(data(msg)) sign = d[35] # 1 -> minus value = util.bin2int(d[36:45]) * 32 # feet/min roc = -1*value if sign else value return roc
def ins_vr(msg): """Vertical rate messured by onbard equiments (IRS, AHRS) Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: int: vertical rate in feet/minutes """ d = util.hex2bin(data(msg)) sign = d[46] # 1 -> minus value = util.bin2int(d[47:56]) * 32 # feet/min roc = -1*value if sign else value return roc
def category(msg): """Aircraft category number Args: msg (string): 28 bytes hexadecimal message string Returns: int: category number """ if typecode(msg) < 1 or typecode(msg) > 4: raise RuntimeError("%s: Not a identification message" % msg) msgbin = util.hex2bin(msg) return util.bin2int(msgbin[5:8])
def roll(msg): """Aircraft roll angle Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: float: angle in degrees, negative->left wing down, positive->right wing down """ d = util.hex2bin(data(msg)) sign = int(d[1]) # 1 -> left wing down value = util.bin2int(d[2:11]) * 45 / 256.0 # degree angle = -1 * value if sign else value return round(angle, 1)
def velocity(msg): """Calculate the speed, heading, and vertical rate Args: msg (string): 28 bytes hexadecimal message string Returns: (int, float, int, string): speed (kt), heading (degree), rate of climb/descend (ft/min), and speed type ('GS' for ground speed, 'AS' for airspeed) """ if typecode(msg) != 19: raise RuntimeError("%s: Not a airborne velocity message" % msg) msgbin = util.hex2bin(msg) subtype = util.bin2int(msgbin[37:40]) if subtype in (1, 2): v_ew_sign = util.bin2int(msgbin[45]) v_ew = util.bin2int(msgbin[46:56]) - 1 # east-west velocity v_ns_sign = util.bin2int(msgbin[56]) v_ns = util.bin2int(msgbin[57:67]) - 1 # north-south velocity v_we = -1*v_ew if v_ew_sign else v_ew v_sn = -1*v_ns if v_ns_sign else v_ns spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts hdg = math.atan2(v_we, v_sn) hdg = math.degrees(hdg) # convert to degrees hdg = hdg if hdg >= 0 else hdg + 360 # no negative val tag = 'GS' else: hdg = util.bin2int(msgbin[46:56]) / 1024.0 * 360.0 spd = util.bin2int(msgbin[57:67]) tag = 'AS' vr_sign = util.bin2int(msgbin[68]) vr = util.bin2int(msgbin[68:77]) # vertical rate rocd = -1*vr if vr_sign else vr # rate of climb/descend return int(spd), round(hdg, 1), int(rocd), tag
def altitude(msg): """Decode aircraft altitude Args: msg (string): 28 bytes hexadecimal message string Returns: int: altitude in feet """ if typecode(msg) < 9 or typecode(msg) > 18: raise RuntimeError("%s: Not a position message" % msg) msgbin = util.hex2bin(msg) q = msgbin[47] if q: n = util.bin2int(msgbin[40:47]+msgbin[48:52]) alt = n * 25 - 1000 return alt else: return None
def nic(msg): """Calculate NIC, navigation integrity category Args: msg (string): 28 bytes hexadecimal message string Returns: int: NIC number (from 0 to 11), -1 if not applicable """ if typecode(msg) < 9 or typecode(msg) > 18: raise RuntimeError("%s: Not a airborne position message" % msg) msgbin = util.hex2bin(msg) tc = typecode(msg) nic_sup_b = util.bin2int(msgbin[39]) if tc in [0, 18, 22]: nic = 0 elif tc == 17: nic = 1 elif tc == 16: if nic_sup_b: nic = 3 else: nic = 2 elif tc == 15: nic = 4 elif tc == 14: nic = 5 elif tc == 13: nic = 6 elif tc == 12: nic = 7 elif tc == 11: if nic_sup_b: nic = 9 else: nic = 8 elif tc in [10, 21]: nic = 10 elif tc in [9, 20]: nic = 11 else: nic = -1 return nic
def isBDS20(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 """ # status bit 1, 14, and 27 d = util.hex2bin(data(msg)) result = True if util.bin2int(d[0:4]) != 2 or util.bin2int(d[4:8]) != 0: result &= False cs = callsign(msg) if '#' in cs: result &= False return result
def callsign(msg): """Aircraft callsign Args: msg (String): 28 bytes hexadecimal message (BDS40) string Returns: string: callsign, max. 8 chars """ chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######' d = util.hex2bin(data(msg)) cs = '' cs += chars[util.bin2int(d[8:14])] cs += chars[util.bin2int(d[14:20])] cs += chars[util.bin2int(d[20:26])] cs += chars[util.bin2int(d[26:32])] cs += chars[util.bin2int(d[32:38])] cs += chars[util.bin2int(d[38:44])] cs += chars[util.bin2int(d[44:50])] cs += chars[util.bin2int(d[50:56])] return cs
def isBDS40(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 """ # status bit 1, 14, and 27 d = util.hex2bin(data(msg)) result = True result = result & checkbits(d, 1, 2, 13) \ & checkbits(d, 14, 15, 26) & checkbits(d, 27, 28, 39) # bits 40-47 and 52-53 shall all be zero if util.bin2int(d[39:47]) != 0: result &= False if util.bin2int(d[51:53]) != 0: result &= False return result
def test_crc_encode(): parity = util.crc("8D406B902015A678D4D220AA4BDA", encode=True) assert util.hex2bin("AA4BDA") == parity
def test_hex2bin(): assert util.hex2bin('6E406B') == "011011100100000001101011"
def position(msg0, msg1, t0, t1): """Decode position from the combination of even and odd position message 131072 is 2^17, since CPR lat and lon are 17 bits each. 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 """ if typecode(msg0) < 5 or typecode(msg0) > 18: raise RuntimeError("%s: Not a position message" % msg0) if typecode(msg1) < 5 or typecode(msg1) > 18: raise RuntimeError("%s: Not a position message" % msg1) msgbin0 = util.hex2bin(msg0) msgbin1 = util.hex2bin(msg1) cprlat_even = util.bin2int(msgbin0[54:71]) / 131072.0 cprlon_even = util.bin2int(msgbin0[71:88]) / 131072.0 cprlat_odd = util.bin2int(msgbin1[54:71]) / 131072.0 cprlon_odd = util.bin2int(msgbin1[71:88]) / 131072.0 air_d_lat_even = 360.0 / 60 air_d_lat_odd = 360.0 / 59 # compute latitude index 'j' j = int(math.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 _cprNL(lat_even) != _cprNL(lat_odd): return None # compute ni, longitude index m, and longitude if (t0 > t1): ni = _cprN(lat_even, 0) m = math.floor(cprlon_even * (_cprNL(lat_even)-1) - cprlon_odd * _cprNL(lat_even) + 0.5) lon = (360.0 / ni) * (m % ni + cprlon_even) lat = lat_even else: ni = _cprN(lat_odd, 1) m = math.floor(cprlon_even * (_cprNL(lat_odd)-1) - cprlon_odd * _cprNL(lat_odd) + 0.5) lon = (360.0 / ni) * (m % ni + cprlon_odd) lat = lat_odd if lon > 180: lon = lon - 360 return round(lat, 5), round(lon, 5)