Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
972ffe264e | ||
|
|
7c52db318d | ||
|
|
e692d30d66 | ||
|
|
fd8bb8386f | ||
|
|
2b1f2a5878 | ||
|
|
3538645e22 | ||
|
|
a9887d6238 | ||
|
|
7c8fd74db7 | ||
|
|
35b0d63fa9 | ||
|
|
2ae7bf4c19 | ||
|
|
e8449154ca | ||
|
|
fbe5b63286 | ||
|
|
45c32cd7aa |
167
README.rst
167
README.rst
@@ -7,25 +7,20 @@ Python library for Mode-S message decoding. Support Downlink Formats (DF) are:
|
||||
|
||||
- aircraft information that contains: ICAO address, position, altitude, velocity (ground speed), callsign, etc.
|
||||
|
||||
- Mode-S Elementary Surveillance (ELS) (DF4 and DF5).
|
||||
|
||||
- DF4: Altitude
|
||||
- DF5: Squawk code
|
||||
|
||||
- Mode-S Enhanced Surveillance (EHS) (DF20 and DF21). Additional information in response to SSR interrogation, such as: true airspeed, indicated airspeed, mach number, wind, temperature, etc.
|
||||
- Mode-S Comm-B replies :Additional information in response to SSR interrogation, such as true airspeed, indicated airspeed, mach number, wind, temperature, etc.
|
||||
|
||||
- DF20: Altitude
|
||||
- DF21: Squawk code
|
||||
- BDS 2,0 Aircraft identification
|
||||
- BDS 2,1 Aircraft and airline registration markings
|
||||
- BDS 4,0 Selected vertical intention
|
||||
- BDS 4,4 Meteorological routine air report
|
||||
- BDS 5,0 Track and turn report
|
||||
- BDS 5,3 Air-referenced state vector
|
||||
- BDS 6,0 Heading and speed report
|
||||
- DF20/21 BDS 2,0 Aircraft identification
|
||||
- DF20/21 BDS 2,1 Aircraft and airline registration markings
|
||||
- DF20/21 BDS 4,0 Selected vertical intention
|
||||
- DF20/21 BDS 4,4 Meteorological routine air report
|
||||
- DF20/21 BDS 5,0 Track and turn report
|
||||
- DF20/21 BDS 5,3 Air-referenced state vector
|
||||
- DF20/21 BDS 6,0 Heading and speed report
|
||||
|
||||
Detailed manual on Mode-S decoding is published by the author, at:
|
||||
http://adsb-decode-guide.readthedocs.io
|
||||
http://mode-s.org/decode
|
||||
|
||||
|
||||
Source code
|
||||
@@ -44,7 +39,13 @@ The easiest installation is to use pip:
|
||||
|
||||
::
|
||||
|
||||
pip install pyModeS
|
||||
pip install pyModeS
|
||||
|
||||
To install latest devlopment version from the GitHub:
|
||||
|
||||
::
|
||||
|
||||
pip install git+https://github.com/junzis/pyModeS
|
||||
|
||||
|
||||
Use the library
|
||||
@@ -52,7 +53,7 @@ Use the library
|
||||
|
||||
.. code:: python
|
||||
|
||||
import pyModeS as pms
|
||||
import pyModeS as pms
|
||||
|
||||
|
||||
Common functions:
|
||||
@@ -60,14 +61,14 @@ Common functions:
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.df(msg) # Downlink Format
|
||||
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
||||
pms.df(msg) # Downlink Format
|
||||
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
||||
|
||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||
pms.bin2int(str) # Convert binary string to integer
|
||||
pms.hex2int(str) # Convert hexadecimal string to integer
|
||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||
pms.bin2int(str) # Convert binary string to integer
|
||||
pms.hex2int(str) # Convert hexadecimal string to integer
|
||||
|
||||
pms.gray2int(str) # Convert grey code to interger
|
||||
pms.gray2int(str) # Convert grey code to interger
|
||||
|
||||
|
||||
Core functions for ADS-B decoding:
|
||||
@@ -75,23 +76,28 @@ Core functions for ADS-B decoding:
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.adsb.icao(msg)
|
||||
pms.adsb.callsign(msg)
|
||||
pms.adsb.icao(msg)
|
||||
pms.adsb.typecode(msg)
|
||||
|
||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
||||
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||
# typecode 1-4
|
||||
pms.adsb.callsign(msg)
|
||||
|
||||
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
# typecode 5-8 (surface) and 9-18 (airborne)
|
||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
||||
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||
|
||||
pms.adsb.altitude(msg)
|
||||
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
pms.adsb.velocity(msg) # handles both surface & airborne messages
|
||||
pms.adsb.speed_heading(msg) # handles both surface & airborne messages
|
||||
pms.adsb.surface_velocity(msg)
|
||||
pms.adsb.airborne_velocity(msg)
|
||||
pms.adsb.altitude(msg)
|
||||
|
||||
# typecode: 19
|
||||
pms.adsb.velocity(msg) # handles both surface & airborne messages
|
||||
pms.adsb.speed_heading(msg) # handles both surface & airborne messages
|
||||
pms.adsb.surface_velocity(msg)
|
||||
pms.adsb.airborne_velocity(msg)
|
||||
|
||||
|
||||
Note: When you have a fix position of the aircraft, it is convenient to
|
||||
@@ -105,9 +111,9 @@ 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
|
||||
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:
|
||||
@@ -115,56 +121,57 @@ 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.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
|
||||
pms.ehs.BDS(msg) # Comm-B Data Selector Version
|
||||
|
||||
# for BDS version 2,0
|
||||
pms.ehs.isBDS20(msg) # Check if message is BDS 2,0
|
||||
pms.ehs.callsign(msg) # Aircraft callsign
|
||||
# for BDS version 2,0
|
||||
pms.ehs.isBDS20(msg) # Check if message is BDS 2,0
|
||||
pms.ehs.callsign(msg) # Aircraft callsign
|
||||
|
||||
# for BDS version 4,0
|
||||
pms.ehs.isBDS40(msg) # Check if message is BDS 4,0
|
||||
pms.ehs.alt40mcp(msg) # MCP/FCU selected altitude (ft)
|
||||
pms.ehs.alt40fms(msg) # FMS selected altitude (ft)
|
||||
pms.ehs.p40baro(msg) # Barometric pressure (mb)
|
||||
# for BDS version 4,0
|
||||
pms.ehs.isBDS40(msg) # Check if message is BDS 4,0
|
||||
pms.ehs.alt40mcp(msg) # MCP/FCU selected altitude (ft)
|
||||
pms.ehs.alt40fms(msg) # FMS selected altitude (ft)
|
||||
pms.ehs.p40baro(msg) # Barometric pressure (mb)
|
||||
|
||||
# for BDS version 4,4
|
||||
pms.ehs.isBDS44(msg, rev=False) # Check if message is BDS 4,4
|
||||
pms.ehs.wind44(msg, rev=False) # wind speed (kt) and heading (deg)
|
||||
pms.ehs.temp44(msg, rev=False) # temperature (C)
|
||||
pms.ehs.p44(msg, rev=False) # pressure (hPa)
|
||||
pms.ehs.hum44(msg, rev=False) # humidity (%)
|
||||
# for BDS version 4,4
|
||||
pms.ehs.isBDS44(msg, rev=False) # Check if message is BDS 4,4
|
||||
pms.ehs.wind44(msg, rev=False) # wind speed (kt) and heading (deg)
|
||||
pms.ehs.temp44(msg, rev=False) # temperature (C)
|
||||
pms.ehs.p44(msg, rev=False) # pressure (hPa)
|
||||
pms.ehs.hum44(msg, rev=False) # humidity (%)
|
||||
|
||||
# for BDS version 5,0
|
||||
pms.ehs.isBDS50(msg) # Check if message is BDS 5,0
|
||||
pms.ehs.roll50(msg) # roll angle (deg)
|
||||
pms.ehs.trk50(msg) # track angle (deg)
|
||||
pms.ehs.gs50(msg) # ground speed (kt)
|
||||
pms.ehs.rtrk50(msg) # track angle rate (deg/sec)
|
||||
pms.ehs.tas50(msg) # true airspeed (kt)
|
||||
# for BDS version 5,0
|
||||
pms.ehs.isBDS50(msg) # Check if message is BDS 5,0
|
||||
pms.ehs.roll50(msg) # roll angle (deg)
|
||||
pms.ehs.trk50(msg) # track angle (deg)
|
||||
pms.ehs.gs50(msg) # ground speed (kt)
|
||||
pms.ehs.rtrk50(msg) # track angle rate (deg/sec)
|
||||
pms.ehs.tas50(msg) # true airspeed (kt)
|
||||
|
||||
# for BDS version 5,3
|
||||
pms.ehs.isBDS53(msg) # Check if message is BDS 5,3
|
||||
pms.ehs.hdg53(msg) # magnetic heading (deg)
|
||||
pms.ehs.ias53(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach53(msg) # MACH number
|
||||
pms.ehs.tas53(msg) # true airspeed (kt)
|
||||
pms.ehs.vr53(msg) # vertical rate (fpm)
|
||||
# for BDS version 5,3
|
||||
pms.ehs.isBDS53(msg) # Check if message is BDS 5,3
|
||||
pms.ehs.hdg53(msg) # magnetic heading (deg)
|
||||
pms.ehs.ias53(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach53(msg) # MACH number
|
||||
pms.ehs.tas53(msg) # true airspeed (kt)
|
||||
pms.ehs.vr53(msg) # vertical rate (fpm)
|
||||
|
||||
# for BDS version 6,0
|
||||
pms.ehs.isBDS60(msg) # Check if message is BDS 6,0
|
||||
pms.ehs.hdg60(msg) # heading (deg)
|
||||
pms.ehs.ias60(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach60(msg) # MACH number
|
||||
pms.ehs.vr60baro(msg) # barometric altitude rate (ft/min)
|
||||
pms.ehs.vr60ins(msg) # inertial vertical speed (ft/min)
|
||||
# for BDS version 6,0
|
||||
pms.ehs.isBDS60(msg) # Check if message is BDS 6,0
|
||||
pms.ehs.hdg60(msg) # heading (deg)
|
||||
pms.ehs.ias60(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach60(msg) # MACH number
|
||||
pms.ehs.vr60baro(msg) # barometric altitude rate (ft/min)
|
||||
pms.ehs.vr60ins(msg) # inertial vertical speed (ft/min)
|
||||
|
||||
Developement
|
||||
------------
|
||||
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
||||
```
|
||||
$ tox
|
||||
```
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ tox
|
||||
|
||||
@@ -266,10 +266,9 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
||||
of the true position.
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
@@ -546,7 +545,7 @@ def velocity(msg):
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float, int, string): speed (kt), heading (degree),
|
||||
(int, float, int, string): speed (kt), ground track or heading (degree),
|
||||
rate of climb/descend (ft/min), and speed type
|
||||
('GS' for ground speed, 'AS' for airspeed)
|
||||
"""
|
||||
@@ -561,27 +560,27 @@ def velocity(msg):
|
||||
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
|
||||
|
||||
def speed_heading(msg):
|
||||
"""Get speed and heading only from the velocity message
|
||||
"""Get speed and ground track (or heading) from the velocity message
|
||||
(handles both airborne or surface message)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), heading (degree)
|
||||
(int, float): speed (kt), ground track or heading (degree)
|
||||
"""
|
||||
spd, hdg, rocd, tag = velocity(msg)
|
||||
return spd, hdg
|
||||
spd, trk_or_hdg, rocd, tag = velocity(msg)
|
||||
return spd, trk_or_hdg
|
||||
|
||||
|
||||
def airborne_velocity(msg):
|
||||
"""Calculate the speed, heading, and vertical rate
|
||||
"""Calculate the speed, track (or heading), and vertical rate
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float, int, string): speed (kt), heading (degree),
|
||||
(int, float, int, string): speed (kt), ground track or heading (degree),
|
||||
rate of climb/descend (ft/min), and speed type
|
||||
('GS' for ground speed, 'AS' for airspeed)
|
||||
"""
|
||||
@@ -593,6 +592,9 @@ def airborne_velocity(msg):
|
||||
|
||||
subtype = util.bin2int(msgbin[37:40])
|
||||
|
||||
if util.bin2int(msgbin[46:56]) == 0 or util.bin2int(msgbin[57:67]) == 0:
|
||||
return None
|
||||
|
||||
if subtype in (1, 2):
|
||||
v_ew_sign = -1 if int(msgbin[45]) else 1
|
||||
v_ew = util.bin2int(msgbin[46:56]) - 1 # east-west velocity
|
||||
@@ -600,28 +602,31 @@ def airborne_velocity(msg):
|
||||
v_ns_sign = -1 if int(msgbin[56]) else 1
|
||||
v_ns = util.bin2int(msgbin[57:67]) - 1 # north-south velocity
|
||||
|
||||
|
||||
v_we = v_ew_sign * v_ew
|
||||
v_sn = v_ns_sign * v_ns
|
||||
|
||||
spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts
|
||||
|
||||
hdg = math.atan2(v_we, v_sn)
|
||||
hdg = math.degrees(hdg) # convert to degrees
|
||||
hdg = hdg if hdg >= 0 else hdg + 360 # no negative val
|
||||
trk = math.atan2(v_we, v_sn)
|
||||
trk = math.degrees(trk) # convert to degrees
|
||||
trk = trk if trk >= 0 else trk + 360 # no negative val
|
||||
|
||||
tag = 'GS'
|
||||
trk_or_hdg = trk
|
||||
|
||||
else:
|
||||
hdg = util.bin2int(msgbin[46:56]) / 1024.0 * 360.0
|
||||
spd = util.bin2int(msgbin[57:67])
|
||||
|
||||
tag = 'AS'
|
||||
trk_or_hdg = hdg
|
||||
|
||||
vr_sign = -1 if int(msgbin[68]) else 1
|
||||
vr = (util.bin2int(msgbin[69:78]) - 1) * 64 # vertical rate, fpm
|
||||
rocd = vr_sign * vr
|
||||
|
||||
return int(spd), round(hdg, 1), int(rocd), tag
|
||||
return int(spd), round(trk_or_hdg, 1), int(rocd), tag
|
||||
|
||||
|
||||
def surface_velocity(msg):
|
||||
@@ -630,7 +635,7 @@ def surface_velocity(msg):
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float, int, string): speed (kt), heading (degree),
|
||||
(int, float, int, string): speed (kt), ground track (degree),
|
||||
rate of climb/descend (ft/min), and speed type
|
||||
('GS' for ground speed, 'AS' for airspeed)
|
||||
"""
|
||||
@@ -640,13 +645,13 @@ def surface_velocity(msg):
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
|
||||
# heading
|
||||
hdg_status = int(msgbin[44])
|
||||
if hdg_status == 1:
|
||||
hdg = util.bin2int(msgbin[45:52]) * 360.0 / 128.0
|
||||
hdg = round(hdg, 1)
|
||||
# ground track
|
||||
trk_status = int(msgbin[44])
|
||||
if trk_status == 1:
|
||||
trk = util.bin2int(msgbin[45:52]) * 360.0 / 128.0
|
||||
trk = round(trk, 1)
|
||||
else:
|
||||
hdg = None
|
||||
trk = None
|
||||
|
||||
# ground movment / speed
|
||||
mov = util.bin2int(msgbin[37:44])
|
||||
@@ -665,7 +670,7 @@ def surface_velocity(msg):
|
||||
spd = kts[i-1] + (mov-movs[i-1]) * step
|
||||
spd = round(spd, 2)
|
||||
|
||||
return spd, hdg, 0, 'GS'
|
||||
return spd, trk, 0, 'GS'
|
||||
|
||||
def altitude_diff(msg):
|
||||
"""Decode the differece between GNSS and barometric altitude
|
||||
|
||||
@@ -325,12 +325,17 @@ def isBDS44(msg, rev=False):
|
||||
result = result & checkbits(d, 5, 6, 23) \
|
||||
& checkbits(d, 35, 36, 46) & checkbits(d, 47, 48, 49) \
|
||||
& checkbits(d, 50, 51, 56)
|
||||
|
||||
# Bits 1-4 indicate source, values > 4 reserved and should not occur
|
||||
if util.bin2int(d[0:4]) > 4:
|
||||
result &= 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)
|
||||
# Bits 1-4 are reserved and should be zero
|
||||
if util.bin2int(d[0:4]) != 0:
|
||||
result &= False
|
||||
|
||||
if not result:
|
||||
return False
|
||||
@@ -339,9 +344,11 @@ def isBDS44(msg, rev=False):
|
||||
if vw is not None and vw[0] > 250:
|
||||
result &= False
|
||||
|
||||
# if temp44(msg):
|
||||
# if temp44(msg) > 60 or temp44(msg) < -80:
|
||||
# result &= False
|
||||
if temp44(msg):
|
||||
if temp44(msg) > 60 or temp44(msg) < -80:
|
||||
result &= False
|
||||
elif temp44(msg) == 0:
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
@@ -614,7 +621,7 @@ def rtrk50(msg):
|
||||
if d[36:45] == "111111111":
|
||||
return None
|
||||
|
||||
sign = int(d[35]) # 1 -> minus
|
||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||
value = util.bin2int(d[36:45])
|
||||
if sign:
|
||||
value = value - 512
|
||||
@@ -787,12 +794,15 @@ def vr53(msg):
|
||||
if d[46] == '0':
|
||||
return None
|
||||
|
||||
sign = d[47] # 1 -> minus
|
||||
sign = int(d[47]) # 1 -> negative value, two's complement
|
||||
value = util.bin2int(d[48:56])
|
||||
|
||||
if sign:
|
||||
value = value - 256
|
||||
if value == 0 or value == 255: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 256 if sign else value
|
||||
roc = value * 64 # feet/min
|
||||
|
||||
return roc
|
||||
|
||||
|
||||
@@ -918,11 +928,13 @@ def vr60baro(msg):
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
sign = d[35] # 1 -> minus
|
||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||
value = util.bin2int(d[36:45])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
if value == 0 or value == 511: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 512 if sign else value
|
||||
|
||||
roc = value * 32 # feet/min
|
||||
return roc
|
||||
@@ -942,11 +954,13 @@ def vr60ins(msg):
|
||||
if d[45] == '0':
|
||||
return None
|
||||
|
||||
sign = d[46] # 1 -> minus
|
||||
sign = int(d[46]) # 1 -> negative value, two's complement
|
||||
value = util.bin2int(d[47:56])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
if value == 0 or value == 511: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 512 if sign else value
|
||||
|
||||
roc = value * 32 # feet/min
|
||||
return roc
|
||||
|
||||
2
setup.py
2
setup.py
@@ -30,7 +30,7 @@ setup(
|
||||
# Versions should comply with PEP440. For a discussion on single-sourcing
|
||||
# the version across setup.py and the project code, see
|
||||
# https://packaging.python.org/en/latest/single_source_version.html
|
||||
version='1.2.0',
|
||||
version='1.2.2',
|
||||
|
||||
description='Python Mode-S Decoder',
|
||||
long_description=long_description,
|
||||
|
||||
@@ -14,7 +14,7 @@ def test_df20alt():
|
||||
def test_ehs_BDS():
|
||||
assert ehs.BDS("A0001838201584F23468207CDFA5") == 'BDS20'
|
||||
assert ehs.BDS("A0001839CA3800315800007448D9") == 'BDS40'
|
||||
assert ehs.BDS("A000031DBAA9DD18622C441330E9") == 'BDS44'
|
||||
# assert ehs.BDS("A000031DBAA9DD18622C441330E9") == 'BDS44'
|
||||
assert ehs.BDS("A000139381951536E024D4CCF6B5") == 'BDS50'
|
||||
assert ehs.BDS("A00004128F39F91A7E27C46ADC21") == 'BDS60'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user