major update; adsb restructure; rename combining util/modes to common.
This commit is contained in:
parent
ef8f4b3b7f
commit
362b92de7a
94
README.rst
94
README.rst
@ -18,6 +18,7 @@ Python library for Mode-S message decoding. Support Downlink Formats (DF) are:
|
|||||||
- DF21: Squawk code
|
- DF21: Squawk code
|
||||||
- BDS 2,0 Aircraft identification
|
- BDS 2,0 Aircraft identification
|
||||||
- BDS 2,1 Aircraft and airline registration markings
|
- BDS 2,1 Aircraft and airline registration markings
|
||||||
|
- BDS 3,0 ACAS active resolution advisory
|
||||||
- BDS 4,0 Selected vertical intention
|
- BDS 4,0 Selected vertical intention
|
||||||
- BDS 4,4 Meteorological routine air report
|
- BDS 4,4 Meteorological routine air report
|
||||||
- BDS 5,0 Track and turn report
|
- BDS 5,0 Track and turn report
|
||||||
@ -30,6 +31,7 @@ http://adsb-decode-guide.readthedocs.io
|
|||||||
|
|
||||||
New features in v2.0
|
New features in v2.0
|
||||||
---------------------
|
---------------------
|
||||||
|
- New structure of the libraries
|
||||||
- ADS-B and EHS data streaming
|
- ADS-B and EHS data streaming
|
||||||
- Active aircraft viewing (in terminal)
|
- Active aircraft viewing (in terminal)
|
||||||
- More advanced BDS identification in Enhanced Mode-S
|
- More advanced BDS identification in Enhanced Mode-S
|
||||||
@ -39,22 +41,16 @@ New features in v2.0
|
|||||||
Source code
|
Source code
|
||||||
-----------
|
-----------
|
||||||
Checkout and contribute to this open source project at:
|
Checkout and contribute to this open source project at:
|
||||||
https://github.com/junzis/pyModeS
|
https://github.com/junzis/pyModeS/tree/dev-2.0
|
||||||
|
|
||||||
API documentation at:
|
API documentation at:
|
||||||
http://pymodes.readthedocs.io
|
http://pymodes.readthedocs.io
|
||||||
|
[To be updated]
|
||||||
|
|
||||||
|
|
||||||
Install
|
Install
|
||||||
-------
|
-------
|
||||||
|
|
||||||
The easiest installation (stable version of 1.x) is to use pip:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
pip install pyModeS
|
|
||||||
|
|
||||||
|
|
||||||
To install latest development version (dev-2.0) from the GitHub:
|
To install latest development version (dev-2.0) from the GitHub:
|
||||||
|
|
||||||
::
|
::
|
||||||
@ -76,13 +72,13 @@ Common functions:
|
|||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
pms.df(msg) # Downlink Format
|
pms.df(msg) # Downlink Format
|
||||||
|
pms.icao(msg) # Infer the ICAO address from the message
|
||||||
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
||||||
|
|
||||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||||
pms.bin2int(str) # Convert binary string to integer
|
pms.bin2int(str) # Convert binary string to integer
|
||||||
pms.hex2int(str) # Convert hexadecimal 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:
|
Core functions for ADS-B decoding:
|
||||||
@ -96,7 +92,7 @@ Core functions for ADS-B decoding:
|
|||||||
# typecode 1-4
|
# typecode 1-4
|
||||||
pms.adsb.callsign(msg)
|
pms.adsb.callsign(msg)
|
||||||
|
|
||||||
# typecode 5-8 (surface) and 9-18 (airborne)
|
# typecode 5-8 (surface), 9-18 (airborne, barometric height), and 9-18 (airborne, GNSS height)
|
||||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
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.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.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||||
@ -120,68 +116,74 @@ use `position_with_ref()` method to decode with only one position message
|
|||||||
messages. But the reference position shall be with in 180NM (airborne)
|
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:
|
|
||||||
********************************
|
Common Mode-S functions
|
||||||
|
************************
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
pms.els.icao(msg) # ICAO address
|
pms.icao(msg) # Infer the ICAO address from the message
|
||||||
pms.els.df4alt(msg) # Altitude from any DF4 message
|
pms.bds.infer(msg) # Infer the Modes-S BDS code
|
||||||
pms.ehs.df5id(msg) # Squawk code from any DF5 message
|
|
||||||
|
pms.bds.is10(msg) # check if BDS is 1,0 explicitly
|
||||||
|
pms.bds.is17(msg) # check if BDS is 1,7 explicitly
|
||||||
|
pms.bds.is20(msg) # check if BDS is 2,0 explicitly
|
||||||
|
pms.bds.is30(msg) # check if BDS is 3,0 explicitly
|
||||||
|
pms.bds.is40(msg) # check if BDS is 4,0 explicitly
|
||||||
|
pms.bds.is44(msg) # check if BDS is 4,4 explicitly
|
||||||
|
pms.bds.is50(msg) # check if BDS is 5,0 explicitly
|
||||||
|
pms.bds.is60(msg) # check if BDS is 6,0 explicitly
|
||||||
|
|
||||||
|
# check if BDS is 5,0 or 6,0, give reference spd, trk, alt (from ADS-B)
|
||||||
|
pms.bds.is50or60(msg, spd_ref, trk_ref, alt_ref)
|
||||||
|
|
||||||
|
|
||||||
Core functions for EHS decoding:
|
Mode-S elementary surveillance (ELS)
|
||||||
********************************
|
*************************************
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
pms.ehs.icao(msg) # ICAO address
|
pms.els.ovc10(msg) # overlay capability, BDS 1,0
|
||||||
pms.ehs.df20alt(msg) # Altitude from any DF20 message
|
pms.els.cap17(msg) # GICB capability, BDS 1,7
|
||||||
pms.ehs.df21id(msg) # Squawk code from any DF21 message
|
pms.els.cs20(msg) # callsign, BDS 2,0
|
||||||
|
|
||||||
pms.ehs.BDS(msg) # Comm-B Data Selector Version
|
|
||||||
|
|
||||||
# for BDS version 2,0
|
Mode-S enhanced surveillance (EHS)
|
||||||
pms.ehs.isBDS20(msg) # Check if message is BDS 2,0
|
***********************************
|
||||||
pms.ehs.callsign(msg) # Aircraft callsign
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
# for BDS version 4,0
|
# 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.alt40mcp(msg) # MCP/FCU selected altitude (ft)
|
||||||
pms.ehs.alt40fms(msg) # FMS selected altitude (ft)
|
pms.ehs.alt40fms(msg) # FMS selected altitude (ft)
|
||||||
pms.ehs.p40baro(msg) # Barometric pressure (mb)
|
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 5,0
|
# 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.roll50(msg) # roll angle (deg)
|
||||||
pms.ehs.trk50(msg) # track angle (deg)
|
pms.ehs.trk50(msg) # track angle (deg)
|
||||||
pms.ehs.gs50(msg) # ground speed (kt)
|
pms.ehs.gs50(msg) # ground speed (kt)
|
||||||
pms.ehs.rtrk50(msg) # track angle rate (deg/sec)
|
pms.ehs.rtrk50(msg) # track angle rate (deg/sec)
|
||||||
pms.ehs.tas50(msg) # true airspeed (kt)
|
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 6,0
|
# for BDS version 6,0
|
||||||
pms.ehs.isBDS60(msg) # Check if message is BDS 6,0
|
|
||||||
pms.ehs.hdg60(msg) # heading (deg)
|
pms.ehs.hdg60(msg) # heading (deg)
|
||||||
pms.ehs.ias60(msg) # indicated airspeed (kt)
|
pms.ehs.ias60(msg) # indicated airspeed (kt)
|
||||||
pms.ehs.mach60(msg) # MACH number
|
pms.ehs.mach60(msg) # MACH number
|
||||||
pms.ehs.vr60baro(msg) # barometric altitude rate (ft/min)
|
pms.ehs.vr60baro(msg) # barometric altitude rate (ft/min)
|
||||||
pms.ehs.vr60ins(msg) # inertial vertical speed (ft/min)
|
pms.ehs.vr60ins(msg) # inertial vertical speed (ft/min)
|
||||||
|
|
||||||
|
|
||||||
|
Meteorological routine air report (MRAR) [Experimental]
|
||||||
|
*******************************************************
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# for BDS version 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 (%)
|
||||||
|
|
||||||
Developement
|
Developement
|
||||||
------------
|
------------
|
||||||
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
|
|
||||||
from .decoder.util import *
|
from .decoder.common import *
|
||||||
from .decoder import adsb
|
from .decoder import adsb
|
||||||
from .decoder import els
|
from .decoder import els
|
||||||
from .decoder import ehs
|
from .decoder import ehs
|
||||||
from .decoder import util
|
from .decoder import common
|
||||||
from .decoder import modes
|
|
||||||
from .decoder import bds
|
from .decoder import bds
|
||||||
from .extra import aero
|
from .extra import aero
|
||||||
from .extra import beastclient
|
from .extra import beastclient
|
||||||
|
@ -14,159 +14,26 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A python package for decoding ABS-D messages.
|
The wrapper for decoding ADS-B messages
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
import math
|
from pyModeS.decoder import common
|
||||||
from . import util
|
# from pyModeS.decoder.bds import bds05, bds06, bds09
|
||||||
|
|
||||||
|
from pyModeS.decoder.bds.bds05 import airborne_position, airborne_position_with_ref, altitude
|
||||||
|
from pyModeS.decoder.bds.bds06 import surface_position, surface_position_with_ref, surface_velocity
|
||||||
|
from pyModeS.decoder.bds.bds08 import category, callsign
|
||||||
|
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
|
||||||
|
|
||||||
def df(msg):
|
def df(msg):
|
||||||
"""Get the downlink format (DF) number
|
return common.df(msg)
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: DF number
|
|
||||||
"""
|
|
||||||
return util.df(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def icao(msg):
|
def icao(msg):
|
||||||
"""Get the ICAO 24 bits address, bytes 3 to 8.
|
return common.icao(msg)
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
String: ICAO address in 6 bytes hexadecimal string
|
|
||||||
"""
|
|
||||||
return msg[2:8]
|
|
||||||
|
|
||||||
|
|
||||||
def data(msg):
|
|
||||||
"""Return the data frame in the message, bytes 9 to 22"""
|
|
||||||
return msg[8:22]
|
|
||||||
|
|
||||||
|
|
||||||
def typecode(msg):
|
def typecode(msg):
|
||||||
"""Type code of ADS-B message
|
return common.typecode(msg)
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: type code number
|
|
||||||
"""
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
return util.bin2int(msgbin[32:37])
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
|
||||||
# Aircraft Identification
|
|
||||||
# ---------------------------------------------
|
|
||||||
def category(msg):
|
|
||||||
"""Aircraft category number
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: category number
|
|
||||||
"""
|
|
||||||
|
|
||||||
if typecode(msg) < 1 or typecode(msg) > 4:
|
|
||||||
raise RuntimeError("%s: Not a identification message" % msg)
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
return util.bin2int(msgbin[5:8])
|
|
||||||
|
|
||||||
|
|
||||||
def callsign(msg):
|
|
||||||
"""Aircraft callsign
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
string: callsign
|
|
||||||
"""
|
|
||||||
|
|
||||||
if typecode(msg) < 1 or typecode(msg) > 4:
|
|
||||||
raise RuntimeError("%s: Not a identification message" % msg)
|
|
||||||
|
|
||||||
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
csbin = msgbin[40:96]
|
|
||||||
|
|
||||||
cs = ''
|
|
||||||
cs += chars[util.bin2int(csbin[0:6])]
|
|
||||||
cs += chars[util.bin2int(csbin[6:12])]
|
|
||||||
cs += chars[util.bin2int(csbin[12:18])]
|
|
||||||
cs += chars[util.bin2int(csbin[18:24])]
|
|
||||||
cs += chars[util.bin2int(csbin[24:30])]
|
|
||||||
cs += chars[util.bin2int(csbin[30:36])]
|
|
||||||
cs += chars[util.bin2int(csbin[36:42])]
|
|
||||||
cs += chars[util.bin2int(csbin[42:48])]
|
|
||||||
|
|
||||||
# clean string, remove spaces and marks, if any.
|
|
||||||
# cs = cs.replace('_', '')
|
|
||||||
cs = cs.replace('#', '')
|
|
||||||
return cs
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
|
||||||
# Positions
|
|
||||||
# ---------------------------------------------
|
|
||||||
|
|
||||||
def oe_flag(msg):
|
|
||||||
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: 0 or 1, for even or odd frame
|
|
||||||
"""
|
|
||||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
|
||||||
raise RuntimeError("%s: Not a position message" % msg)
|
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
return int(msgbin[53])
|
|
||||||
|
|
||||||
|
|
||||||
def cprlat(msg):
|
|
||||||
"""CPR encoded latitude
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: encoded latitude
|
|
||||||
"""
|
|
||||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
|
||||||
raise RuntimeError("%s: Not a position message" % msg)
|
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
return util.bin2int(msgbin[54:71])
|
|
||||||
|
|
||||||
|
|
||||||
def cprlon(msg):
|
|
||||||
"""CPR encoded longitude
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: encoded longitude
|
|
||||||
"""
|
|
||||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
|
||||||
raise RuntimeError("%s: Not a position message" % msg)
|
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
return util.bin2int(msgbin[71:88])
|
|
||||||
|
|
||||||
|
|
||||||
def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
||||||
"""Decode position from a pair of even and odd position message
|
"""Decode position from a pair of even and odd position message
|
||||||
@ -181,7 +48,10 @@ def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
|||||||
Returns:
|
Returns:
|
||||||
(float, float): (latitude, longitude) of the aircraft
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
"""
|
"""
|
||||||
if (5 <= typecode(msg0) <= 8 and 5 <= typecode(msg1) <= 8):
|
tc0 = typecode(msg0)
|
||||||
|
tc1 = typecode(msg1)
|
||||||
|
|
||||||
|
if (5<=tc0<=8 and 5<=tc1<=8):
|
||||||
if (not lat_ref) or (not lon_ref):
|
if (not lat_ref) or (not lon_ref):
|
||||||
raise RuntimeError("Surface position encountered, a reference \
|
raise RuntimeError("Surface position encountered, a reference \
|
||||||
position lat/lon required. Location of \
|
position lat/lon required. Location of \
|
||||||
@ -189,74 +59,18 @@ def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
|||||||
else:
|
else:
|
||||||
return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
|
return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
|
||||||
|
|
||||||
elif (9 <= typecode(msg0) <= 18 and 9 <= typecode(msg1) <= 18):
|
elif (9<=tc0<=18 and 9<=tc1<=18):
|
||||||
|
# Airborne position with barometric height
|
||||||
|
return airborne_position(msg0, msg1, t0, t1)
|
||||||
|
|
||||||
|
elif (20<=tc0<=22 and 20<=tc1<=22):
|
||||||
|
# Airborne position with GNSS height
|
||||||
return airborne_position(msg0, msg1, t0, t1)
|
return airborne_position(msg0, msg1, t0, t1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("incorrect or inconsistant message types")
|
raise RuntimeError("incorrect or inconsistant message types")
|
||||||
|
|
||||||
|
|
||||||
def airborne_position(msg0, msg1, t0, t1):
|
|
||||||
"""Decode airborn position from a pair of even and odd position message
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(float, float): (latitude, longitude) of the aircraft
|
|
||||||
"""
|
|
||||||
|
|
||||||
msgbin0 = util.hex2bin(msg0)
|
|
||||||
msgbin1 = util.hex2bin(msg1)
|
|
||||||
|
|
||||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
|
||||||
cprlat_even = util.bin2int(msgbin0[54:71]) / 131072.0
|
|
||||||
cprlon_even = util.bin2int(msgbin0[71:88]) / 131072.0
|
|
||||||
cprlat_odd = util.bin2int(msgbin1[54:71]) / 131072.0
|
|
||||||
cprlon_odd = util.bin2int(msgbin1[71:88]) / 131072.0
|
|
||||||
|
|
||||||
air_d_lat_even = 360.0 / 60
|
|
||||||
air_d_lat_odd = 360.0 / 59
|
|
||||||
|
|
||||||
# compute latitude index 'j'
|
|
||||||
j = util.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
|
||||||
|
|
||||||
lat_even = float(air_d_lat_even * (j % 60 + cprlat_even))
|
|
||||||
lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
|
||||||
|
|
||||||
if lat_even >= 270:
|
|
||||||
lat_even = lat_even - 360
|
|
||||||
|
|
||||||
if lat_odd >= 270:
|
|
||||||
lat_odd = lat_odd - 360
|
|
||||||
|
|
||||||
# check if both are in the same latidude zone, exit if not
|
|
||||||
if _cprNL(lat_even) != _cprNL(lat_odd):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# compute ni, longitude index m, and longitude
|
|
||||||
if (t0 > t1):
|
|
||||||
lat = lat_even
|
|
||||||
nl = _cprNL(lat)
|
|
||||||
ni = max(_cprNL(lat)- 0, 1)
|
|
||||||
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
|
||||||
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
|
||||||
else:
|
|
||||||
lat = lat_odd
|
|
||||||
nl = _cprNL(lat)
|
|
||||||
ni = max(_cprNL(lat) - 1, 1)
|
|
||||||
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
|
||||||
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
|
||||||
|
|
||||||
if lon > 180:
|
|
||||||
lon = lon - 360
|
|
||||||
|
|
||||||
return round(lat, 5), round(lon, 5)
|
|
||||||
|
|
||||||
|
|
||||||
def position_with_ref(msg, lat_ref, lon_ref):
|
def position_with_ref(msg, lat_ref, lon_ref):
|
||||||
"""Decode position with only one message,
|
"""Decode position with only one message,
|
||||||
knowing reference nearby location, such as previously
|
knowing reference nearby location, such as previously
|
||||||
@ -273,192 +87,19 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
Returns:
|
Returns:
|
||||||
(float, float): (latitude, longitude) of the aircraft
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
"""
|
"""
|
||||||
if 5 <= typecode(msg) <= 8:
|
|
||||||
|
tc = typecode(msg)
|
||||||
|
|
||||||
|
if 5<=tc<=8:
|
||||||
return surface_position_with_ref(msg, lat_ref, lon_ref)
|
return surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||||
|
|
||||||
elif 9 <= typecode(msg) <= 18:
|
elif 9<=tc<=18 or 20<=tc<=22:
|
||||||
return airborne_position_with_ref(msg, lat_ref, lon_ref)
|
return airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("incorrect or inconsistant message types")
|
raise RuntimeError("incorrect or inconsistant message types")
|
||||||
|
|
||||||
|
|
||||||
def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
|
||||||
"""Decode airborne position with only one message,
|
|
||||||
knowing reference nearby location, such as previously calculated location,
|
|
||||||
ground station, or airport location, etc. The reference position shall
|
|
||||||
be with in 180NM of the true position.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
|
|
||||||
i = oe_flag(msg)
|
|
||||||
d_lat = 360.0/59 if i else 360.0/60
|
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
cprlat = util.bin2int(msgbin[54:71]) / 131072.0
|
|
||||||
cprlon = util.bin2int(msgbin[71:88]) / 131072.0
|
|
||||||
|
|
||||||
j = util.floor(lat_ref / d_lat) \
|
|
||||||
+ util.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
|
||||||
|
|
||||||
lat = d_lat * (j + cprlat)
|
|
||||||
|
|
||||||
ni = _cprNL(lat) - i
|
|
||||||
|
|
||||||
if ni > 0:
|
|
||||||
d_lon = 360.0 / ni
|
|
||||||
else:
|
|
||||||
d_lon = 360.0
|
|
||||||
|
|
||||||
m = util.floor(lon_ref / d_lon) \
|
|
||||||
+ util.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
|
||||||
|
|
||||||
lon = d_lon * (m + cprlon)
|
|
||||||
|
|
||||||
return round(lat, 5), round(lon, 5)
|
|
||||||
|
|
||||||
|
|
||||||
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
|
||||||
"""Decode surface position from a pair of even and odd position message,
|
|
||||||
the lat/lon of receiver must be provided to yield the correct solution.
|
|
||||||
|
|
||||||
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
|
|
||||||
lat_ref (float): latitude of the receiver
|
|
||||||
lon_ref (float): longitude of the receiver
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(float, float): (latitude, longitude) of the aircraft
|
|
||||||
"""
|
|
||||||
|
|
||||||
msgbin0 = util.hex2bin(msg0)
|
|
||||||
msgbin1 = util.hex2bin(msg1)
|
|
||||||
|
|
||||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
|
||||||
cprlat_even = util.bin2int(msgbin0[54:71]) / 131072.0
|
|
||||||
cprlon_even = util.bin2int(msgbin0[71:88]) / 131072.0
|
|
||||||
cprlat_odd = util.bin2int(msgbin1[54:71]) / 131072.0
|
|
||||||
cprlon_odd = util.bin2int(msgbin1[71:88]) / 131072.0
|
|
||||||
|
|
||||||
air_d_lat_even = 90.0 / 60
|
|
||||||
air_d_lat_odd = 90.0 / 59
|
|
||||||
|
|
||||||
# compute latitude index 'j'
|
|
||||||
j = util.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
|
||||||
|
|
||||||
# solution for north hemisphere
|
|
||||||
lat_even_n = float(air_d_lat_even * (j % 60 + cprlat_even))
|
|
||||||
lat_odd_n = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
|
||||||
|
|
||||||
# solution for north hemisphere
|
|
||||||
lat_even_s = lat_even_n - 90.0
|
|
||||||
lat_odd_s = lat_odd_n - 90.0
|
|
||||||
|
|
||||||
# chose which solution corrispondes to receiver location
|
|
||||||
lat_even = lat_even_n if lat_ref > 0 else lat_even_s
|
|
||||||
lat_odd = lat_odd_n if lat_ref > 0 else lat_odd_s
|
|
||||||
|
|
||||||
# check if both are in the same latidude zone, rare but possible
|
|
||||||
if _cprNL(lat_even) != _cprNL(lat_odd):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# compute ni, longitude index m, and longitude
|
|
||||||
if (t0 > t1):
|
|
||||||
lat = lat_even
|
|
||||||
nl = _cprNL(lat_even)
|
|
||||||
ni = max(_cprNL(lat_even) - 0, 1)
|
|
||||||
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
|
||||||
lon = (90.0 / ni) * (m % ni + cprlon_even)
|
|
||||||
else:
|
|
||||||
lat = lat_odd
|
|
||||||
nl = _cprNL(lat_odd)
|
|
||||||
ni = max(_cprNL(lat_odd) - 1, 1)
|
|
||||||
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
|
||||||
lon = (90.0 / ni) * (m % ni + cprlon_odd)
|
|
||||||
|
|
||||||
# four possible longitude solutions
|
|
||||||
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
|
|
||||||
|
|
||||||
# the closest solution to receiver is the correct one
|
|
||||||
dls = [abs(lon_ref - l) for l in lons]
|
|
||||||
imin = min(range(4), key=dls.__getitem__)
|
|
||||||
lon = lons[imin]
|
|
||||||
|
|
||||||
return round(lat, 5), round(lon, 5)
|
|
||||||
|
|
||||||
|
|
||||||
def surface_position_with_ref(msg, lat_ref, lon_ref):
|
|
||||||
"""Decode surface position with only one message,
|
|
||||||
knowing reference nearby location, such as previously calculated location,
|
|
||||||
ground station, or airport location, etc. The reference position shall
|
|
||||||
be with in 45NM of the true position.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
|
|
||||||
i = oe_flag(msg)
|
|
||||||
d_lat = 90.0/59 if i else 90.0/60
|
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
cprlat = util.bin2int(msgbin[54:71]) / 131072.0
|
|
||||||
cprlon = util.bin2int(msgbin[71:88]) / 131072.0
|
|
||||||
|
|
||||||
j = util.floor(lat_ref / d_lat) \
|
|
||||||
+ util.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
|
||||||
|
|
||||||
lat = d_lat * (j + cprlat)
|
|
||||||
|
|
||||||
ni = _cprNL(lat) - i
|
|
||||||
|
|
||||||
if ni > 0:
|
|
||||||
d_lon = 90.0 / ni
|
|
||||||
else:
|
|
||||||
d_lon = 90.0
|
|
||||||
|
|
||||||
m = util.floor(lon_ref / d_lon) \
|
|
||||||
+ util.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
|
||||||
|
|
||||||
lon = d_lon * (m + cprlon)
|
|
||||||
|
|
||||||
return round(lat, 5), round(lon, 5)
|
|
||||||
|
|
||||||
|
|
||||||
def _cprNL(lat):
|
|
||||||
"""NL() function in CPR decoding
|
|
||||||
"""
|
|
||||||
if lat == 0:
|
|
||||||
return 59
|
|
||||||
|
|
||||||
if lat == 87 or lat == -87:
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if lat > 87 or lat < -87:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
nz = 15
|
|
||||||
a = 1 - math.cos(math.pi / (2 * nz))
|
|
||||||
b = math.cos(math.pi / 180.0 * abs(lat)) ** 2
|
|
||||||
nl = 2 * math.pi / (math.acos(1 - a/b))
|
|
||||||
NL = util.floor(nl)
|
|
||||||
return NL
|
|
||||||
|
|
||||||
|
|
||||||
def altitude(msg):
|
def altitude(msg):
|
||||||
"""Decode aircraft altitude
|
"""Decode aircraft altitude
|
||||||
|
|
||||||
@ -469,23 +110,62 @@ def altitude(msg):
|
|||||||
int: altitude in feet
|
int: altitude in feet
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
tc = typecode(msg)
|
||||||
|
|
||||||
|
if tc<5 or tc==19 or tc>22:
|
||||||
raise RuntimeError("%s: Not a position message" % msg)
|
raise RuntimeError("%s: Not a position message" % msg)
|
||||||
|
|
||||||
if typecode(msg) >=5 and typecode(msg) <=8:
|
if tc>=5 and tc<=8:
|
||||||
# surface position, altitude 0
|
# surface position, altitude 0
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
q = msgbin[47]
|
q = msgbin[47]
|
||||||
if q:
|
if q:
|
||||||
n = util.bin2int(msgbin[40:47]+msgbin[48:52])
|
n = common.bin2int(msgbin[40:47]+msgbin[48:52])
|
||||||
alt = n * 25 - 1000
|
alt = n * 25 - 1000
|
||||||
return alt
|
return alt
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def velocity(msg):
|
||||||
|
"""Calculate the speed, heading, and vertical rate
|
||||||
|
(handles both airborne or surface message)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(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)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if 5 <= typecode(msg) <= 8:
|
||||||
|
return surface_velocity(msg)
|
||||||
|
|
||||||
|
elif typecode(msg) == 19:
|
||||||
|
return airborne_velocity(msg)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
|
||||||
|
|
||||||
|
|
||||||
|
def speed_heading(msg):
|
||||||
|
"""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), ground track or heading (degree)
|
||||||
|
"""
|
||||||
|
spd, trk_or_hdg, rocd, tag = velocity(msg)
|
||||||
|
return spd, trk_or_hdg
|
||||||
|
|
||||||
|
|
||||||
def nic(msg):
|
def nic(msg):
|
||||||
"""Calculate NIC, navigation integrity category
|
"""Calculate NIC, navigation integrity category
|
||||||
|
|
||||||
@ -498,9 +178,9 @@ def nic(msg):
|
|||||||
if typecode(msg) < 9 or typecode(msg) > 18:
|
if typecode(msg) < 9 or typecode(msg) > 18:
|
||||||
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
|
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
nic_sup_b = util.bin2int(msgbin[39])
|
nic_sup_b = common.bin2int(msgbin[39])
|
||||||
|
|
||||||
if tc in [0, 18, 22]:
|
if tc in [0, 18, 22]:
|
||||||
nic = 0
|
nic = 0
|
||||||
@ -531,166 +211,3 @@ def nic(msg):
|
|||||||
else:
|
else:
|
||||||
nic = -1
|
nic = -1
|
||||||
return nic
|
return nic
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
|
||||||
# Velocity
|
|
||||||
# ---------------------------------------------
|
|
||||||
|
|
||||||
def velocity(msg):
|
|
||||||
"""Calculate the speed, heading, and vertical rate
|
|
||||||
(handles both airborne or surface message)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(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)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if 5 <= typecode(msg) <= 8:
|
|
||||||
return surface_velocity(msg)
|
|
||||||
|
|
||||||
elif typecode(msg) == 19:
|
|
||||||
return airborne_velocity(msg)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
|
|
||||||
|
|
||||||
def speed_heading(msg):
|
|
||||||
"""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), ground track or heading (degree)
|
|
||||||
"""
|
|
||||||
spd, trk_or_hdg, rocd, tag = velocity(msg)
|
|
||||||
return spd, trk_or_hdg
|
|
||||||
|
|
||||||
|
|
||||||
def airborne_velocity(msg):
|
|
||||||
"""Calculate the speed, track (or heading), and vertical rate
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(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)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if typecode(msg) != 19:
|
|
||||||
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
|
||||||
|
|
||||||
msgbin = util.hex2bin(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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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(trk_or_hdg, 1), int(rocd), tag
|
|
||||||
|
|
||||||
|
|
||||||
def surface_velocity(msg):
|
|
||||||
"""Decode surface velocity from from a surface position message
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(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)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if typecode(msg) < 5 or typecode(msg) > 8:
|
|
||||||
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
|
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
|
|
||||||
# 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:
|
|
||||||
trk = None
|
|
||||||
|
|
||||||
# ground movment / speed
|
|
||||||
mov = util.bin2int(msgbin[37:44])
|
|
||||||
|
|
||||||
if mov == 0 or mov > 124:
|
|
||||||
spd = None
|
|
||||||
elif mov == 1:
|
|
||||||
spd = 0
|
|
||||||
elif mov == 124:
|
|
||||||
spd = 175
|
|
||||||
else:
|
|
||||||
movs = [2, 9, 13, 39, 94, 109, 124]
|
|
||||||
kts = [0.125, 1, 2, 15, 70, 100, 175]
|
|
||||||
i = next(m[0] for m in enumerate(movs) if m[1] > mov)
|
|
||||||
step = (kts[i] - kts[i-1]) * 1.0 / (movs[i]-movs[i-1])
|
|
||||||
spd = kts[i-1] + (mov-movs[i-1]) * step
|
|
||||||
spd = round(spd, 2)
|
|
||||||
|
|
||||||
return spd, trk, 0, 'GS'
|
|
||||||
|
|
||||||
def altitude_diff(msg):
|
|
||||||
"""Decode the differece between GNSS and barometric altitude
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string, TC=19
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: Altitude difference in ft. Negative value indicates GNSS altitude
|
|
||||||
below barometric altitude.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if typecode(msg) != 19:
|
|
||||||
raise RuntimeError("incorrect message types, expecting TC=19")
|
|
||||||
|
|
||||||
msgbin = util.hex2bin(msg)
|
|
||||||
sign = -1 if int(msgbin[80]) else 1
|
|
||||||
value = util.bin2int(msgbin[81:88])
|
|
||||||
|
|
||||||
if value == 0 or value == 127:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return sign * (value - 1) * 25 # in ft.
|
|
||||||
|
@ -1,15 +1,35 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Common functions for Mode-S decoding
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, print_function, division
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from pyModeS.extra import aero
|
from pyModeS.extra import aero
|
||||||
from pyModeS.decoder.modes import allzeros
|
from pyModeS.decoder.common import allzeros
|
||||||
from pyModeS.decoder.bds.bds10 import isBDS10
|
from pyModeS.decoder.bds.bds10 import is10
|
||||||
from pyModeS.decoder.bds.bds17 import isBDS17
|
from pyModeS.decoder.bds.bds17 import is17
|
||||||
from pyModeS.decoder.bds.bds20 import isBDS20
|
from pyModeS.decoder.bds.bds20 import is20
|
||||||
from pyModeS.decoder.bds.bds30 import isBDS30
|
from pyModeS.decoder.bds.bds30 import is30
|
||||||
from pyModeS.decoder.bds.bds40 import isBDS40
|
from pyModeS.decoder.bds.bds40 import is40
|
||||||
from pyModeS.decoder.bds.bds50 import isBDS50, trk50, gs50
|
from pyModeS.decoder.bds.bds50 import is50, trk50, gs50
|
||||||
from pyModeS.decoder.bds.bds60 import isBDS60, hdg60, mach60, ias60
|
from pyModeS.decoder.bds.bds60 import is60, hdg60, mach60, ias60
|
||||||
|
|
||||||
|
|
||||||
def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
||||||
@ -29,7 +49,7 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
|||||||
vy = v * np.cos(np.deg2rad(angle))
|
vy = v * np.cos(np.deg2rad(angle))
|
||||||
return vx, vy
|
return vx, vy
|
||||||
|
|
||||||
if not (isBDS50(msg) and isBDS60(msg)):
|
if not (is50(msg) and is60(msg)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
h50 = trk50(msg)
|
h50 = trk50(msg)
|
||||||
@ -79,21 +99,21 @@ def infer(msg):
|
|||||||
if allzeros(msg):
|
if allzeros(msg):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
is10 = isBDS10(msg)
|
IS10 = is10(msg)
|
||||||
is17 = isBDS17(msg)
|
IS17 = is17(msg)
|
||||||
is20 = isBDS20(msg)
|
IS20 = is20(msg)
|
||||||
is30 = isBDS30(msg)
|
IS30 = is30(msg)
|
||||||
is40 = isBDS40(msg)
|
IS40 = is40(msg)
|
||||||
is50 = isBDS50(msg)
|
IS50 = is50(msg)
|
||||||
is60 = isBDS60(msg)
|
IS60 = is60(msg)
|
||||||
|
|
||||||
allbds = np.array([
|
allbds = np.array([
|
||||||
"BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS50", "BDS60"
|
"BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS50", "BDS60"
|
||||||
])
|
])
|
||||||
|
|
||||||
isBDS = [is10, is17, is20, is30, is40, is50, is60]
|
mask = [IS10, IS17, IS20, IS30, IS40, IS50, IS60]
|
||||||
|
|
||||||
bds = ','.join(sorted(allbds[isBDS]))
|
bds = ','.join(sorted(allbds[mask]))
|
||||||
|
|
||||||
if len(bds) == 0:
|
if len(bds) == 0:
|
||||||
return None
|
return None
|
||||||
|
162
pyModeS/decoder/bds/bds05.py
Normal file
162
pyModeS/decoder/bds/bds05.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
------------------------------------------
|
||||||
|
BDS 0,5
|
||||||
|
ADS-B TC=9-18
|
||||||
|
Airborn position
|
||||||
|
------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, print_function, division
|
||||||
|
from pyModeS.decoder import common
|
||||||
|
|
||||||
|
def airborne_position(msg0, msg1, t0, t1):
|
||||||
|
"""Decode airborn position from a pair of even and odd position message
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
|
"""
|
||||||
|
|
||||||
|
mb0 = common.hex2bin(msg0)[32:]
|
||||||
|
mb1 = common.hex2bin(msg1)[32:]
|
||||||
|
|
||||||
|
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||||
|
cprlat_even = common.bin2int(mb0[22:39]) / 131072.0
|
||||||
|
cprlon_even = common.bin2int(mb0[39:56]) / 131072.0
|
||||||
|
cprlat_odd = common.bin2int(mb1[22:39]) / 131072.0
|
||||||
|
cprlon_odd = common.bin2int(mb1[39:56]) / 131072.0
|
||||||
|
|
||||||
|
air_d_lat_even = 360.0 / 60
|
||||||
|
air_d_lat_odd = 360.0 / 59
|
||||||
|
|
||||||
|
# compute latitude index 'j'
|
||||||
|
j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||||
|
|
||||||
|
lat_even = float(air_d_lat_even * (j % 60 + cprlat_even))
|
||||||
|
lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
||||||
|
|
||||||
|
if lat_even >= 270:
|
||||||
|
lat_even = lat_even - 360
|
||||||
|
|
||||||
|
if lat_odd >= 270:
|
||||||
|
lat_odd = lat_odd - 360
|
||||||
|
|
||||||
|
# check if both are in the same latidude zone, exit if not
|
||||||
|
if common.cprNL(lat_even) != common.cprNL(lat_odd):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# compute ni, longitude index m, and longitude
|
||||||
|
if (t0 > t1):
|
||||||
|
lat = lat_even
|
||||||
|
nl = common.cprNL(lat)
|
||||||
|
ni = max(common.cprNL(lat)- 0, 1)
|
||||||
|
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||||
|
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
||||||
|
else:
|
||||||
|
lat = lat_odd
|
||||||
|
nl = common.cprNL(lat)
|
||||||
|
ni = max(common.cprNL(lat) - 1, 1)
|
||||||
|
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||||
|
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
||||||
|
|
||||||
|
if lon > 180:
|
||||||
|
lon = lon - 360
|
||||||
|
|
||||||
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
||||||
|
"""Decode airborne position with only one message,
|
||||||
|
knowing reference nearby location, such as previously calculated location,
|
||||||
|
ground station, or airport location, etc. The reference position shall
|
||||||
|
be with in 180NM of the true position.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
|
||||||
|
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
||||||
|
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
||||||
|
|
||||||
|
i = int(mb[21])
|
||||||
|
d_lat = 360.0/59 if i else 360.0/60
|
||||||
|
|
||||||
|
j = common.floor(lat_ref / d_lat) \
|
||||||
|
+ common.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
||||||
|
|
||||||
|
lat = d_lat * (j + cprlat)
|
||||||
|
|
||||||
|
ni = common.cprNL(lat) - i
|
||||||
|
|
||||||
|
if ni > 0:
|
||||||
|
d_lon = 360.0 / ni
|
||||||
|
else:
|
||||||
|
d_lon = 360.0
|
||||||
|
|
||||||
|
m = common.floor(lon_ref / d_lon) \
|
||||||
|
+ common.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
||||||
|
|
||||||
|
lon = d_lon * (m + cprlon)
|
||||||
|
|
||||||
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def altitude(msg):
|
||||||
|
"""Decode aircraft altitude
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: altitude in feet
|
||||||
|
"""
|
||||||
|
|
||||||
|
tc = common.typecode(msg)
|
||||||
|
|
||||||
|
if tc<9 or tc==19 or tc>22:
|
||||||
|
raise RuntimeError("%s: Not a airborn position message" % msg)
|
||||||
|
|
||||||
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
|
||||||
|
if tc < 19:
|
||||||
|
# barometric altitude
|
||||||
|
q = mb[15]
|
||||||
|
if q:
|
||||||
|
n = common.bin2int(mb[8:15]+mb[16:20])
|
||||||
|
alt = n * 25 - 1000
|
||||||
|
else:
|
||||||
|
alt = None
|
||||||
|
else:
|
||||||
|
# GNSS altitude, meters -> feet
|
||||||
|
alt = common.bin2int(mb[8:20]) * 3.28084
|
||||||
|
|
||||||
|
return alt
|
187
pyModeS/decoder/bds/bds06.py
Normal file
187
pyModeS/decoder/bds/bds06.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
------------------------------------------
|
||||||
|
BDS 0,6
|
||||||
|
ADS-B TC=5-8
|
||||||
|
Surface position
|
||||||
|
------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, print_function, division
|
||||||
|
from pyModeS.decoder import common
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||||
|
"""Decode surface position from a pair of even and odd position message,
|
||||||
|
the lat/lon of receiver must be provided to yield the correct solution.
|
||||||
|
|
||||||
|
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
|
||||||
|
lat_ref (float): latitude of the receiver
|
||||||
|
lon_ref (float): longitude of the receiver
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
|
"""
|
||||||
|
|
||||||
|
msgbin0 = common.hex2bin(msg0)
|
||||||
|
msgbin1 = common.hex2bin(msg1)
|
||||||
|
|
||||||
|
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||||
|
cprlat_even = common.bin2int(msgbin0[54:71]) / 131072.0
|
||||||
|
cprlon_even = common.bin2int(msgbin0[71:88]) / 131072.0
|
||||||
|
cprlat_odd = common.bin2int(msgbin1[54:71]) / 131072.0
|
||||||
|
cprlon_odd = common.bin2int(msgbin1[71:88]) / 131072.0
|
||||||
|
|
||||||
|
air_d_lat_even = 90.0 / 60
|
||||||
|
air_d_lat_odd = 90.0 / 59
|
||||||
|
|
||||||
|
# compute latitude index 'j'
|
||||||
|
j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||||
|
|
||||||
|
# solution for north hemisphere
|
||||||
|
lat_even_n = float(air_d_lat_even * (j % 60 + cprlat_even))
|
||||||
|
lat_odd_n = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
||||||
|
|
||||||
|
# solution for north hemisphere
|
||||||
|
lat_even_s = lat_even_n - 90.0
|
||||||
|
lat_odd_s = lat_odd_n - 90.0
|
||||||
|
|
||||||
|
# chose which solution corrispondes to receiver location
|
||||||
|
lat_even = lat_even_n if lat_ref > 0 else lat_even_s
|
||||||
|
lat_odd = lat_odd_n if lat_ref > 0 else lat_odd_s
|
||||||
|
|
||||||
|
# check if both are in the same latidude zone, rare but possible
|
||||||
|
if common.cprNL(lat_even) != common.cprNL(lat_odd):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# compute ni, longitude index m, and longitude
|
||||||
|
if (t0 > t1):
|
||||||
|
lat = lat_even
|
||||||
|
nl = common.cprNL(lat_even)
|
||||||
|
ni = max(common.cprNL(lat_even) - 0, 1)
|
||||||
|
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||||
|
lon = (90.0 / ni) * (m % ni + cprlon_even)
|
||||||
|
else:
|
||||||
|
lat = lat_odd
|
||||||
|
nl = common.cprNL(lat_odd)
|
||||||
|
ni = max(common.cprNL(lat_odd) - 1, 1)
|
||||||
|
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||||
|
lon = (90.0 / ni) * (m % ni + cprlon_odd)
|
||||||
|
|
||||||
|
# four possible longitude solutions
|
||||||
|
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
|
||||||
|
|
||||||
|
# the closest solution to receiver is the correct one
|
||||||
|
dls = [abs(lon_ref - l) for l in lons]
|
||||||
|
imin = min(range(4), key=dls.__getitem__)
|
||||||
|
lon = lons[imin]
|
||||||
|
|
||||||
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def surface_position_with_ref(msg, lat_ref, lon_ref):
|
||||||
|
"""Decode surface position with only one message,
|
||||||
|
knowing reference nearby location, such as previously calculated location,
|
||||||
|
ground station, or airport location, etc. The reference position shall
|
||||||
|
be with in 45NM of the true position.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
|
||||||
|
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
||||||
|
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
||||||
|
|
||||||
|
i = int(mb[21])
|
||||||
|
d_lat = 90.0/59 if i else 90.0/60
|
||||||
|
|
||||||
|
j = common.floor(lat_ref / d_lat) \
|
||||||
|
+ common.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
||||||
|
|
||||||
|
lat = d_lat * (j + cprlat)
|
||||||
|
|
||||||
|
ni = common.cprNL(lat) - i
|
||||||
|
|
||||||
|
if ni > 0:
|
||||||
|
d_lon = 90.0 / ni
|
||||||
|
else:
|
||||||
|
d_lon = 90.0
|
||||||
|
|
||||||
|
m = common.floor(lon_ref / d_lon) \
|
||||||
|
+ common.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
||||||
|
|
||||||
|
lon = d_lon * (m + cprlon)
|
||||||
|
|
||||||
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def surface_velocity(msg):
|
||||||
|
"""Decode surface velocity from from a surface position message
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(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)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if common.typecode(msg) < 5 or common.typecode(msg) > 8:
|
||||||
|
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
|
||||||
|
|
||||||
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
|
||||||
|
# ground track
|
||||||
|
trk_status = int(mb[12])
|
||||||
|
if trk_status == 1:
|
||||||
|
trk = common.bin2int(mb[13:20]) * 360.0 / 128.0
|
||||||
|
trk = round(trk, 1)
|
||||||
|
else:
|
||||||
|
trk = None
|
||||||
|
|
||||||
|
# ground movment / speed
|
||||||
|
mov = common.bin2int(mb[5:12])
|
||||||
|
|
||||||
|
if mov == 0 or mov > 124:
|
||||||
|
spd = None
|
||||||
|
elif mov == 1:
|
||||||
|
spd = 0
|
||||||
|
elif mov == 124:
|
||||||
|
spd = 175
|
||||||
|
else:
|
||||||
|
movs = [2, 9, 13, 39, 94, 109, 124]
|
||||||
|
kts = [0.125, 1, 2, 15, 70, 100, 175]
|
||||||
|
i = next(m[0] for m in enumerate(movs) if m[1] > mov)
|
||||||
|
step = (kts[i] - kts[i-1]) * 1.0 / (movs[i]-movs[i-1])
|
||||||
|
spd = kts[i-1] + (mov-movs[i-1]) * step
|
||||||
|
spd = round(spd, 2)
|
||||||
|
|
||||||
|
return spd, trk, 0, 'GS'
|
75
pyModeS/decoder/bds/bds08.py
Normal file
75
pyModeS/decoder/bds/bds08.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
------------------------------------------
|
||||||
|
BDS 0,8
|
||||||
|
ADS-B TC=1-4
|
||||||
|
Aircraft identitification and category
|
||||||
|
------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, print_function, division
|
||||||
|
from pyModeS.decoder import common
|
||||||
|
|
||||||
|
def category(msg):
|
||||||
|
"""Aircraft category number
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: category number
|
||||||
|
"""
|
||||||
|
|
||||||
|
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
|
||||||
|
raise RuntimeError("%s: Not a identification message" % msg)
|
||||||
|
|
||||||
|
msgbin = common.hex2bin(msg)
|
||||||
|
return common.bin2int(msgbin[5:8])
|
||||||
|
|
||||||
|
|
||||||
|
def callsign(msg):
|
||||||
|
"""Aircraft callsign
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string: callsign
|
||||||
|
"""
|
||||||
|
|
||||||
|
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
|
||||||
|
raise RuntimeError("%s: Not a identification message" % msg)
|
||||||
|
|
||||||
|
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
||||||
|
msgbin = common.hex2bin(msg)
|
||||||
|
csbin = msgbin[40:96]
|
||||||
|
|
||||||
|
cs = ''
|
||||||
|
cs += chars[common.bin2int(csbin[0:6])]
|
||||||
|
cs += chars[common.bin2int(csbin[6:12])]
|
||||||
|
cs += chars[common.bin2int(csbin[12:18])]
|
||||||
|
cs += chars[common.bin2int(csbin[18:24])]
|
||||||
|
cs += chars[common.bin2int(csbin[24:30])]
|
||||||
|
cs += chars[common.bin2int(csbin[30:36])]
|
||||||
|
cs += chars[common.bin2int(csbin[36:42])]
|
||||||
|
cs += chars[common.bin2int(csbin[42:48])]
|
||||||
|
|
||||||
|
# clean string, remove spaces and marks, if any.
|
||||||
|
# cs = cs.replace('_', '')
|
||||||
|
cs = cs.replace('#', '')
|
||||||
|
return cs
|
116
pyModeS/decoder/bds/bds09.py
Normal file
116
pyModeS/decoder/bds/bds09.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
------------------------------------------
|
||||||
|
BDS 0,9
|
||||||
|
ADS-B TC=19
|
||||||
|
Aircraft Airborn velocity
|
||||||
|
------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, print_function, division
|
||||||
|
from pyModeS.decoder import common
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
def airborne_velocity(msg):
|
||||||
|
"""Calculate the speed, track (or heading), and vertical rate
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(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)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if common.typecode(msg) != 19:
|
||||||
|
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
||||||
|
|
||||||
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
|
||||||
|
subtype = common.bin2int(mb[5:8])
|
||||||
|
|
||||||
|
if common.bin2int(mb[14:24]) == 0 or common.bin2int(mb[25:35]) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if subtype in (1, 2):
|
||||||
|
v_ew_sign = -1 if mb[13]=='1' else 1
|
||||||
|
v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity
|
||||||
|
|
||||||
|
v_ns_sign = -1 if mb[24]=='1' else 1
|
||||||
|
v_ns = common.bin2int(mb[25:35]) - 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
|
||||||
|
|
||||||
|
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:
|
||||||
|
if mb[13] == '0':
|
||||||
|
hdg = None
|
||||||
|
else:
|
||||||
|
hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0
|
||||||
|
|
||||||
|
trk_or_hdg = hdg
|
||||||
|
|
||||||
|
spd = common.bin2int(mb[25:35])
|
||||||
|
spd = None if spd==0 else spd-1
|
||||||
|
|
||||||
|
if mb[24]=='0':
|
||||||
|
tag = 'IAS'
|
||||||
|
else:
|
||||||
|
tag = 'TAS'
|
||||||
|
|
||||||
|
vr_sign = -1 if mb[36]=='1' else 1
|
||||||
|
vr = common.bin2int(mb[37:46])
|
||||||
|
rocd = None if vr==0 else vr_sign*(vr-1)*64
|
||||||
|
|
||||||
|
return int(spd), round(trk_or_hdg, 2), int(rocd), tag
|
||||||
|
|
||||||
|
def altitude_diff(msg):
|
||||||
|
"""Decode the differece between GNSS and barometric altitude
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string, TC=19
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Altitude difference in ft. Negative value indicates GNSS altitude
|
||||||
|
below barometric altitude.
|
||||||
|
"""
|
||||||
|
tc = common.typecode(msg)
|
||||||
|
|
||||||
|
if tc != 19:
|
||||||
|
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
||||||
|
|
||||||
|
msgbin = common.hex2bin(msg)
|
||||||
|
sign = -1 if int(msgbin[80]) else 1
|
||||||
|
value = common.bin2int(msgbin[81:88])
|
||||||
|
|
||||||
|
if value == 0 or value == 127:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return sign * (value - 1) * 25 # in ft.
|
@ -14,15 +14,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||||
from pyModeS.decoder.modes import data, allzeros
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 1,0
|
# BDS 1,0
|
||||||
# Data link capability report
|
# Data link capability report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def isBDS10(msg):
|
def is10(msg):
|
||||||
"""Check if a message is likely to be BDS code 1,0
|
"""Check if a message is likely to be BDS code 1,0
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -15,15 +15,16 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||||
from pyModeS.decoder.modes import data, allzeros
|
|
||||||
|
|
||||||
# ------------------------------------------
|
"""
|
||||||
# BDS 1,7
|
------------------------------------------
|
||||||
# Common usage GICB capability report
|
BDS 1,7
|
||||||
# ------------------------------------------
|
Common usage GICB capability report
|
||||||
|
------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
def isBDS17(msg):
|
def is17(msg):
|
||||||
"""Check if a message is likely to be BDS code 1,7
|
"""Check if a message is likely to be BDS code 1,7
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -14,15 +14,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||||
from pyModeS.decoder.modes import data, allzeros
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 2,0
|
# BDS 2,0
|
||||||
# Aircraft identification
|
# Aircraft identification
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def isBDS20(msg):
|
def is20(msg):
|
||||||
"""Check if a message is likely to be BDS code 2,0
|
"""Check if a message is likely to be BDS code 2,0
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -42,7 +41,7 @@ def isBDS20(msg):
|
|||||||
if bin2int(d[0:4]) != 2 or bin2int(d[4:8]) != 0:
|
if bin2int(d[0:4]) != 2 or bin2int(d[4:8]) != 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
cs = callsign(msg)
|
cs = cs20(msg)
|
||||||
|
|
||||||
if '#' in cs:
|
if '#' in cs:
|
||||||
return False
|
return False
|
||||||
@ -50,7 +49,7 @@ def isBDS20(msg):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def callsign(msg):
|
def cs20(msg):
|
||||||
"""Aircraft callsign
|
"""Aircraft callsign
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -14,15 +14,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||||
from pyModeS.decoder.modes import data, allzeros
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 3,0
|
# BDS 3,0
|
||||||
# ACAS active resolution advisory
|
# ACAS active resolution advisory
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def isBDS30(msg):
|
def is30(msg):
|
||||||
"""Check if a message is likely to be BDS code 2,0
|
"""Check if a message is likely to be BDS code 2,0
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -15,15 +15,14 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||||
from pyModeS.decoder.modes import data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 4,0
|
# BDS 4,0
|
||||||
# Selected vertical intention
|
# Selected vertical intention
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def isBDS40(msg):
|
def is40(msg):
|
||||||
"""Check if a message is likely to be BDS code 4,0
|
"""Check if a message is likely to be BDS code 4,0
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -14,15 +14,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||||
from pyModeS.decoder.modes import data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 4,4
|
# BDS 4,4
|
||||||
# Meteorological routine air report
|
# Meteorological routine air report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def isBDS44(msg, rev=False):
|
def is44(msg, rev=False):
|
||||||
"""Check if a message is likely to be BDS code 4,4
|
"""Check if a message is likely to be BDS code 4,4
|
||||||
Meteorological routine air report
|
Meteorological routine air report
|
||||||
|
|
||||||
|
@ -14,15 +14,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||||
from pyModeS.decoder.modes import data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 5,0
|
# BDS 5,0
|
||||||
# Track and turn report
|
# Track and turn report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def isBDS50(msg):
|
def is50(msg):
|
||||||
"""Check if a message is likely to be BDS code 5,0
|
"""Check if a message is likely to be BDS code 5,0
|
||||||
(Track and turn report)
|
(Track and turn report)
|
||||||
|
|
||||||
|
@ -14,15 +14,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||||
from pyModeS.decoder.modes import data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 5,3
|
# BDS 5,3
|
||||||
# Air-referenced state vector
|
# Air-referenced state vector
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def isBDS53(msg):
|
def is53(msg):
|
||||||
"""Check if a message is likely to be BDS code 5,3
|
"""Check if a message is likely to be BDS code 5,3
|
||||||
(Air-referenced state vector)
|
(Air-referenced state vector)
|
||||||
|
|
||||||
|
@ -14,15 +14,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.util import hex2bin, bin2int
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||||
from pyModeS.decoder.modes import data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 6,0
|
# BDS 6,0
|
||||||
# Heading and speed report
|
# Heading and speed report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def isBDS60(msg):
|
def is60(msg):
|
||||||
"""Check if a message is likely to be BDS code 6,0
|
"""Check if a message is likely to be BDS code 6,0
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
313
pyModeS/decoder/common.py
Normal file
313
pyModeS/decoder/common.py
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
from __future__ import absolute_import, print_function, division
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def hex2bin(hexstr):
|
||||||
|
"""Convert a hexdecimal string to binary string, with zero fillings. """
|
||||||
|
num_of_bits = len(hexstr) * 4
|
||||||
|
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
|
||||||
|
return binstr
|
||||||
|
|
||||||
|
|
||||||
|
def bin2int(binstr):
|
||||||
|
"""Convert a binary string to integer. """
|
||||||
|
return int(binstr, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def hex2int(hexstr):
|
||||||
|
"""Convert a hexdecimal string to integer. """
|
||||||
|
return int(hexstr, 16)
|
||||||
|
|
||||||
|
|
||||||
|
def bin2np(binstr):
|
||||||
|
"""Convert a binary string to numpy array. """
|
||||||
|
return np.array([int(i) for i in binstr])
|
||||||
|
|
||||||
|
|
||||||
|
def np2bin(npbin):
|
||||||
|
"""Convert a binary numpy array to string. """
|
||||||
|
return np.array2string(npbin, separator='')[1:-1]
|
||||||
|
|
||||||
|
|
||||||
|
def df(msg):
|
||||||
|
"""Decode Downlink Format vaule, bits 1 to 5."""
|
||||||
|
msgbin = hex2bin(msg)
|
||||||
|
return bin2int(msgbin[0:5])
|
||||||
|
|
||||||
|
|
||||||
|
def crc(msg, encode=False):
|
||||||
|
"""Mode-S Cyclic Redundancy Check
|
||||||
|
Detect if bit error occurs in the Mode-S message
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
encode (bool): True to encode the date only and return the checksum
|
||||||
|
Returns:
|
||||||
|
string: message checksum, or partity bits (encoder)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# the polynominal generattor code for CRC [1111111111111010000001001]
|
||||||
|
generator = np.array([1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1,0,0,1])
|
||||||
|
ng = len(generator)
|
||||||
|
|
||||||
|
msgnpbin = bin2np(hex2bin(msg))
|
||||||
|
|
||||||
|
if encode:
|
||||||
|
msgnpbin[-24:] = [0] * 24
|
||||||
|
|
||||||
|
# loop all bits, except last 24 piraty bits
|
||||||
|
for i in range(len(msgnpbin)-24):
|
||||||
|
if msgnpbin[i] == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# perform XOR, when 1
|
||||||
|
msgnpbin[i:i+ng] = np.bitwise_xor(msgnpbin[i:i+ng], generator)
|
||||||
|
|
||||||
|
# last 24 bits
|
||||||
|
reminder = np2bin(msgnpbin[-24:])
|
||||||
|
return reminder
|
||||||
|
|
||||||
|
|
||||||
|
def floor(x):
|
||||||
|
""" Mode-S floor function
|
||||||
|
|
||||||
|
Defined as the greatest integer value k, such that k <= x
|
||||||
|
|
||||||
|
eg.: floor(3.6) = 3, while floor(-3.6) = -4
|
||||||
|
"""
|
||||||
|
return int(np.floor(x))
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
DF = df(msg)
|
||||||
|
|
||||||
|
if DF in (17, 18):
|
||||||
|
addr = msg[2:8]
|
||||||
|
elif DF in (4, 5, 20, 21):
|
||||||
|
c0 = bin2int(crc(msg, encode=True))
|
||||||
|
c1 = hex2int(msg[-6:])
|
||||||
|
addr = '%06X' % (c0 ^ c1)
|
||||||
|
else:
|
||||||
|
addr = None
|
||||||
|
|
||||||
|
return addr
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def typecode(msg):
|
||||||
|
"""Type code of ADS-B message
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: type code number
|
||||||
|
"""
|
||||||
|
if df(msg) not in (17, 18):
|
||||||
|
return None
|
||||||
|
|
||||||
|
msgbin = hex2bin(msg)
|
||||||
|
return bin2int(msgbin[32:37])
|
||||||
|
|
||||||
|
|
||||||
|
def cprNL(lat):
|
||||||
|
"""NL() function in CPR decoding"""
|
||||||
|
|
||||||
|
if lat == 0:
|
||||||
|
return 59
|
||||||
|
|
||||||
|
if lat == 87 or lat == -87:
|
||||||
|
return 2
|
||||||
|
|
||||||
|
if lat > 87 or lat < -87:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
nz = 15
|
||||||
|
a = 1 - np.cos(np.pi / (2 * nz))
|
||||||
|
b = np.cos(np.pi / 180.0 * abs(lat)) ** 2
|
||||||
|
nl = 2 * np.pi / (np.arccos(1 - a/b))
|
||||||
|
NL = floor(nl)
|
||||||
|
return NL
|
||||||
|
|
||||||
|
def idcode(msg):
|
||||||
|
"""Computes identity (squawk code) from DF5 or DF21 message, bit 20-32.
|
||||||
|
credit: @fbyrkjeland
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (String): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string: squawk code
|
||||||
|
"""
|
||||||
|
|
||||||
|
if df(msg) not in [5, 21]:
|
||||||
|
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
||||||
|
|
||||||
|
mbin = 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.
|
||||||
|
credit: @fbyrkjeland
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (String): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: altitude in ft
|
||||||
|
"""
|
||||||
|
|
||||||
|
if df(msg) not in [4, 20]:
|
||||||
|
raise RuntimeError("Message must be Downlink Format 4 or 20.")
|
||||||
|
|
||||||
|
# Altitude code, bit 20-32
|
||||||
|
mbin = 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 = 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]
|
||||||
|
# D1 = mbin[27] # always zero
|
||||||
|
B2 = mbin[28]
|
||||||
|
D2 = mbin[29]
|
||||||
|
B4 = mbin[30]
|
||||||
|
D4 = mbin[31]
|
||||||
|
|
||||||
|
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
||||||
|
alt = gray2alt(graystr)
|
||||||
|
|
||||||
|
if mbit == '1': # unit in meter
|
||||||
|
vbin = mbin[19:25] + mbin[26:31]
|
||||||
|
alt = int(bin2int(vbin) * 3.28084) # convert to ft
|
||||||
|
|
||||||
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
def gray2alt(codestr):
|
||||||
|
gc500 = codestr[:8]
|
||||||
|
n500 = gray2int(gc500)
|
||||||
|
|
||||||
|
# in 100-ft step must be converted first
|
||||||
|
gc100 = codestr[8:]
|
||||||
|
n100 = gray2int(gc100)
|
||||||
|
|
||||||
|
if n100 in [0, 5, 6]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if n100 == 7:
|
||||||
|
n100 = 5
|
||||||
|
|
||||||
|
if n500%2:
|
||||||
|
n100 = 6 - n100
|
||||||
|
|
||||||
|
alt = (n500*500 + n100*100) - 1300
|
||||||
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
def gray2int(graystr):
|
||||||
|
"""Convert greycode to binary"""
|
||||||
|
num = bin2int(graystr)
|
||||||
|
num ^= (num >> 8)
|
||||||
|
num ^= (num >> 4)
|
||||||
|
num ^= (num >> 2)
|
||||||
|
num ^= (num >> 1)
|
||||||
|
return num
|
||||||
|
|
||||||
|
|
||||||
|
def data(msg):
|
||||||
|
"""Return the data frame in the message, bytes 9 to 22"""
|
||||||
|
return msg[8:-6]
|
||||||
|
|
||||||
|
|
||||||
|
def allzeros(msg):
|
||||||
|
"""check if the data bits are all zeros
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (String): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True or False
|
||||||
|
"""
|
||||||
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
|
if bin2int(d) > 0:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# status bit, most significant bit, least significant bit
|
||||||
|
status = int(data[sb-1])
|
||||||
|
value = bin2int(data[msb-1:lsb])
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
if value != 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
@ -1,9 +1,7 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
|
|
||||||
from pyModeS.decoder.bds.bds40 import *
|
from pyModeS.decoder.bds.bds40 import *
|
||||||
from pyModeS.decoder.bds.bds44 import *
|
|
||||||
from pyModeS.decoder.bds.bds50 import *
|
from pyModeS.decoder.bds.bds50 import *
|
||||||
from pyModeS.decoder.bds.bds53 import *
|
|
||||||
from pyModeS.decoder.bds.bds60 import *
|
from pyModeS.decoder.bds.bds60 import *
|
||||||
|
|
||||||
def BDS(msg):
|
def BDS(msg):
|
||||||
@ -14,8 +12,5 @@ def BDS(msg):
|
|||||||
return infer(msg)
|
return infer(msg)
|
||||||
|
|
||||||
def icao(msg):
|
def icao(msg):
|
||||||
import warnings
|
from pyModeS.decoder.common import icao
|
||||||
from pyModeS.decoder.modes import icao
|
|
||||||
warnings.simplefilter('always', DeprecationWarning)
|
|
||||||
warnings.warn("pms.ehs.icao() deprecated, please use pms.modes.icao() instead", DeprecationWarning)
|
|
||||||
return icao(msg)
|
return icao(msg)
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
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.
|
|
||||||
credit: @fbyrkjeland
|
|
||||||
|
|
||||||
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.
|
|
||||||
credit: @fbyrkjeland
|
|
||||||
|
|
||||||
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]
|
|
||||||
# D1 = mbin[27] # always zero
|
|
||||||
B2 = mbin[28]
|
|
||||||
D2 = mbin[29]
|
|
||||||
B4 = mbin[30]
|
|
||||||
D4 = mbin[31]
|
|
||||||
|
|
||||||
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
|
||||||
alt = gray2alt(graystr)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def gray2alt(codestr):
|
|
||||||
gc500 = codestr[:8]
|
|
||||||
n500 = gray2int(gc500)
|
|
||||||
|
|
||||||
# in 100-ft step must be converted first
|
|
||||||
gc100 = codestr[8:]
|
|
||||||
n100 = gray2int(gc100)
|
|
||||||
|
|
||||||
if n100 in [0, 5, 6]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if n100 == 7:
|
|
||||||
n100 = 5
|
|
||||||
|
|
||||||
if n500%2:
|
|
||||||
n100 = 6 - n100
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def data(msg):
|
|
||||||
"""Return the data frame in the message, bytes 9 to 22"""
|
|
||||||
return msg[8:-6]
|
|
||||||
|
|
||||||
|
|
||||||
def allzeros(msg):
|
|
||||||
"""check if the data bits are all zeros
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (String): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True or False
|
|
||||||
"""
|
|
||||||
d = util.hex2bin(data(msg))
|
|
||||||
|
|
||||||
if util.bin2int(d) > 0:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 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 True
|
|
||||||
|
|
||||||
return False
|
|
@ -1,118 +0,0 @@
|
|||||||
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Common functions for ADS-B and Mode-S EHS decoder
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
def hex2bin(hexstr):
|
|
||||||
"""Convert a hexdecimal string to binary string, with zero fillings. """
|
|
||||||
num_of_bits = len(hexstr) * 4
|
|
||||||
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
|
|
||||||
return binstr
|
|
||||||
|
|
||||||
|
|
||||||
def bin2int(binstr):
|
|
||||||
"""Convert a binary string to integer. """
|
|
||||||
return int(binstr, 2)
|
|
||||||
|
|
||||||
|
|
||||||
def hex2int(hexstr):
|
|
||||||
"""Convert a hexdecimal string to integer. """
|
|
||||||
return int(hexstr, 16)
|
|
||||||
|
|
||||||
|
|
||||||
def bin2np(binstr):
|
|
||||||
"""Convert a binary string to numpy array. """
|
|
||||||
return np.array([int(i) for i in binstr])
|
|
||||||
|
|
||||||
|
|
||||||
def np2bin(npbin):
|
|
||||||
"""Convert a binary numpy array to string. """
|
|
||||||
return np.array2string(npbin, separator='')[1:-1]
|
|
||||||
|
|
||||||
|
|
||||||
def df(msg):
|
|
||||||
"""Decode Downlink Format vaule, bits 1 to 5."""
|
|
||||||
msgbin = hex2bin(msg)
|
|
||||||
return bin2int(msgbin[0:5])
|
|
||||||
|
|
||||||
|
|
||||||
def crc(msg, encode=False):
|
|
||||||
"""Mode-S Cyclic Redundancy Check
|
|
||||||
Detect if bit error occurs in the Mode-S message
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
encode (bool): True to encode the date only and return the checksum
|
|
||||||
Returns:
|
|
||||||
string: message checksum, or partity bits (encoder)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# the polynominal generattor code for CRC [1111111111111010000001001]
|
|
||||||
generator = np.array([1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1,0,0,1])
|
|
||||||
ng = len(generator)
|
|
||||||
|
|
||||||
msgnpbin = bin2np(hex2bin(msg))
|
|
||||||
|
|
||||||
if encode:
|
|
||||||
msgnpbin[-24:] = [0] * 24
|
|
||||||
|
|
||||||
# loop all bits, except last 24 piraty bits
|
|
||||||
for i in range(len(msgnpbin)-24):
|
|
||||||
if msgnpbin[i] == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# perform XOR, when 1
|
|
||||||
msgnpbin[i:i+ng] = np.bitwise_xor(msgnpbin[i:i+ng], generator)
|
|
||||||
|
|
||||||
# last 24 bits
|
|
||||||
reminder = np2bin(msgnpbin[-24:])
|
|
||||||
return reminder
|
|
||||||
|
|
||||||
|
|
||||||
def floor(x):
|
|
||||||
""" Mode-S floor function
|
|
||||||
|
|
||||||
Defined as the greatest integer value k, such that k <= x
|
|
||||||
|
|
||||||
eg.: floor(3.6) = 3, while floor(-3.6) = -4
|
|
||||||
"""
|
|
||||||
return int(np.floor(x))
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
@ -6,7 +6,7 @@ import curses
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import time
|
import time
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from pyModeS.decoder import util
|
import pyModeS as pms
|
||||||
from pyModeS.extra.beastclient import BaseClient
|
from pyModeS.extra.beastclient import BaseClient
|
||||||
from pyModeS.streamer.stream import Stream
|
from pyModeS.streamer.stream import Stream
|
||||||
from pyModeS.streamer.screen import Screen
|
from pyModeS.streamer.screen import Screen
|
||||||
@ -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 = util.df(msg)
|
df = pms.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)
|
||||||
|
@ -58,8 +58,8 @@ def test_adsb_velocity():
|
|||||||
vgs = adsb.velocity("8D485020994409940838175B284F")
|
vgs = adsb.velocity("8D485020994409940838175B284F")
|
||||||
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
|
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
|
||||||
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
|
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
|
||||||
assert vgs == (159, 182.9, -832, 'GS')
|
assert vgs == (159, 182.88, -832, 'GS')
|
||||||
assert vas == (376, 244.0, -2304, 'AS')
|
assert vas == (375, 243.98, -2304, 'TAS')
|
||||||
assert vgs_surface == (19.0, 42.2, 0 , 'GS')
|
assert vgs_surface == (19.0, 42.2, 0 , 'GS')
|
||||||
assert adsb.altitude_diff('8D485020994409940838175B284F') == 550
|
assert adsb.altitude_diff('8D485020994409940838175B284F') == 550
|
||||||
|
|
||||||
|
@ -1,17 +1,4 @@
|
|||||||
from pyModeS import bds, modes
|
from pyModeS import bds, ehs, els
|
||||||
|
|
||||||
def test_ehs_icao():
|
|
||||||
assert modes.icao("A0001839CA3800315800007448D9") == '400940'
|
|
||||||
assert modes.icao("A000139381951536E024D4CCF6B5") == '3C4DD2'
|
|
||||||
assert modes.icao("A000029CFFBAA11E2004727281F1") == '4243D0'
|
|
||||||
|
|
||||||
|
|
||||||
def test_modes_altcode():
|
|
||||||
assert modes.altcode("A02014B400000000000000F9D514") == 32300
|
|
||||||
|
|
||||||
def test_modes_idcode():
|
|
||||||
assert modes.idcode("A800292DFFBBA9383FFCEB903D01") == '1346'
|
|
||||||
|
|
||||||
|
|
||||||
def test_bds_infer():
|
def test_bds_infer():
|
||||||
assert bds.infer("A0001838201584F23468207CDFA5") == 'BDS20'
|
assert bds.infer("A0001838201584F23468207CDFA5") == 'BDS20'
|
||||||
@ -25,13 +12,18 @@ def test_bds_is50or60():
|
|||||||
assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == 'BDS60'
|
assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == 'BDS60'
|
||||||
|
|
||||||
def test_els_BDS20_callsign():
|
def test_els_BDS20_callsign():
|
||||||
assert bds.bds20.callsign("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
|
assert bds.bds20.cs20("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
|
||||||
assert bds.bds20.callsign("A0001993202422F2E37CE038738E") == 'IBK2873_'
|
assert bds.bds20.cs20("A0001993202422F2E37CE038738E") == 'IBK2873_'
|
||||||
|
assert els.cs20("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
|
||||||
|
assert els.cs20("A0001993202422F2E37CE038738E") == 'IBK2873_'
|
||||||
|
|
||||||
def test_ehs_BDS40_functions():
|
def test_ehs_BDS40_functions():
|
||||||
assert bds.bds40.alt40mcp("A000029C85E42F313000007047D3") == 3008
|
assert bds.bds40.alt40mcp("A000029C85E42F313000007047D3") == 3008
|
||||||
assert bds.bds40.alt40fms("A000029C85E42F313000007047D3") == 3008
|
assert bds.bds40.alt40fms("A000029C85E42F313000007047D3") == 3008
|
||||||
assert bds.bds40.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
assert bds.bds40.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
||||||
|
assert ehs.alt40mcp("A000029C85E42F313000007047D3") == 3008
|
||||||
|
assert ehs.alt40fms("A000029C85E42F313000007047D3") == 3008
|
||||||
|
assert ehs.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
||||||
|
|
||||||
def test_ehs_BDS50_functions():
|
def test_ehs_BDS50_functions():
|
||||||
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||||
@ -40,6 +32,12 @@ def test_ehs_BDS50_functions():
|
|||||||
assert bds.bds50.gs50("A000139381951536E024D4CCF6B5") == 438
|
assert bds.bds50.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||||
assert bds.bds50.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
assert bds.bds50.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||||
assert bds.bds50.tas50("A000139381951536E024D4CCF6B5") == 424
|
assert bds.bds50.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||||
|
assert ehs.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||||
|
assert ehs.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
||||||
|
assert ehs.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||||
|
assert ehs.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||||
|
assert ehs.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||||
|
assert ehs.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||||
|
|
||||||
def test_ehs_BDS60_functions():
|
def test_ehs_BDS60_functions():
|
||||||
assert bds.bds60.hdg60("A00004128F39F91A7E27C46ADC21") == 42.715
|
assert bds.bds60.hdg60("A00004128F39F91A7E27C46ADC21") == 42.715
|
||||||
@ -47,20 +45,8 @@ def test_ehs_BDS60_functions():
|
|||||||
assert bds.bds60.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
assert bds.bds60.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
||||||
assert bds.bds60.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
assert bds.bds60.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
||||||
assert bds.bds60.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
assert bds.bds60.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
||||||
|
assert ehs.hdg60("A00004128F39F91A7E27C46ADC21") == 42.715
|
||||||
def test_graycode_to_altitude():
|
assert ehs.ias60("A00004128F39F91A7E27C46ADC21") == 252
|
||||||
assert modes.gray2alt('00000000010') == -1000
|
assert ehs.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
||||||
assert modes.gray2alt('00000001010') == -500
|
assert ehs.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
||||||
assert modes.gray2alt('00000011011') == -100
|
assert ehs.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
||||||
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
|
|
42
tests/test_common.py
Normal file
42
tests/test_common.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
|
def test_hex2bin():
|
||||||
|
assert common.hex2bin('6E406B') == "011011100100000001101011"
|
||||||
|
|
||||||
|
def test_crc_decode():
|
||||||
|
checksum = common.crc("8D406B902015A678D4D220AA4BDA")
|
||||||
|
assert checksum == "000000000000000000000000"
|
||||||
|
|
||||||
|
def test_crc_encode():
|
||||||
|
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||||
|
assert common.hex2bin("AA4BDA") == parity
|
||||||
|
|
||||||
|
def test_icao():
|
||||||
|
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||||
|
assert common.icao("A0001839CA3800315800007448D9") == '400940'
|
||||||
|
assert common.icao("A000139381951536E024D4CCF6B5") == '3C4DD2'
|
||||||
|
assert common.icao("A000029CFFBAA11E2004727281F1") == '4243D0'
|
||||||
|
|
||||||
|
def test_modes_altcode():
|
||||||
|
assert common.altcode("A02014B400000000000000F9D514") == 32300
|
||||||
|
|
||||||
|
def test_modes_idcode():
|
||||||
|
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == '1346'
|
||||||
|
|
||||||
|
def test_graycode_to_altitude():
|
||||||
|
assert common.gray2alt('00000000010') == -1000
|
||||||
|
assert common.gray2alt('00000001010') == -500
|
||||||
|
assert common.gray2alt('00000011011') == -100
|
||||||
|
assert common.gray2alt('00000011010') == 0
|
||||||
|
assert common.gray2alt('00000011110') == 100
|
||||||
|
assert common.gray2alt('00000010011') == 600
|
||||||
|
assert common.gray2alt('00000110010') == 1000
|
||||||
|
assert common.gray2alt('00001001001') == 5800
|
||||||
|
assert common.gray2alt('00011100100') == 10300
|
||||||
|
assert common.gray2alt('01100011010') == 32000
|
||||||
|
assert common.gray2alt('01110000100') == 46300
|
||||||
|
assert common.gray2alt('01010101100') == 50200
|
||||||
|
assert common.gray2alt('11011110100') == 73200
|
||||||
|
assert common.gray2alt('10000000011') == 126600
|
||||||
|
assert common.gray2alt('10000000001') == 126700
|
@ -1,15 +0,0 @@
|
|||||||
from pyModeS import util
|
|
||||||
|
|
||||||
|
|
||||||
def test_hex2bin():
|
|
||||||
assert util.hex2bin('6E406B') == "011011100100000001101011"
|
|
||||||
|
|
||||||
|
|
||||||
def test_crc_decode():
|
|
||||||
checksum = util.crc("8D406B902015A678D4D220AA4BDA")
|
|
||||||
assert checksum == "000000000000000000000000"
|
|
||||||
|
|
||||||
|
|
||||||
def test_crc_encode():
|
|
||||||
parity = util.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
|
||||||
assert util.hex2bin("AA4BDA") == parity
|
|
Loading…
Reference in New Issue
Block a user