From 8b48fabf5a810d9690ed4108807634f7ea2fe793 Mon Sep 17 00:00:00 2001 From: Junzi Sun Date: Fri, 16 Mar 2018 11:53:09 +0100 Subject: [PATCH] add ehs BDS10 decoder, cut redundancies --- pyModeS/__init__.py | 3 +- pyModeS/decoder/ehs.py | 255 +++++++++++------- pyModeS/decoder/els.py | 37 --- pyModeS/decoder/{modes_common.py => modes.py} | 15 +- pyModeS/decoder/util.py | 27 +- tests/test_ehs.py | 39 +-- 6 files changed, 210 insertions(+), 166 deletions(-) delete mode 100644 pyModeS/decoder/els.py rename pyModeS/decoder/{modes_common.py => modes.py} (92%) diff --git a/pyModeS/__init__.py b/pyModeS/__init__.py index 10f17ac..c43679b 100644 --- a/pyModeS/__init__.py +++ b/pyModeS/__init__.py @@ -3,8 +3,7 @@ from __future__ import absolute_import, print_function, division from .decoder.util import * from .decoder import adsb from .decoder import ehs -from .decoder import els from .decoder import util -from .decoder import modes_common +from .decoder import modes from .extra import aero from .extra import beastclient diff --git a/pyModeS/decoder/ehs.py b/pyModeS/decoder/ehs.py index 7804347..7515b39 100644 --- a/pyModeS/decoder/ehs.py +++ b/pyModeS/decoder/ehs.py @@ -19,17 +19,17 @@ A python package for decoding ModeS (DF20, DF21) messages. from __future__ import absolute_import, print_function, division import numpy as np -from pyModeS.decoder import util, modes_common +from pyModeS.decoder import util, modes from pyModeS.extra import aero def icao(msg): - return modes_common.icao(msg) + return modes.icao(msg) def data(msg): """Return the data frame in the message, bytes 9 to 22""" return msg[8:22] -def isnull(msg): +def allzeros(msg): """check if the data bits are all zeros Args: @@ -45,7 +45,7 @@ def isnull(msg): else: return True -def checkbits(data, sb, msb, lsb): +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. """ @@ -56,44 +56,59 @@ def checkbits(data, sb, msb, lsb): if not status: if value != 0: - return False + return True + + return False - return True # ------------------------------------------ -# Common functions +# BDS 1,0 +# Data link capability report # ------------------------------------------ -def df20alt(msg): - """Computes the altitude from DF20 message, bit 20-32 +def isBDS10(msg): + """Check if a message is likely to be BDS code 1,0 Args: msg (String): 28 bytes hexadecimal message string Returns: - int: altitude in ft + bool: True or False """ - if util.df(msg) != 20: - raise RuntimeError("Message must be Downlink Format 20.") + if allzeros(msg): + return False + + d = util.hex2bin(data(msg)) + + # first 8 bits must be 0x10 + if d[0:8] != '00010000': + return False - return modes_common.altcode(msg) + # bit 10 to 14 are reserved + if util.bin2int(d[9:14]) != 0: + return False + # overlay capabilty conflict + if d[14] == '1' and util.bin2int(d[16:23]) < 5: + return False + if d[14] == '0' and util.bin2int(d[16:23]) > 4: + return False + + return True -def df21id(msg): - """Computes identity (squawk code) from DF21, bit 20-32 +def ovc10(msg): + """Return the overlay control capability Args: msg (String): 28 bytes hexadecimal message string Returns: - string: squawk code + int: Whether the transponder is OVC capable """ + d = util.hex2bin(data(msg)) - if util.df(msg) != 21: - raise RuntimeError("Message must be Downlink Format 21.") - - return modes_common.idcode(msg) + return int(d[14]) # ------------------------------------------ # BDS 1,7 @@ -110,24 +125,22 @@ def isBDS17(msg): bool: True or False """ - if isnull(msg): + if allzeros(msg): return False d = util.hex2bin(data(msg)) - result = True - if util.bin2int(d[28:56]) != 0: - result &= False + 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', 'BDS09', 'BDS20']).issubset(caps): - result &= False + return False - return result + return True def cap17(msg): """Extract capacities from BDS 1,7 message @@ -148,6 +161,7 @@ def cap17(msg): return capacity + # ------------------------------------------ # BDS 2,0 # Aircraft identification @@ -163,23 +177,22 @@ def isBDS20(msg): bool: True or False """ - if isnull(msg): + if allzeros(msg): return False - # status bit 1, 14, and 27 d = util.hex2bin(data(msg)) - result = True + # status bit 1, 14, and 27 if util.bin2int(d[0:4]) != 2 or util.bin2int(d[4:8]) != 0: - result &= False + return False cs = callsign(msg) if '#' in cs: - result &= False + return False - return result + return True def callsign(msg): @@ -223,25 +236,31 @@ def isBDS40(msg): bool: True or False """ - if isnull(msg): + if allzeros(msg): return False - # status bit 1, 14, and 27 d = util.hex2bin(data(msg)) - result = True + # status bit 1, 14, and 27 + + if wrongstatus(d, 1, 2, 13): + return False - result = result & checkbits(d, 1, 2, 13) \ - & checkbits(d, 14, 15, 26) & checkbits(d, 27, 28, 39) + if wrongstatus(d, 14, 15, 26): + return False + + if wrongstatus(d, 27, 28, 39): + return False # bits 40-47 and 52-53 shall all be zero + if util.bin2int(d[39:47]) != 0: - result &= False + return False if util.bin2int(d[51:53]) != 0: - result &= False + return False - return result + return True def alt40mcp(msg): @@ -315,44 +334,62 @@ def isBDS44(msg, rev=False): bool: True or False """ - if isnull(msg): + if allzeros(msg): return False d = util.hex2bin(data(msg)) - result = True if not rev: # status bit 5, 35, 47, 50 - result = result & checkbits(d, 5, 6, 23) \ - & checkbits(d, 35, 36, 46) & checkbits(d, 47, 48, 49) \ - & checkbits(d, 50, 51, 56) + if wrongstatus(d, 5, 6, 23): + return False + + if wrongstatus(d, 35, 36, 46): + return False + + if wrongstatus(d, 47, 48, 49): + return False + + if wrongstatus(d, 50, 51, 56): + return False + # Bits 1-4 indicate source, values > 4 reserved and should not occur if util.bin2int(d[0:4]) > 4: - result &= False + return False else: # status bit 5, 15, 24, 36, 49 - result = result & checkbits(d, 5, 6, 14) \ - & checkbits(d, 15, 16, 23) & checkbits(d, 24, 25, 35) \ - & checkbits(d, 36, 37, 47) & checkbits(d, 49, 50, 56) + if wrongstatus(d, 5, 6, 14): + return False + + if wrongstatus(d, 15, 16, 23): + return False + + if wrongstatus(d, 24, 25, 35): + return False + + if wrongstatus(d, 36, 37, 47): + return False + + if wrongstatus(d, 49, 50, 56): + return False + # Bits 1-4 are reserved and should be zero if util.bin2int(d[0:4]) != 0: - result &= False - - if not result: - return False + return False vw = wind44(msg, rev=rev) if vw is not None and vw[0] > 250: - result &= False + return False if temp44(msg): if temp44(msg) > 60 or temp44(msg) < -80: - result &= False + return False + elif temp44(msg) == 0: - result &= False + return False - return result + return True def wind44(msg, rev=False): @@ -498,40 +535,45 @@ def isBDS50(msg): bool: True or False """ - if isnull(msg): + if allzeros(msg): return False - # status bit 1, 12, 24, 35, 46 d = util.hex2bin(data(msg)) - result = True + # status bit 1, 12, 24, 35, 46 - 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 wrongstatus(d, 1, 3, 11): + return False - if not result: + if wrongstatus(d, 12, 13, 23): return False - if d[2:11] == "000000000": - result &= True - else: + if wrongstatus(d, 24, 25, 34): + return False + + if wrongstatus(d, 35, 36, 45): + return False + + if wrongstatus(d, 46, 47, 56): + return False + + if d[2:11] != "000000000": roll = abs(roll50(msg)) if roll and roll > 60: - result &= False + return False gs = gs50(msg) if gs is not None and gs > 600: - result &= False + return False tas = tas50(msg) if tas is not None and tas > 500: - result &= False + return False if (gs is not None) and (tas is not None) and (abs(tas - gs) > 200): - result &= False + return False - return result + return True def roll50(msg): @@ -666,38 +708,45 @@ def isBDS53(msg): bool: True or False """ - if isnull(msg): + if allzeros(msg): return False - # status bit 1, 13, 24, 34, 47 d = util.hex2bin(data(msg)) - result = True + # status bit 1, 13, 24, 34, 47 - result = result & checkbits(d, 1, 3, 12) & checkbits(d, 13, 14, 23) \ - & checkbits(d, 24, 25, 33) & checkbits(d, 34, 35, 46) \ - & checkbits(d, 47, 49, 56) + if wrongstatus(d, 1, 3, 12): + return False + + if wrongstatus(d, 13, 14, 23): + return False + + if wrongstatus(d, 24, 25, 33): + return False + + if wrongstatus(d, 34, 35, 46): + return False - if not result: + if wrongstatus(d, 47, 49, 56): return False ias = ias53(msg) if ias is not None and ias > 500: - result &= False + return False mach = mach53(msg) if mach is not None and mach > 1: - result &= False + return False tas = tas53(msg) if tas is not None and tas > 500: - result &= False + return False vr = vr53(msg) if vr is not None and abs(vr) > 8000: - result &= False + return False - return result + return True def hdg53(msg): @@ -822,38 +871,45 @@ def isBDS60(msg): bool: True or False """ - if isnull(msg): + if allzeros(msg): return False - # status bit 1, 13, 24, 35, 46 d = util.hex2bin(data(msg)) - result = True + # status bit 1, 13, 24, 35, 46 + + if wrongstatus(d, 1, 2, 12): + return False - 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 wrongstatus(d, 13, 14, 23): + return False - if not result: + if wrongstatus(d, 24, 25, 34): + return False + + if wrongstatus(d, 35, 36, 45): + return False + + if wrongstatus(d, 46, 47, 56): return False ias = ias60(msg) if ias is not None and ias > 500: - result &= False + return False mach = mach60(msg) if mach is not None and mach > 1: - result &= False + return False vr_baro = vr60baro(msg) if vr_baro is not None and abs(vr_baro) > 6000: - result &= False + return False vr_ins = vr60ins(msg) if vr_ins is not None and abs(vr_ins) > 6000: - result &= False + return False - return result + return True def hdg60(msg): @@ -1038,9 +1094,10 @@ def BDS(msg): String or None: BDS version, or possible versions, or None if nothing matches. """ - if isnull(msg): + if allzeros(msg): return None + is10 = isBDS10(msg) is17 = isBDS17(msg) is20 = isBDS20(msg) is40 = isBDS40(msg) @@ -1051,11 +1108,11 @@ def BDS(msg): is60 = isBDS60(msg) allbds = np.array([ - "BDS17", "BDS20", "BDS40", "BDS44", "BDS44REV", + "BDS10", "BDS17", "BDS20", "BDS40", "BDS44", "BDS44REV", "BDS50", "BDS53", "BDS60" ]) - isBDS = [is17, is20, is40, is44, is44rev, is50, is53, is60] + isBDS = [is10, is17, is20, is40, is44, is44rev, is50, is53, is60] bds = ','.join(sorted(allbds[isBDS])) diff --git a/pyModeS/decoder/els.py b/pyModeS/decoder/els.py deleted file mode 100644 index ff3c6bf..0000000 --- a/pyModeS/decoder/els.py +++ /dev/null @@ -1,37 +0,0 @@ -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/decoder/modes_common.py b/pyModeS/decoder/modes.py similarity index 92% rename from pyModeS/decoder/modes_common.py rename to pyModeS/decoder/modes.py index 08ad417..379f986 100644 --- a/pyModeS/decoder/modes_common.py +++ b/pyModeS/decoder/modes.py @@ -110,13 +110,14 @@ def altcode(msg): return alt + def gray2alt(codestr): gc500 = codestr[:8] - n500 = util.gray2int(gc500) + n500 = gray2int(gc500) # in 100-ft step must be converted first gc100 = codestr[8:] - n100 = util.gray2int(gc100) + n100 = gray2int(gc100) if n100 in [0, 5, 6]: return None @@ -129,3 +130,13 @@ def gray2alt(codestr): alt = (n500*500 + n100*100) - 1300 return alt + + +def gray2int(graystr): + """Convert greycode to binary""" + num = util.bin2int(graystr) + num ^= (num >> 8) + num ^= (num >> 4) + num ^= (num >> 2) + num ^= (num >> 1) + return num diff --git a/pyModeS/decoder/util.py b/pyModeS/decoder/util.py index 774f2d0..d561ded 100644 --- a/pyModeS/decoder/util.py +++ b/pyModeS/decoder/util.py @@ -97,11 +97,22 @@ def floor(x): return int(np.floor(x)) -def gray2int(graystr): - """Convert greycode to binary (DF4, 20 altitude coding)""" - num = bin2int(graystr) - num ^= (num >> 8) - num ^= (num >> 4) - num ^= (num >> 2) - num ^= (num >> 1) - return num +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 = hex2int(icao) + + 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 diff --git a/tests/test_ehs.py b/tests/test_ehs.py index ab1b362..ce9e60b 100644 --- a/tests/test_ehs.py +++ b/tests/test_ehs.py @@ -1,5 +1,5 @@ from pyModeS import ehs -from pyModeS import modes_common +from pyModeS import modes def test_ehs_icao(): assert ehs.icao("A0001839CA3800315800007448D9") == '400940' @@ -7,8 +7,11 @@ def test_ehs_icao(): assert ehs.icao("A000029CFFBAA11E2004727281F1") == '4243D0' -def test_df20alt(): - assert ehs.df20alt("A02014B400000000000000F9D514") == 32300 +def test_modes_altcode(): + assert ehs.modes.altcode("A02014B400000000000000F9D514") == 32300 + +def test_modes_idcode(): + assert ehs.modes.idcode("A800292DFFBBA9383FFCEB903D01") == '1346' def test_ehs_BDS(): @@ -49,18 +52,18 @@ def test_ehs_BDS60_functions(): assert ehs.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920 def test_graycode_to_altitude(): - assert modes_common.gray2alt('00000000010') == -1000 - assert modes_common.gray2alt('00000001010') == -500 - assert modes_common.gray2alt('00000011011') == -100 - assert modes_common.gray2alt('00000011010') == 0 - assert modes_common.gray2alt('00000011110') == 100 - assert modes_common.gray2alt('00000010011') == 600 - assert modes_common.gray2alt('00000110010') == 1000 - assert modes_common.gray2alt('00001001001') == 5800 - assert modes_common.gray2alt('00011100100') == 10300 - assert modes_common.gray2alt('01100011010') == 32000 - assert modes_common.gray2alt('01110000100') == 46300 - assert modes_common.gray2alt('01010101100') == 50200 - assert modes_common.gray2alt('11011110100') == 73200 - assert modes_common.gray2alt('10000000011') == 126600 - assert modes_common.gray2alt('10000000001') == 126700 + assert modes.gray2alt('00000000010') == -1000 + assert modes.gray2alt('00000001010') == -500 + assert modes.gray2alt('00000011011') == -100 + assert modes.gray2alt('00000011010') == 0 + assert modes.gray2alt('00000011110') == 100 + assert modes.gray2alt('00000010011') == 600 + assert modes.gray2alt('00000110010') == 1000 + assert modes.gray2alt('00001001001') == 5800 + assert modes.gray2alt('00011100100') == 10300 + assert modes.gray2alt('01100011010') == 32000 + assert modes.gray2alt('01110000100') == 46300 + assert modes.gray2alt('01010101100') == 50200 + assert modes.gray2alt('11011110100') == 73200 + assert modes.gray2alt('10000000011') == 126600 + assert modes.gray2alt('10000000001') == 126700