Source code for pyModeS.common

import numpy as np
from textwrap import wrap


[docs]def hex2bin(hexstr): """Convert a hexdecimal string to binary string, with zero fillings.""" num_of_bits = len(hexstr) * 4 binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits)) return binstr
[docs]def hex2int(hexstr): """Convert a hexdecimal string to integer.""" return int(hexstr, 16)
[docs]def bin2int(binstr): """Convert a binary string to integer.""" return int(binstr, 2)
[docs]def df(msg): """Decode Downlink Format value, bits 1 to 5.""" dfbin = hex2bin(msg[:2]) return min(bin2int(dfbin[0:5]), 24)
[docs]def crc(msg, encode=False): """Mode-S Cyclic Redundancy Check. Detect if bit error occurs in the Mode-S message. When encode option is on, the checksum is generated. Args: msg (string): 28 bytes hexadecimal message string encode (bool): True to encode the date only and return the checksum Returns: int: message checksum, or partity bits (encoder) """ # the CRC generator G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)] if encode: msg = msg[:-6] + "000000" msgbin = hex2bin(msg) msgbin_split = wrap(msgbin, 8) mbytes = list(map(bin2int, msgbin_split)) for ibyte in range(len(mbytes) - 3): for ibit in range(8): mask = 0x80 >> ibit bits = mbytes[ibyte] & mask if bits > 0: mbytes[ibyte] = mbytes[ibyte] ^ (G[0] >> ibit) mbytes[ibyte + 1] = mbytes[ibyte + 1] ^ ( 0xFF & ((G[0] << 8 - ibit) | (G[1] >> ibit)) ) mbytes[ibyte + 2] = mbytes[ibyte + 2] ^ ( 0xFF & ((G[1] << 8 - ibit) | (G[2] >> ibit)) ) mbytes[ibyte + 3] = mbytes[ibyte + 3] ^ ( 0xFF & ((G[2] << 8 - ibit) | (G[3] >> ibit)) ) result = (mbytes[-3] << 16) | (mbytes[-2] << 8) | mbytes[-1] return result
[docs]def crc_legacy(msg, encode=False): """Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow).""" # the polynominal generattor code for CRC [1111111111111010000001001] generator = np.array( [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1] ) ng = len(generator) msgnpbin = np.array([int(i) for i in hex2bin(msg)]) if encode: msgnpbin[-24:] = [0] * 24 # loop all bits, except last 24 piraty bits for i in range(len(msgnpbin) - 24): if msgnpbin[i] == 0: continue # perform XOR, when 1 msgnpbin[i : i + ng] = np.bitwise_xor(msgnpbin[i : i + ng], generator) # last 24 bits msgbin = np.array2string(msgnpbin[-24:], separator="")[1:-1] reminder = bin2int(msgbin) return reminder
[docs]def floor(x): """Mode-S floor function. Defined as the greatest integer value k, such that k <= x For example: floor(3.6) = 3 and floor(-3.6) = -4 """ return int(np.floor(x))
[docs]def icao(msg): """Calculate the ICAO address from an Mode-S message. Applicable only with DF4, DF5, DF20, DF21 messages. Args: msg (String): 28 bytes hexadecimal message string Returns: String: ICAO address in 6 bytes hexadecimal string """ DF = df(msg) if DF in (11, 17, 18): addr = msg[2:8] elif DF in (0, 4, 5, 16, 20, 21): c0 = crc(msg, encode=True) c1 = int(msg[-6:], 16) addr = "%06X" % (c0 ^ c1) else: addr = None return addr
[docs]def is_icao_assigned(icao): """Check whether the ICAO address is assigned (Annex 10, Vol 3).""" if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6): return False icaoint = int(icao, 16) if 0x200000 < icaoint < 0x27FFFF: return False # AFI if 0x280000 < icaoint < 0x28FFFF: return False # SAM if 0x500000 < icaoint < 0x5FFFFF: return False # EUR, NAT if 0x600000 < icaoint < 0x67FFFF: return False # MID if 0x680000 < icaoint < 0x6F0000: return False # ASIA if 0x900000 < icaoint < 0x9FFFFF: return False # NAM, PAC if 0xB00000 < icaoint < 0xBFFFFF: return False # CAR if 0xD00000 < icaoint < 0xDFFFFF: return False # future if 0xF00000 < icaoint < 0xFFFFFF: return False # future return True
[docs]def typecode(msg): """Type code of ADS-B message Args: msg (string): 28 bytes hexadecimal message string Returns: int: type code number """ if df(msg) not in (17, 18): return None tcbin = hex2bin(msg[8:10]) return bin2int(tcbin[0:5])
[docs]def cprNL(lat): """NL() function in CPR decoding.""" if np.isclose(lat, 0): return 59 elif np.isclose(abs(lat), 87): return 2 elif lat > 87 or lat < -87: return 1 nz = 15 a = 1 - np.cos(np.pi / (2 * nz)) b = np.cos(np.pi / 180.0 * abs(lat)) ** 2 nl = 2 * np.pi / (np.arccos(1 - a / b)) NL = floor(nl) return NL
[docs]def idcode(msg): """Compute identity (squawk code). Applicable only for DF5 or DF21 messages, bit 20-32. credit: @fbyrkjeland Args: msg (String): 28 bytes hexadecimal message string Returns: string: squawk code """ if df(msg) not in [5, 21]: raise RuntimeError("Message must be Downlink Format 5 or 21.") mbin = hex2bin(msg) C1 = mbin[19] A1 = mbin[20] C2 = mbin[21] A2 = mbin[22] C4 = mbin[23] A4 = mbin[24] # _ = mbin[25] B1 = mbin[26] D1 = mbin[27] B2 = mbin[28] D2 = mbin[29] B4 = mbin[30] D4 = mbin[31] byte1 = int(A4 + A2 + A1, 2) byte2 = int(B4 + B2 + B1, 2) byte3 = int(C4 + C2 + C1, 2) byte4 = int(D4 + D2 + D1, 2) return str(byte1) + str(byte2) + str(byte3) + str(byte4)
[docs]def altcode(msg): """Compute the altitude. Applicable only for DF4 or DF20 message, bit 20-32. credit: @fbyrkjeland Args: msg (String): 28 bytes hexadecimal message string Returns: int: altitude in ft """ if df(msg) not in [0, 4, 16, 20]: raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.") # Altitude code, bit 20-32 mbin = hex2bin(msg) mbit = mbin[25] # M bit: 26 qbit = mbin[27] # Q bit: 28 if mbit == "0": # unit in ft if qbit == "1": # 25ft interval vbin = mbin[19:25] + mbin[26] + mbin[28:32] alt = bin2int(vbin) * 25 - 1000 if qbit == "0": # 100ft interval, above 50175ft C1 = mbin[19] A1 = mbin[20] C2 = mbin[21] A2 = mbin[22] C4 = mbin[23] A4 = mbin[24] # _ = mbin[25] B1 = mbin[26] # D1 = mbin[27] # always zero B2 = mbin[28] D2 = mbin[29] B4 = mbin[30] D4 = mbin[31] graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4 alt = gray2alt(graystr) if mbit == "1": # unit in meter vbin = mbin[19:25] + mbin[26:31] alt = int(bin2int(vbin) * 3.28084) # convert to ft return alt
[docs]def gray2alt(codestr): gc500 = codestr[:8] n500 = gray2int(gc500) # in 100-ft step must be converted first gc100 = codestr[8:] n100 = gray2int(gc100) if n100 in [0, 5, 6]: return None if n100 == 7: n100 = 5 if n500 % 2: n100 = 6 - n100 alt = (n500 * 500 + n100 * 100) - 1300 return alt
[docs]def gray2int(graystr): """Convert greycode to binary.""" num = bin2int(graystr) num ^= num >> 8 num ^= num >> 4 num ^= num >> 2 num ^= num >> 1 return num
[docs]def data(msg): """Return the data frame in the message, bytes 9 to 22.""" return msg[8:-6]
[docs]def allzeros(msg): """Check if the data bits are all zeros. Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ d = hex2bin(data(msg)) if bin2int(d) > 0: return False else: return True
[docs]def wrongstatus(data, sb, msb, lsb): """Check if the status bit and field bits are consistency. This Function is used for checking BDS code versions. """ # status bit, most significant bit, least significant bit status = int(data[sb - 1]) value = bin2int(data[msb - 1 : lsb]) if not status: if value != 0: return True return False