update EHS BDS identification, add isBDS50or60() function

This commit is contained in:
Junzi Sun 2018-03-13 16:55:50 +01:00
parent 5697e5b88e
commit 4911e69171
8 changed files with 274 additions and 274 deletions

View File

@ -6,3 +6,5 @@ from .decoder import ehs
from .decoder import els from .decoder import els
from .decoder import util from .decoder import util
from .decoder import modes_common from .decoder import modes_common
from .extra import aero
from .extra import beastclient

View File

@ -18,8 +18,9 @@ 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
from . import util, modes_common import numpy as np
from scipy.stats import multivariate_normal from pyModeS.decoder import util, modes_common
from pyModeS.extra import aero
def icao(msg): def icao(msg):
return modes_common.icao(msg) return modes_common.icao(msg)
@ -972,6 +973,61 @@ def vr60ins(msg):
return roc return roc
def isBDS50or60(msg, spd_ref, trk_ref, alt_ref):
"""Use reference ground speed and trk to determine BDS50 and DBS60
Args:
msg (String): 28 bytes hexadecimal message string
spd_ref (float): reference speed (ADS-B ground speed), kts
trk_ref (float): reference track (ADS-B track angle), deg
alt_ref (float): reference altitude (ADS-B altitude), ft
Returns:
String or None: BDS version, or possible versions, or None if nothing matches.
"""
def vxy(v, angle):
vx = v * np.sin(np.deg2rad(angle))
vy = v * np.cos(np.deg2rad(angle))
return vx, vy
if not (isBDS50(msg) and isBDS60(msg)):
return None
h50 = trk50(msg)
v50 = gs50(msg)
h50 = np.nan if h50 is None else h50
v50 = np.nan if v50 is None else v50
h60 = hdg60(msg)
m60 = mach60(msg)
i60 = ias60(msg)
h60 = np.nan if h60 is None else h60
m60 = np.nan if m60 is None else m60
i60 = np.nan if i60 is None else i60
XY5 = vxy(v50*aero.kts, h50)
XY6m = vxy(aero.mach2tas(m60, alt_ref*aero.ft), h60)
XY6i = vxy(aero.cas2tas(i60*aero.kts, alt_ref*aero.ft), h60)
allbds = ['BDS50', 'BDS60', 'BDS60']
X = np.array([XY5, XY6m, XY6i])
Mu = np.array(vxy(spd_ref*aero.kts, trk_ref*aero.kts))
# compute Mahalanobis distance matrix
# Cov = [[20**2, 0], [0, 20**2]]
# mmatrix = np.sqrt(np.dot(np.dot(X-Mu, np.linalg.inv(Cov)), (X-Mu).T))
# dist = np.diag(mmatrix)
# since the covariance matrix is identity matrix,
# M-dist is same as eculidian distance
dist = np.linalg.norm(X-Mu, axis=1)
BDS = allbds[np.nanargmin(dist)]
return BDS
def BDS(msg): def BDS(msg):
"""Estimate the most likely BDS code of an message """Estimate the most likely BDS code of an message
@ -994,78 +1050,16 @@ def BDS(msg):
is53 = isBDS53(msg) is53 = isBDS53(msg)
is60 = isBDS60(msg) is60 = isBDS60(msg)
BDS = ["BDS17", "BDS20", "BDS40", "BDS44", "BDS44REV", "BDS50", "BDS53", "BDS60"] allbds = np.array([
"BDS17", "BDS20", "BDS40", "BDS44", "BDS44REV",
"BDS50", "BDS53", "BDS60"
])
isBDS = [is17, is20, is40, is44, is44rev, is50, is53, is60] isBDS = [is17, is20, is40, is44, is44rev, is50, is53, is60]
if sum(isBDS) == 0: bds = ','.join(sorted(allbds[isBDS]))
if len(bds) == 0:
return None return None
elif sum(isBDS) == 1:
return BDS[isBDS.index(True)]
else: else:
bds_ = [bds for (bds, i) in zip(BDS, isBDS) if i] return bds
return ','.join(bds_)
def Vxy(V, angle):
Vx = V*np.sin(np.deg2rad(angle))
Vy = V*np.cos(np.deg2rad(angle))
return Vx, Vy
def BDSv2(msg, SPDref=np.nan, TRKref=np.nan, ALTref=np.nan):
"""Use probabilistic method to determine the most likely BDS code of an message
Args:
msg (String): 28 bytes hexadecimal message string
SPDref (float): reference speed (for example ADS-B GS)
TRKref (float): reference track (for example ADS-B TRK)
ALTref (float): reference altitude (for example ADS-B altitude)
Returns:
String or None: BDS version, or possible versions, or None if nothing matches.
"""
BDS = pms.ehs.BDS(msg)
if type(BDS) != list:
return BDS
else:
if 'BDS53' in BDS:
BDS.remove('BDS53')
if 'BDS40' in BDS:
fms = pms.ehs.alt40fms(msg)
mcp = pms.ehs.alt40mcp(msg)
baro = pms.ehs.p40baro(msg)
if fms != None:
if (((fms % 100) <= 8) or ((fms % 100) >= 92)) and fms < 50500:
return 'BDS40'
if mcp != None:
if (((mcp % 100) <= 8) or ((mcp % 100) >= 92)) and mcp < 50500:
return 'BDS40'
if baro != None:
if (983 <= baro <= 1043): #1013 -+ 30
return 'BDS40'
if set(BDS).issubset(['BDS50', 'BDS60']):
if ~(np.isnan(SPDref) or np.isnan(TRKref) or np.isnan(ALTref)):
meanV = Vxy(SPDref, TRKref)
sigmaV = 20
covV = [[sigmaV**2, 0], [0, sigmaV**2]]
try: # Because register field is not available.
pBDS50 = multivariate_normal(meanV, covV).pdf(Vxy(pms.ehs.gs50(msg), pms.ehs.trk50(msg)))
pBDS60_1 = multivariate_normal(meanV, covV).pdf(Vxy(aero.mach2tas(pms.ehs.mach60(msg), ALTref*aero.ft)/aero.kts, pms.ehs.hdg60(msg)))
pBDS60_2 = multivariate_normal(meanV, covV).pdf(Vxy(aero.cas2tas(pms.ehs.ias60(msg)*aero.kts, ALTref*aero.ft)/aero.kts, pms.ehs.hdg60(msg)))
pBDS60 = max(pBDS60_1, pBDS60_2)
except:
return BDS
if pBDS50 + pBDS60 > 0: #Avoid None values
if pBDS50 > pBDS60:
return 'BDS50'
elif pBDS50 < pBDS60:
return 'BDS60'
return BDS

