From aa9f49b470a48ed3b809eb2a03ddb2e2caab126a Mon Sep 17 00:00:00 2001 From: junzis Date: Fri, 21 Jul 2017 17:40:10 +0200 Subject: [PATCH] add DF4/20 altitude and DF5/21 squawk decoding --- README.rst | 25 ++++++++- pyModeS/__init__.py | 1 + pyModeS/ehs.py | 107 ++++++++++++----------------------- pyModeS/els.py | 37 ++++++++++++ pyModeS/modes_common.py | 121 ++++++++++++++++++++++++++++++++++++++++ pyModeS/util.py | 13 +++++ 6 files changed, 230 insertions(+), 74 deletions(-) create mode 100644 pyModeS/els.py create mode 100644 pyModeS/modes_common.py diff --git a/README.rst b/README.rst index 1790807..0c2d3bb 100644 --- a/README.rst +++ b/README.rst @@ -48,7 +48,8 @@ Usage import pyModeS as pms -Common function for Mode-S message: +Common functions: +***************** .. code:: python @@ -59,8 +60,12 @@ Common function for Mode-S message: pms.bin2int(str) # Convert binary string to integer pms.hex2int(str) # Convert hexadecimal string to integer + pms.bin2gray(str) # Convert binary string to grey code + pms.gray2bin(str) # Convert grey code to binary string + Core functions for ADS-B decoding: +********************************** .. code:: python @@ -83,17 +88,31 @@ Core functions for ADS-B decoding: pms.adsb.airborne_velocity(msg) -**Hint: When you have a fix position of the aircraft, it is convenient to +Hint: When you have a fix position of the aircraft, it is convenient to use `position_with_ref()` method to decode with only one position message (either odd or even). This works with both airborne and surface position messages. But the reference position shall be with in 180NM (airborne) -or 45NM (surface) of the true position.** +or 45NM (surface) of the true position. + +Core functions for ELS decoding: +******************************** + +.. code:: python + + pms.els.icao(msg) # ICAO address + pms.els.df4alt(msg) # Altitude from any DF4 message + pms.ehs.df5id(msg) # Squawk code from any DF5 message + Core functions for EHS decoding: +******************************** .. code:: python pms.ehs.icao(msg) # ICAO address + pms.ehs.df20alt(msg) # Altitude from any DF20 message + pms.ehs.df21id(msg) # Squawk code from any DF21 message + pms.ehs.BDS(msg) # Comm-B Data Selector Version # for BDS version 2,0 diff --git a/pyModeS/__init__.py b/pyModeS/__init__.py index 5be7b05..3abdc10 100644 --- a/pyModeS/__init__.py +++ b/pyModeS/__init__.py @@ -3,3 +3,4 @@ from __future__ import absolute_import, print_function, division from .util import * from . import adsb from . import ehs +from . import els diff --git a/pyModeS/ehs.py b/pyModeS/ehs.py index fdd4d1d..8035af7 100644 --- a/pyModeS/ehs.py +++ b/pyModeS/ehs.py @@ -18,65 +18,15 @@ A python package for decoding ModeS (DF20, DF21) messages. """ from __future__ import absolute_import, print_function, division -from . import util - -def df(msg): - """Get the downlink format (DF) number - - Args: - msg (String): 28 bytes hexadecimal message string - - Returns: - int: DF number - """ - return util.df(msg) +from . import util, modes_common +def icao(msg): + return modes_common.icao(msg) def data(msg): """Return the data frame in the message, bytes 9 to 22""" return msg[8:22] - -def icao(msg): - """Calculate the ICAO address from an Mode-S message - with DF4, DF5, DF20, DF21 - - Args: - msg (String): 28 bytes hexadecimal message string - - Returns: - String: ICAO address in 6 bytes hexadecimal string - """ - - if df(msg) not in (4, 5, 20, 21): - # raise RuntimeError("Message DF must be in (4, 5, 20, 21)") - return None - - c0 = util.bin2int(util.crc(msg, encode=True)) - c1 = util.hex2int(msg[-6:]) - icao = '%06X' % (c0 ^ c1) - return icao - - -def checkbits(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 = util.bin2int(data[msb-1:lsb]) - - if not status: - if value != 0: - return False - - return True - - -# ------------------------------------------ -# Common functions -# ------------------------------------------ def isnull(msg): """check if the data bits are all zeros @@ -93,9 +43,27 @@ def isnull(msg): else: return True +def checkbits(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 = util.bin2int(data[msb-1:lsb]) + + if not status: + if value != 0: + return False + + return True + +# ------------------------------------------ +# Common functions +# ------------------------------------------ def df20alt(msg): - """Computes the altitude from DF20 bit 20-32 + """Computes the altitude from DF20 message, bit 20-32 Args: msg (String): 28 bytes hexadecimal message string @@ -104,29 +72,26 @@ def df20alt(msg): int: altitude in ft """ - if df(msg) != 20: + if util.df(msg) != 20: raise RuntimeError("Message must be Downlink Format 20.") - # Altitude code, bit 20-32 - mbin = util.hex2bin(msg) - - mbit = mbin[25] # M bit: 26 - qbit = mbin[27] # Q bit: 28 + return modes_common.altcode(msg) - if mbit == '0': # unit in ft - if qbit == '1': # 25ft interval - vbin = mbin[19:25] + mbin[26] + mbin[28:32] - alt = util.bin2int(vbin) * 25 - 1000 - if qbit == '0': # 100ft interval - # to be implemented - alt = None - if mbit == '1': # unit in meter - vbin = mbin[19:25] + mbin[26:31] - alt = int(util.bin2int(vbin) * 3.28084) # convert to ft +def df21id(msg): + """Computes identity (squawk code) from DF21, bit 20-32 - return alt + Args: + msg (String): 28 bytes hexadecimal message string + Returns: + string: squawk code + """ + + if util.df(msg) != 21: + raise RuntimeError("Message must be Downlink Format 21.") + + return modes_common.idcode(msg) # ------------------------------------------ # BDS 1,7 diff --git a/pyModeS/els.py b/pyModeS/els.py new file mode 100644 index 0000000..ff3c6bf --- /dev/null +++ b/pyModeS/els.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import, print_function, division +from . import util, modes_common + +def icao(msg): + return modes_common.icao(msg) + + +def df4alt(msg): + """Computes the altitude from DF4 message, bit 20-32 + + Args: + msg (String): 28 bytes hexadecimal message string + + Returns: + int: altitude in ft + """ + + if util.df(msg) != 4: + raise RuntimeError("Message must be Downlink Format 4.") + + return modes_common.altcode(msg) + + +def df5id(msg): + """Computes identity (squawk code) from DF5 message, bit 20-32 + + Args: + msg (String): 28 bytes hexadecimal message string + + Returns: + string: squawk code + """ + + if util.df(msg) != 5: + raise RuntimeError("Message must be Downlink Format 5.") + + return modes_common.idcode(msg) diff --git a/pyModeS/modes_common.py b/pyModeS/modes_common.py new file mode 100644 index 0000000..1973915 --- /dev/null +++ b/pyModeS/modes_common.py @@ -0,0 +1,121 @@ +from __future__ import absolute_import, print_function, division +from . import util + + +def icao(msg): + """Calculate the ICAO address from an Mode-S message + with DF4, DF5, DF20, DF21 + + Args: + msg (String): 28 bytes hexadecimal message string + + Returns: + String: ICAO address in 6 bytes hexadecimal string + """ + + if util.df(msg) not in (4, 5, 20, 21): + # raise RuntimeError("Message DF must be in (4, 5, 20, 21)") + return None + + c0 = util.bin2int(util.crc(msg, encode=True)) + c1 = util.hex2int(msg[-6:]) + addr = '%06X' % (c0 ^ c1) + return addr + + +def idcode(msg): + """Computes identity (squawk code) from DF5 or DF21 message, bit 20-32 + + Args: + msg (String): 28 bytes hexadecimal message string + + Returns: + string: squawk code + """ + + if util.df(msg) not in [5, 21]: + raise RuntimeError("Message must be Downlink Format 5 or 21.") + + mbin = util.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) + + +def altcode(msg): + """Computes the altitude from DF4 or DF20 message, bit 20-32 + + Args: + msg (String): 28 bytes hexadecimal message string + + Returns: + int: altitude in ft + """ + + if util.df(msg) not in [4, 20]: + raise RuntimeError("Message must be Downlink Format 4 or 20.") + + # Altitude code, bit 20-32 + mbin = util.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 = util.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] + # _ = mbin[27] + B2 = mbin[28] + D2 = mbin[29] + B4 = mbin[30] + D4 = mbin[31] + + # standard greycode + gc5 = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + N5 = int(util.gray2bin(gc5, 8), 2) + + # in 100-ft step must be converted + gc1 = C1 + C2 + C4 + N1 = int(util.gray2bin(gc1, 3), 2) - 1 + + if N1 == 6: + N1 = 4 + + if N5%2 != 0: + N1 = 4 - N1 + + alt = (N5*500 + N1*100) - 1200 + if mbit == '1': # unit in meter + vbin = mbin[19:25] + mbin[26:31] + alt = int(util.bin2int(vbin) * 3.28084) # convert to ft + + return alt diff --git a/pyModeS/util.py b/pyModeS/util.py index 89a3efd..8dab2d7 100644 --- a/pyModeS/util.py +++ b/pyModeS/util.py @@ -85,3 +85,16 @@ def floor(x): eg.: floor(3.6) = 3, while floor(-3.6) = -4 """ return int(math.floor(x)) + + +def bin2gray(binary, nbits): + """Convert binary to greycode""" + graycode = binary + for i in range(1, nbits): + bit = str(int(binary[i-1]) ^ int(binary[i])) + graycode = str(graycode[:i]) + str(bit) + return graycode + +def gray2bin(greycode, nbits): + """Convert greycode to binary""" + return bin2gray(greycode, nbits) # simply XOR again