add ehs BDS10 decoder, cut redundancies

This commit is contained in:
Junzi Sun 2018-03-16 11:53:09 +01:00
parent 4911e69171
commit 8b48fabf5a
6 changed files with 224 additions and 180 deletions

View File

@ -3,8 +3,7 @@ from __future__ import absolute_import, print_function, division
from .decoder.util import * from .decoder.util import *
from .decoder import adsb from .decoder import adsb
from .decoder import ehs from .decoder import ehs
from .decoder import els
from .decoder import util from .decoder import util
from .decoder import modes_common from .decoder import modes
from .extra import aero from .extra import aero
from .extra import beastclient from .extra import beastclient

View File

@ -19,17 +19,17 @@ A python package for decoding ModeS (DF20, DF21) messages.
from __future__ import absolute_import, print_function, division from __future__ import absolute_import, print_function, division
import numpy as np import numpy as np
from pyModeS.decoder import util, modes_common from pyModeS.decoder import util, modes
from pyModeS.extra import aero from pyModeS.extra import aero
def icao(msg): def icao(msg):
return modes_common.icao(msg) return modes.icao(msg)
def data(msg): def data(msg):
"""Return the data frame in the message, bytes 9 to 22""" """Return the data frame in the message, bytes 9 to 22"""
return msg[8:22] return msg[8:22]
def isnull(msg): def allzeros(msg):
"""check if the data bits are all zeros """check if the data bits are all zeros
Args: Args:
@ -45,7 +45,7 @@ def isnull(msg):
else: else:
return True 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 """Check if the status bit and field bits are consistency. This Function
is used for checking BDS code versions. is used for checking BDS code versions.
""" """
@ -56,44 +56,59 @@ def checkbits(data, sb, msb, lsb):
if not status: if not status:
if value != 0: if value != 0:
return True
return False
# ------------------------------------------
# BDS 1,0
# Data link capability report
# ------------------------------------------
def isBDS10(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 = util.hex2bin(data(msg))
# first 8 bits must be 0x10
if d[0:8] != '00010000':
return False
# 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 False
return True return True
# ------------------------------------------ def ovc10(msg):
# Common functions """Return the overlay control capability
# ------------------------------------------
def df20alt(msg):
"""Computes the altitude from DF20 message, bit 20-32
Args: Args:
msg (String): 28 bytes hexadecimal message string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: altitude in ft int: Whether the transponder is OVC capable
""" """
d = util.hex2bin(data(msg))
if util.df(msg) != 20: return int(d[14])
raise RuntimeError("Message must be Downlink Format 20.")
return modes_common.altcode(msg)
def df21id(msg):
"""Computes identity (squawk code) from DF21, bit 20-32
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 # BDS 1,7
@ -110,24 +125,22 @@ def isBDS17(msg):
bool: True or False bool: True or False
""" """
if isnull(msg): if allzeros(msg):
return False return False
d = util.hex2bin(data(msg)) d = util.hex2bin(data(msg))
result = True
if util.bin2int(d[28:56]) != 0: if util.bin2int(d[28:56]) != 0:
result &= False return False
caps = cap17(msg) caps = cap17(msg)
# basic BDS codes for ADS-B shall be supported # basic BDS codes for ADS-B shall be supported
# assuming ADS-B out is installed (2017EU/2020US mandate) # assuming ADS-B out is installed (2017EU/2020US mandate)
if not set(['BDS05', 'BDS06', 'BDS09', 'BDS20']).issubset(caps): if not set(['BDS05', 'BDS06', 'BDS09', 'BDS20']).issubset(caps):
result &= False return False
return result return True
def cap17(msg): def cap17(msg):
"""Extract capacities from BDS 1,7 message """Extract capacities from BDS 1,7 message
@ -148,6 +161,7 @@ def cap17(msg):
return capacity return capacity
# ------------------------------------------ # ------------------------------------------
# BDS 2,0 # BDS 2,0
# Aircraft identification # Aircraft identification
@ -163,23 +177,22 @@ def isBDS20(msg):
bool: True or False bool: True or False
""" """
if isnull(msg): if allzeros(msg):
return False return False
# status bit 1, 14, and 27
d = util.hex2bin(data(msg)) 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: if util.bin2int(d[0:4]) != 2 or util.bin2int(d[4:8]) != 0:
result &= False return False
cs = callsign(msg) cs = callsign(msg)
if '#' in cs: if '#' in cs:
result &= False return False
return result return True
def callsign(msg): def callsign(msg):
@ -223,25 +236,31 @@ def isBDS40(msg):
bool: True or False bool: True or False
""" """
if isnull(msg): if allzeros(msg):
return False return False
# status bit 1, 14, and 27
d = util.hex2bin(data(msg)) d = util.hex2bin(data(msg))
result = True # status bit 1, 14, and 27
result = result & checkbits(d, 1, 2, 13) \ if wrongstatus(d, 1, 2, 13):
& checkbits(d, 14, 15, 26) & checkbits(d, 27, 28, 39) return False
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 # bits 40-47 and 52-53 shall all be zero
if util.bin2int(d[39:47]) != 0: if util.bin2int(d[39:47]) != 0:
result &= False return False
if util.bin2int(d[51:53]) != 0: if util.bin2int(d[51:53]) != 0:
result &= False return False
return result return True
def alt40mcp(msg): def alt40mcp(msg):
@ -315,44 +334,62 @@ def isBDS44(msg, rev=False):
bool: True or False bool: True or False
""" """
if isnull(msg): if allzeros(msg):
return False return False
d = util.hex2bin(data(msg)) d = util.hex2bin(data(msg))
result = True
if not rev: if not rev:
# status bit 5, 35, 47, 50 # status bit 5, 35, 47, 50
result = result & checkbits(d, 5, 6, 23) \ if wrongstatus(d, 5, 6, 23):
& checkbits(d, 35, 36, 46) & checkbits(d, 47, 48, 49) \ return False
& checkbits(d, 50, 51, 56)
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 # Bits 1-4 indicate source, values > 4 reserved and should not occur
if util.bin2int(d[0:4]) > 4: if util.bin2int(d[0:4]) > 4:
result &= False return False
else: else:
# status bit 5, 15, 24, 36, 49 # status bit 5, 15, 24, 36, 49
result = result & checkbits(d, 5, 6, 14) \ if wrongstatus(d, 5, 6, 14):
& checkbits(d, 15, 16, 23) & checkbits(d, 24, 25, 35) \ return False
& checkbits(d, 36, 37, 47) & checkbits(d, 49, 50, 56)
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 # Bits 1-4 are reserved and should be zero
if util.bin2int(d[0:4]) != 0: if util.bin2int(d[0:4]) != 0:
result &= False
if not result:
return False return False
vw = wind44(msg, rev=rev) vw = wind44(msg, rev=rev)
if vw is not None and vw[0] > 250: if vw is not None and vw[0] > 250:
result &= False return False
if temp44(msg): if temp44(msg):
if temp44(msg) > 60 or temp44(msg) < -80: if temp44(msg) > 60 or temp44(msg) < -80:
result &= False return False
elif temp44(msg) == 0:
result &= False
return result elif temp44(msg) == 0:
return False
return True
def wind44(msg, rev=False): def wind44(msg, rev=False):
@ -498,40 +535,45 @@ def isBDS50(msg):
bool: True or False bool: True or False
""" """
if isnull(msg): if allzeros(msg):
return False return False
# status bit 1, 12, 24, 35, 46
d = util.hex2bin(data(msg)) 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) \ if wrongstatus(d, 1, 3, 11):
& checkbits(d, 24, 25, 34) & checkbits(d, 35, 36, 45) \
& checkbits(d, 46, 47, 56)
if not result:
return False return False
if d[2:11] == "000000000": if wrongstatus(d, 12, 13, 23):
result &= True return False
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)) roll = abs(roll50(msg))
if roll and roll > 60: if roll and roll > 60:
result &= False return False
gs = gs50(msg) gs = gs50(msg)
if gs is not None and gs > 600: if gs is not None and gs > 600:
result &= False return False
tas = tas50(msg) tas = tas50(msg)
if tas is not None and tas > 500: 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): 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): def roll50(msg):
@ -666,38 +708,45 @@ def isBDS53(msg):
bool: True or False bool: True or False
""" """
if isnull(msg): if allzeros(msg):
return False return False
# status bit 1, 13, 24, 34, 47
d = util.hex2bin(data(msg)) 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) \ if wrongstatus(d, 1, 3, 12):
& checkbits(d, 24, 25, 33) & checkbits(d, 34, 35, 46) \ return False
& checkbits(d, 47, 49, 56)
if not result: 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 wrongstatus(d, 47, 49, 56):
return False return False
ias = ias53(msg) ias = ias53(msg)
if ias is not None and ias > 500: if ias is not None and ias > 500:
result &= False return False
mach = mach53(msg) mach = mach53(msg)
if mach is not None and mach > 1: if mach is not None and mach > 1:
result &= False return False
tas = tas53(msg) tas = tas53(msg)
if tas is not None and tas > 500: if tas is not None and tas > 500:
result &= False return False
vr = vr53(msg) vr = vr53(msg)
if vr is not None and abs(vr) > 8000: if vr is not None and abs(vr) > 8000:
result &= False return False
return result return True
def hdg53(msg): def hdg53(msg):
@ -822,38 +871,45 @@ def isBDS60(msg):
bool: True or False bool: True or False
""" """
if isnull(msg): if allzeros(msg):
return False return False
# status bit 1, 13, 24, 35, 46
d = util.hex2bin(data(msg)) d = util.hex2bin(data(msg))
result = True # status bit 1, 13, 24, 35, 46
result = result & checkbits(d, 1, 2, 12) & checkbits(d, 13, 14, 23) \ if wrongstatus(d, 1, 2, 12):
& checkbits(d, 24, 25, 34) & checkbits(d, 35, 36, 45) \ return False
& checkbits(d, 46, 47, 56)
if not result: if wrongstatus(d, 13, 14, 23):
return False
if wrongstatus(d, 24, 25, 34):
return False
if wrongstatus(d, 35, 36, 45):
return False
if wrongstatus(d, 46, 47, 56):
return False return False
ias = ias60(msg) ias = ias60(msg)
if ias is not None and ias > 500: if ias is not None and ias > 500:
result &= False return False
mach = mach60(msg) mach = mach60(msg)
if mach is not None and mach > 1: if mach is not None and mach > 1:
result &= False return False
vr_baro = vr60baro(msg) vr_baro = vr60baro(msg)
if vr_baro is not None and abs(vr_baro) > 6000: if vr_baro is not None and abs(vr_baro) > 6000:
result &= False return False
vr_ins = vr60ins(msg) vr_ins = vr60ins(msg)
if vr_ins is not None and abs(vr_ins) > 6000: if vr_ins is not None and abs(vr_ins) > 6000:
result &= False return False
return result return True
def hdg60(msg): def hdg60(msg):
@ -1038,9 +1094,10 @@ def BDS(msg):
String or None: BDS version, or possible versions, or None if nothing matches. String or None: BDS version, or possible versions, or None if nothing matches.
""" """
if isnull(msg): if allzeros(msg):
return None return None
is10 = isBDS10(msg)
is17 = isBDS17(msg) is17 = isBDS17(msg)
is20 = isBDS20(msg) is20 = isBDS20(msg)
is40 = isBDS40(msg) is40 = isBDS40(msg)
@ -1051,11 +1108,11 @@ def BDS(msg):
is60 = isBDS60(msg) is60 = isBDS60(msg)
allbds = np.array([ allbds = np.array([
"BDS17", "BDS20", "BDS40", "BDS44", "BDS44REV", "BDS10", "BDS17", "BDS20", "BDS40", "BDS44", "BDS44REV",
"BDS50", "BDS53", "BDS60" "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])) bds = ','.join(sorted(allbds[isBDS]))

View File

@ -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)

View File

@ -110,13 +110,14 @@ def altcode(msg):
return alt return alt
def gray2alt(codestr): def gray2alt(codestr):
gc500 = codestr[:8] gc500 = codestr[:8]
n500 = util.gray2int(gc500) n500 = gray2int(gc500)
# in 100-ft step must be converted first # in 100-ft step must be converted first
gc100 = codestr[8:] gc100 = codestr[8:]
n100 = util.gray2int(gc100) n100 = gray2int(gc100)
if n100 in [0, 5, 6]: if n100 in [0, 5, 6]:
return None return None
@ -129,3 +130,13 @@ def gray2alt(codestr):
alt = (n500*500 + n100*100) - 1300 alt = (n500*500 + n100*100) - 1300
return alt 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

View File

@ -97,11 +97,22 @@ def floor(x):
return int(np.floor(x)) return int(np.floor(x))
def gray2int(graystr): def is_icao_assigned(icao):
"""Convert greycode to binary (DF4, 20 altitude coding)""" """ Check whether the ICAO address is assigned (Annex 10, Vol 3)"""
num = bin2int(graystr)
num ^= (num >> 8) if (icao is None) or (not isinstance(icao, str)) or (len(icao)!=6):
num ^= (num >> 4) return False
num ^= (num >> 2)
num ^= (num >> 1) icaoint = hex2int(icao)
return num
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

View File

@ -1,5 +1,5 @@
from pyModeS import ehs from pyModeS import ehs
from pyModeS import modes_common from pyModeS import modes
def test_ehs_icao(): def test_ehs_icao():
assert ehs.icao("A0001839CA3800315800007448D9") == '400940' assert ehs.icao("A0001839CA3800315800007448D9") == '400940'
@ -7,8 +7,11 @@ def test_ehs_icao():
assert ehs.icao("A000029CFFBAA11E2004727281F1") == '4243D0' assert ehs.icao("A000029CFFBAA11E2004727281F1") == '4243D0'
def test_df20alt(): def test_modes_altcode():
assert ehs.df20alt("A02014B400000000000000F9D514") == 32300 assert ehs.modes.altcode("A02014B400000000000000F9D514") == 32300
def test_modes_idcode():
assert ehs.modes.idcode("A800292DFFBBA9383FFCEB903D01") == '1346'
def test_ehs_BDS(): def test_ehs_BDS():
@ -49,18 +52,18 @@ def test_ehs_BDS60_functions():
assert ehs.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920 assert ehs.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
def test_graycode_to_altitude(): def test_graycode_to_altitude():
assert modes_common.gray2alt('00000000010') == -1000 assert modes.gray2alt('00000000010') == -1000
assert modes_common.gray2alt('00000001010') == -500 assert modes.gray2alt('00000001010') == -500
assert modes_common.gray2alt('00000011011') == -100 assert modes.gray2alt('00000011011') == -100
assert modes_common.gray2alt('00000011010') == 0 assert modes.gray2alt('00000011010') == 0
assert modes_common.gray2alt('00000011110') == 100 assert modes.gray2alt('00000011110') == 100
assert modes_common.gray2alt('00000010011') == 600 assert modes.gray2alt('00000010011') == 600
assert modes_common.gray2alt('00000110010') == 1000 assert modes.gray2alt('00000110010') == 1000
assert modes_common.gray2alt('00001001001') == 5800 assert modes.gray2alt('00001001001') == 5800
assert modes_common.gray2alt('00011100100') == 10300 assert modes.gray2alt('00011100100') == 10300
assert modes_common.gray2alt('01100011010') == 32000 assert modes.gray2alt('01100011010') == 32000
assert modes_common.gray2alt('01110000100') == 46300 assert modes.gray2alt('01110000100') == 46300
assert modes_common.gray2alt('01010101100') == 50200 assert modes.gray2alt('01010101100') == 50200
assert modes_common.gray2alt('11011110100') == 73200 assert modes.gray2alt('11011110100') == 73200
assert modes_common.gray2alt('10000000011') == 126600 assert modes.gray2alt('10000000011') == 126600
assert modes_common.gray2alt('10000000001') == 126700 assert modes.gray2alt('10000000001') == 126700