View File

@ -0,0 +1 @@
from __future__ import absolute_import, print_function, division

View File

@ -1,178 +1,178 @@
""" """
Functions for aeronautics in this module Functions for aeronautics in this module
- physical quantities always in SI units - physical quantities always in SI units
- lat,lon,course and heading in degrees - lat,lon,course and heading in degrees
International Standard Atmosphere International Standard Atmosphere
p,rho,T = atmos(H) # atmos as function of geopotential altitude H [m] p,rho,T = atmos(H) # atmos as function of geopotential altitude H [m]
a = vsound(H) # speed of sound [m/s] as function of H[m] a = vsound(H) # speed of sound [m/s] as function of H[m]
p = pressure(H) # calls atmos but retruns only pressure [Pa] p = pressure(H) # calls atmos but retruns only pressure [Pa]
T = temperature(H) # calculates temperature [K] T = temperature(H) # calculates temperature [K]
rho = density(H) # calls atmos but retruns only pressure [Pa] rho = density(H) # calls atmos but retruns only pressure [Pa]
Speed conversion at altitude H[m] in ISA: Speed conversion at altitude H[m] in ISA:
Mach = tas2mach(Vtas,H) # true airspeed (Vtas) to mach number conversion Mach = tas2mach(Vtas,H) # true airspeed (Vtas) to mach number conversion
Vtas = mach2tas(Mach,H) # true airspeed (Vtas) to mach number conversion Vtas = mach2tas(Mach,H) # true airspeed (Vtas) to mach number conversion
Vtas = eas2tas(Veas,H) # equivalent airspeed to true airspeed, H in [m] Vtas = eas2tas(Veas,H) # equivalent airspeed to true airspeed, H in [m]
Veas = tas2eas(Vtas,H) # true airspeed to equivent airspeed, H in [m] Veas = tas2eas(Vtas,H) # true airspeed to equivent airspeed, H in [m]
Vtas = cas2tas(Vcas,H) # Vcas to Vtas conversion both m/s, H in [m] Vtas = cas2tas(Vcas,H) # Vcas to Vtas conversion both m/s, H in [m]
Vcas = tas2cas(Vtas,H) # Vtas to Vcas conversion both m/s, H in [m] Vcas = tas2cas(Vtas,H) # Vtas to Vcas conversion both m/s, H in [m]
Vcas = mach2cas(Mach,H) # Mach to Vcas conversion Vcas in m/s, H in [m] Vcas = mach2cas(Mach,H) # Mach to Vcas conversion Vcas in m/s, H in [m]
Mach = cas2mach(Vcas,H) # Vcas to mach copnversion Vcas in m/s, H in [m] Mach = cas2mach(Vcas,H) # Vcas to mach copnversion Vcas in m/s, H in [m]
""" """
import numpy as np import numpy as np
"""Aero and geo Constants """ """Aero and geo Constants """
kts = 0.514444 # knot -> m/s kts = 0.514444 # knot -> m/s
ft = 0.3048 # ft -> m ft = 0.3048 # ft -> m
fpm = 0.00508 # ft/min -> m/s fpm = 0.00508 # ft/min -> m/s
inch = 0.0254 # inch -> m inch = 0.0254 # inch -> m
sqft = 0.09290304 # 1 square foot sqft = 0.09290304 # 1 square foot
nm = 1852. # nautical mile -> m nm = 1852. # nautical mile -> m
lbs = 0.453592 # pound -> kg lbs = 0.453592 # pound -> kg
g0 = 9.80665 # m/s2, Sea level gravity constant g0 = 9.80665 # m/s2, Sea level gravity constant
R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA
p0 = 101325. # Pa, air pressure, sea level ISA p0 = 101325. # Pa, air pressure, sea level ISA
rho0 = 1.225 # kg/m3, air density, sea level ISA rho0 = 1.225 # kg/m3, air density, sea level ISA
T0 = 288.15 # K, temperature, sea level ISA T0 = 288.15 # K, temperature, sea level ISA
gamma = 1.40 # cp/cv for air gamma = 1.40 # cp/cv for air
gamma1 = 0.2 # (gamma-1)/2 for air gamma1 = 0.2 # (gamma-1)/2 for air
gamma2 = 3.5 # gamma/(gamma-1) for air gamma2 = 3.5 # gamma/(gamma-1) for air
beta = -0.0065 # [K/m] ISA temp gradient below tropopause beta = -0.0065 # [K/m] ISA temp gradient below tropopause
r_earth = 6371000. # m, average earth radius r_earth = 6371000. # m, average earth radius
a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0) a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0)
def atmos(H): def atmos(H):
# H in metres # H in metres
T = np.maximum(288.15 - 0.0065 * H, 216.65) T = np.maximum(288.15 - 0.0065 * H, 216.65)
rhotrop = 1.225 * (T / 288.15)**4.256848030018761 rhotrop = 1.225 * (T / 288.15)**4.256848030018761
dhstrat = np.maximum(0., H - 11000.0) dhstrat = np.maximum(0., H - 11000.0)
rho = rhotrop * np.exp(-dhstrat / 6341.552161) rho = rhotrop * np.exp(-dhstrat / 6341.552161)
p = rho * R * T p = rho * R * T
return p, rho, T return p, rho, T
def temperature(H): def temperature(H):
p, r, T = atmos(H) p, r, T = atmos(H)
return T return T
def pressure(H): def pressure(H):
p, r, T = atmos(H) p, r, T = atmos(H)
return p return p
def density(H): def density(H):
p, r, T = atmos(H) p, r, T = atmos(H)
return r return r
def vsound(H): def vsound(H):
"""Speed of sound""" """Speed of sound"""
T = temperature(H) T = temperature(H)
a = np.sqrt(gamma * R * T) a = np.sqrt(gamma * R * T)
return a return a
def distance(lat1, lon1, lat2, lon2, H=0): def distance(lat1, lon1, lat2, lon2, H=0):
""" """
Compute spherical distance from spherical coordinates. Compute spherical distance from spherical coordinates.
For two locations in spherical coordinates For two locations in spherical coordinates
(1, theta, phi) and (1, theta', phi') (1, theta, phi) and (1, theta', phi')
cosine( arc length ) = cosine( arc length ) =
sin phi sin phi' cos(theta-theta') + cos phi cos phi' sin phi sin phi' cos(theta-theta') + cos phi cos phi'
distance = rho * arc length distance = rho * arc length
""" """
# phi = 90 - latitude # phi = 90 - latitude
phi1 = np.radians(90.0 - lat1) phi1 = np.radians(90.0 - lat1)
phi2 = np.radians(90.0 - lat2) phi2 = np.radians(90.0 - lat2)
# theta = longitude # theta = longitude
theta1 = np.radians(lon1) theta1 = np.radians(lon1)
theta2 = np.radians(lon2) theta2 = np.radians(lon2)
cos = np.sin(phi1) * np.sin(phi2) * np.cos(theta1 - theta2) \ cos = np.sin(phi1) * np.sin(phi2) * np.cos(theta1 - theta2) + np.cos(phi1) * np.cos(phi2)
+ np.cos(phi1) * np.cos(phi2) cos = np.where(cos>1, 1, cos)
arc = np.arccos(cos) arc = np.arccos(cos)
dist = arc * (r_earth + H) # meters, radius of earth dist = arc * (r_earth + H) # meters, radius of earth
return dist return dist
def bearing(lat1, lon1, lat2, lon2): def bearing(lat1, lon1, lat2, lon2):
lat1 = np.radians(lat1) lat1 = np.radians(lat1)
lon1 = np.radians(lon1) lon1 = np.radians(lon1)
lat2 = np.radians(lat2) lat2 = np.radians(lat2)
lon2 = np.radians(lon2) lon2 = np.radians(lon2)
x = np.sin(lon2-lon1) * np.cos(lat2) x = np.sin(lon2-lon1) * np.cos(lat2)
y = np.cos(lat1) * np.sin(lat2) \ y = np.cos(lat1) * np.sin(lat2) \
- np.sin(lat1) * np.cos(lat2) * np.cos(lon2-lon1) - np.sin(lat1) * np.cos(lat2) * np.cos(lon2-lon1)
initial_bearing = np.arctan2(x, y) initial_bearing = np.arctan2(x, y)
initial_bearing = np.degrees(initial_bearing) initial_bearing = np.degrees(initial_bearing)
bearing = (initial_bearing + 360) % 360 bearing = (initial_bearing + 360) % 360
return bearing return bearing
# ----------------------------------------------------- # -----------------------------------------------------
# Speed conversions, altitude H all in meters # Speed conversions, altitude H all in meters
# ----------------------------------------------------- # -----------------------------------------------------
def tas2mach(Vtas, H): def tas2mach(Vtas, H):
"""True Airspeed to Mach number""" """True Airspeed to Mach number"""
a = vsound(H) a = vsound(H)
Mach = Vtas/a Mach = Vtas/a
return Mach return Mach
def mach2tas(Mach, H): def mach2tas(Mach, H):
"""Mach number to True Airspeed""" """Mach number to True Airspeed"""
a = vsound(H) a = vsound(H)
Vtas = Mach*a Vtas = Mach*a
return Vtas return Vtas
def eas2tas(Veas, H): def eas2tas(Veas, H):
"""Equivalent Airspeed to True Airspeed""" """Equivalent Airspeed to True Airspeed"""
rho = density(H) rho = density(H)
Vtas = Veas * np.sqrt(rho0/rho) Vtas = Veas * np.sqrt(rho0/rho)
return Vtas return Vtas
def tas2eas(Vtas, H): def tas2eas(Vtas, H):
"""True Airspeed to Equivalent Airspeed""" """True Airspeed to Equivalent Airspeed"""
rho = density(H) rho = density(H)
Veas = Vtas * np.sqrt(rho/rho0) Veas = Vtas * np.sqrt(rho/rho0)
return Veas return Veas
def cas2tas(Vcas, H): def cas2tas(Vcas, H):
"""Calibrated Airspeed to True Airspeed""" """Calibrated Airspeed to True Airspeed"""
p, rho, T = atmos(H) p, rho, T = atmos(H)
qdyn = p0*((1.+rho0*Vcas*Vcas/(7.*p0))**3.5-1.) qdyn = p0*((1.+rho0*Vcas*Vcas/(7.*p0))**3.5-1.)
Vtas = np.sqrt(7.*p/rho*((1.+qdyn/p)**(2./7.)-1.)) Vtas = np.sqrt(7.*p/rho*((1.+qdyn/p)**(2./7.)-1.))
return Vtas return Vtas
def tas2cas(Vtas, H): def tas2cas(Vtas, H):
"""True Airspeed to Calibrated Airspeed""" """True Airspeed to Calibrated Airspeed"""
p, rho, T = atmos(H) p, rho, T = atmos(H)
qdyn = p*((1.+rho*Vtas*Vtas/(7.*p))**3.5-1.) qdyn = p*((1.+rho*Vtas*Vtas/(7.*p))**3.5-1.)
Vcas = np.sqrt(7.*p0/rho0*((qdyn/p0+1.)**(2./7.)-1.)) Vcas = np.sqrt(7.*p0/rho0*((qdyn/p0+1.)**(2./7.)-1.))
return Vcas return Vcas
def mach2cas(Mach, H): def mach2cas(Mach, H):
"""Mach number to Calibrated Airspeed""" """Mach number to Calibrated Airspeed"""
Vtas = mach2tas(Mach, H) Vtas = mach2tas(Mach, H)
Vcas = tas2cas(Vtas, H) Vcas = tas2cas(Vtas, H)
return Vcas return Vcas
def cas2mach(Vcas, H): def cas2mach(Vcas, H):
"""Calibrated Airspeed to Mach number""" """Calibrated Airspeed to Mach number"""
Vtas = cas2tas(Vcas, H) Vtas = cas2tas(Vcas, H)
Mach = tas2mach(Vtas, H) Mach = tas2mach(Vtas, H)
return Mach return Mach

View File

@ -5,11 +5,11 @@ import argparse
import curses import curses
import numpy as np import numpy as np
import time import time
import pyModeS as pms
from threading import Lock from threading import Lock
from client import BaseClient from pyModeS.decoder import util
from stream import Stream from pyModeS.extra.beastclient import BaseClient
from screen import Screen from pyModeS.streamer.stream import Stream
from pyModeS.streamer.screen import Screen
LOCK = Lock() LOCK = Lock()
ADSB_MSG = [] ADSB_MSG = []
@ -43,7 +43,7 @@ class ModesClient(BaseClient):
if len(msg) < 28: # only process long messages if len(msg) < 28: # only process long messages
continue continue
df = pms.df(msg) df = util.df(msg)
if df == 17 or df == 18: if df == 17 or df == 18:
local_buffer_adsb_msg.append(msg) local_buffer_adsb_msg.append(msg)

View File

@ -1,8 +1,7 @@
from __future__ import print_function, division from __future__ import absolute_import, print_function, division
import numpy as np import numpy as np
import time import time
import pyModeS as pms from pyModeS.decoder import adsb, ehs
class Stream(): class Stream():
def __init__(self, lat0, lon0): def __init__(self, lat0, lon0):
@ -32,8 +31,8 @@ class Stream():
# process adsb message # process adsb message
for t, msg in zip(adsb_ts, adsb_msgs): for t, msg in zip(adsb_ts, adsb_msgs):
icao = pms.adsb.icao(msg) icao = adsb.icao(msg)
tc = pms.adsb.typecode(msg) tc = adsb.typecode(msg)
if icao not in self.acs: if icao not in self.acs:
self.acs[icao] = { self.acs[icao] = {
@ -52,10 +51,10 @@ class Stream():
self.acs[icao]['t'] = t self.acs[icao]['t'] = t
if 1 <= tc <= 4: if 1 <= tc <= 4:
self.acs[icao]['callsign'] = pms.adsb.callsign(msg) self.acs[icao]['callsign'] = adsb.callsign(msg)
if (5 <= tc <= 8) or (tc == 19): if (5 <= tc <= 8) or (tc == 19):
vdata = pms.adsb.velocity(msg) vdata = adsb.velocity(msg)
if vdata is None: if vdata is None:
continue continue
@ -69,7 +68,7 @@ class Stream():
self.acs[icao]['tv'] = t self.acs[icao]['tv'] = t
if (5 <= tc <= 18): if (5 <= tc <= 18):
oe = pms.adsb.oe_flag(msg) oe = adsb.oe_flag(msg)
self.acs[icao][oe] = msg self.acs[icao][oe] = msg
self.acs[icao]['t'+str(oe)] = t self.acs[icao]['t'+str(oe)] = t
@ -77,12 +76,12 @@ class Stream():
# use single message decoding # use single message decoding
rlat = self.acs[icao]['lat'] rlat = self.acs[icao]['lat']
rlon = self.acs[icao]['lon'] rlon = self.acs[icao]['lon']
latlon = pms.adsb.position_with_ref(msg, rlat, rlon) latlon = adsb.position_with_ref(msg, rlat, rlon)
elif ('t0' in self.acs[icao]) and ('t1' in self.acs[icao]) and \ elif ('t0' in self.acs[icao]) and ('t1' in self.acs[icao]) and \
(abs(self.acs[icao]['t0'] - self.acs[icao]['t1']) < 10): (abs(self.acs[icao]['t0'] - self.acs[icao]['t1']) < 10):
# use multi message decoding # use multi message decoding
try: try:
latlon = pms.adsb.position( latlon = adsb.position(
self.acs[icao][0], self.acs[icao][0],
self.acs[icao][1], self.acs[icao][1],
self.acs[icao]['t0'], self.acs[icao]['t0'],
@ -99,29 +98,29 @@ class Stream():
self.acs[icao]['tpos'] = t self.acs[icao]['tpos'] = t
self.acs[icao]['lat'] = latlon[0] self.acs[icao]['lat'] = latlon[0]
self.acs[icao]['lon'] = latlon[1] self.acs[icao]['lon'] = latlon[1]
self.acs[icao]['alt'] = pms.adsb.altitude(msg) self.acs[icao]['alt'] = adsb.altitude(msg)
local_updated_acs_buffer.append(icao) local_updated_acs_buffer.append(icao)
# process ehs message # process ehs message
for t, msg in zip(ehs_ts, ehs_msgs): for t, msg in zip(ehs_ts, ehs_msgs):
icao = pms.ehs.icao(msg) icao = ehs.icao(msg)
if icao not in self.acs: if icao not in self.acs:
continue continue
bds = pms.ehs.BDS(msg) bds = ehs.BDS(msg)
if bds == 'BDS50': if bds == 'BDS50':
tas = pms.ehs.tas50(msg) tas = ehs.tas50(msg)
if tas: if tas:
self.acs[icao]['t50'] = t self.acs[icao]['t50'] = t
self.acs[icao]['tas'] = tas self.acs[icao]['tas'] = tas
elif bds == 'BDS60': elif bds == 'BDS60':
ias = pms.ehs.ias60(msg) ias = ehs.ias60(msg)
hdg = pms.ehs.hdg60(msg) hdg = ehs.hdg60(msg)
mach = pms.ehs.mach60(msg) mach = ehs.mach60(msg)
if ias or hdg or mach: if ias or hdg or mach:
self.acs[icao]['t60'] = t self.acs[icao]['t60'] = t

View File

@ -14,10 +14,14 @@ def test_df20alt():
def test_ehs_BDS(): def test_ehs_BDS():
assert ehs.BDS("A0001838201584F23468207CDFA5") == 'BDS20' assert ehs.BDS("A0001838201584F23468207CDFA5") == 'BDS20'
assert ehs.BDS("A0001839CA3800315800007448D9") == 'BDS40' assert ehs.BDS("A0001839CA3800315800007448D9") == 'BDS40'
# assert ehs.BDS("A000031DBAA9DD18622C441330E9") == 'BDS44'
assert ehs.BDS("A000139381951536E024D4CCF6B5") == 'BDS50' assert ehs.BDS("A000139381951536E024D4CCF6B5") == 'BDS50'
assert ehs.BDS("A00004128F39F91A7E27C46ADC21") == 'BDS60' assert ehs.BDS("A00004128F39F91A7E27C46ADC21") == 'BDS60'
def test_ehs_isBDS50or60():
assert ehs.isBDS50or60("A0001838201584F23468207CDFA5", 0, 0, 0) == None
assert ehs.isBDS50or60("A0000000FFDA9517000464000000", 182, 237, 1250) == 'BDS50'
assert ehs.isBDS50or60("A0000000919A5927E23444000000", 413, 54, 18700) == 'BDS60'
def test_ehs_BDS20_callsign(): def test_ehs_BDS20_callsign():
assert ehs.callsign("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_' assert ehs.callsign("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
assert ehs.callsign("A0001993202422F2E37CE038738E") == 'IBK2873_' assert ehs.callsign("A0001993202422F2E37CE038738E") == 'IBK2873_'