Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c52db318d | ||
|
|
e692d30d66 | ||
|
|
fd8bb8386f | ||
|
|
2b1f2a5878 | ||
|
|
3538645e22 | ||
|
|
a9887d6238 | ||
|
|
7c8fd74db7 | ||
|
|
35b0d63fa9 | ||
|
|
2ae7bf4c19 | ||
|
|
e8449154ca | ||
|
|
fbe5b63286 | ||
|
|
45c32cd7aa | ||
|
|
c708d57fcc | ||
|
|
53a258bd35 | ||
|
|
d9e277dc54 | ||
|
|
854386fbd4 | ||
|
|
3117febac0 | ||
|
|
8693c51998 | ||
|
|
8c90371111 | ||
|
|
c6952e4e63 | ||
|
|
98e5d81ae1 | ||
|
|
fd557d1c40 | ||
|
|
cdbcf47bc2 | ||
|
|
5f7e28950c | ||
|
|
c1d0a925d5 | ||
|
|
b0a71717f0 | ||
|
|
25e5a4e412 | ||
|
|
d3022c6fe5 | ||
|
|
aa9f49b470 | ||
|
|
27daf52850 | ||
|
|
0e29a4d18a | ||
|
|
1e842e4789 | ||
|
|
1220368ada | ||
|
|
fb32ace095 | ||
|
|
abafd97b3f | ||
|
|
0a38231713 | ||
|
|
2fd822d275 | ||
|
|
8de58bb01f | ||
|
|
140f312c11 | ||
|
|
fefd26a787 | ||
|
|
1e82b5c59c | ||
|
|
a1615767b6 | ||
|
|
03a62dc68c | ||
|
|
46fee6b7dc | ||
|
|
8aa821c8dd | ||
|
|
621d3e7580 | ||
|
|
3b3609bf2b | ||
|
|
ef2268127c | ||
|
|
cc66e2f4e4 | ||
|
|
128163b41d | ||
|
|
8933afb1c1 | ||
|
|
15f2833aee |
184
README.rst
184
README.rst
@@ -1,21 +1,30 @@
|
||||
A Python Mode-S Decoder
|
||||
=======================
|
||||
The Python Mode-S Decoder
|
||||
=========================
|
||||
|
||||
Python library for Mode-S message decoding. Two separate methods are
|
||||
implemented to decode the following messages:
|
||||
Python library for Mode-S message decoding. Support Downlink Formats (DF) are:
|
||||
|
||||
- Automatic Dependent Surveillance - Broadcast (ADS-B) (DF17)
|
||||
|
||||
- aircraft information that contains: ICAO address, position,
|
||||
altitude, velocity (ground speed), callsign, etc.
|
||||
- aircraft information that contains: ICAO address, position, altitude, velocity (ground speed), callsign, etc.
|
||||
|
||||
- Mode-S Enhanced Surveillance (EHS) (DF20 and DF21)
|
||||
- Mode-S Elementary Surveillance (ELS) (DF4 and DF5).
|
||||
|
||||
- additional information in response to SSR interrogation, such as:
|
||||
true airspeed, indicated airspeed, mach number, track angle,
|
||||
heading, roll angle, etc.
|
||||
- DF4: Altitude
|
||||
- DF5: Squawk code
|
||||
|
||||
A detailed manual on Mode-S decoding is published by the author, at:
|
||||
- Mode-S Enhanced Surveillance (EHS) (DF20 and DF21). Additional information in response to SSR interrogation, such as: true airspeed, indicated airspeed, mach number, wind, temperature, etc.
|
||||
|
||||
- DF20: Altitude
|
||||
- DF21: Squawk code
|
||||
- BDS 2,0 Aircraft identification
|
||||
- BDS 2,1 Aircraft and airline registration markings
|
||||
- BDS 4,0 Selected vertical intention
|
||||
- BDS 4,4 Meteorological routine air report
|
||||
- BDS 5,0 Track and turn report
|
||||
- BDS 5,3 Air-referenced state vector
|
||||
- BDS 6,0 Heading and speed report
|
||||
|
||||
Detailed manual on Mode-S decoding is published by the author, at:
|
||||
http://adsb-decode-guide.readthedocs.io
|
||||
|
||||
|
||||
@@ -27,92 +36,147 @@ https://github.com/junzis/pyModeS
|
||||
API documentation at:
|
||||
http://pymodes.readthedocs.io
|
||||
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
Checkout source code, or install using pip:
|
||||
The easiest installation is to use pip:
|
||||
|
||||
::
|
||||
|
||||
pip install pyModeS
|
||||
pip install pyModeS
|
||||
|
||||
Usage
|
||||
-----
|
||||
To install latest devlopment version from the GitHub:
|
||||
|
||||
::
|
||||
|
||||
pip install git+https://github.com/junzis/pyModeS
|
||||
|
||||
|
||||
Use the library
|
||||
---------------
|
||||
|
||||
.. code:: python
|
||||
|
||||
import pyModeS as pms
|
||||
import pyModeS as pms
|
||||
|
||||
|
||||
Common function for Mode-S message:
|
||||
Common functions:
|
||||
*****************
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.df(msg) # Downlink Format
|
||||
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
||||
pms.df(msg) # Downlink Format
|
||||
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
||||
|
||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||
pms.bin2int(str) # Convert binary string to integer
|
||||
pms.hex2int(str) # Convert hexadecimal string to integer
|
||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||
pms.bin2int(str) # Convert binary string to integer
|
||||
pms.hex2int(str) # Convert hexadecimal string to integer
|
||||
|
||||
pms.gray2int(str) # Convert grey code to interger
|
||||
|
||||
|
||||
Core functions for ADS-B decoding:
|
||||
**********************************
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.adsb.icao(msg)
|
||||
pms.adsb.callsign(msg)
|
||||
pms.adsb.icao(msg)
|
||||
pms.adsb.typecode(msg)
|
||||
|
||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
||||
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||
# typecode 1-4
|
||||
pms.adsb.callsign(msg)
|
||||
|
||||
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
# typecode 5-8 (surface) and 9-18 (airborne)
|
||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
||||
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||
|
||||
pms.adsb.altitude(msg)
|
||||
pms.adsb.velocity(msg)
|
||||
pms.adsb.speed_heading(msg)
|
||||
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
**Hint: When you have a fix position of the aircraft, it is convenient to
|
||||
pms.adsb.altitude(msg)
|
||||
|
||||
# typecode: 19
|
||||
pms.adsb.velocity(msg) # handles both surface & airborne messages
|
||||
pms.adsb.speed_heading(msg) # handles both surface & airborne messages
|
||||
pms.adsb.surface_velocity(msg)
|
||||
pms.adsb.airborne_velocity(msg)
|
||||
|
||||
|
||||
Note: When you have a fix position of the aircraft, it is convenient to
|
||||
use `position_with_ref()` method to decode with only one position message
|
||||
(either odd or even). This works with both airborne and surface position
|
||||
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 EHS decoding:
|
||||
Core functions for ELS decoding:
|
||||
********************************
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.ehs.icao(msg) # ICAO address
|
||||
pms.ehs.BDS(msg) # Comm-B Data Selector Version
|
||||
pms.els.icao(msg) # ICAO address
|
||||
pms.els.df4alt(msg) # Altitude from any DF4 message
|
||||
pms.ehs.df5id(msg) # Squawk code from any DF5 message
|
||||
|
||||
# for BDS version 2,0
|
||||
pms.ehs.callsign(msg) # Aircraft callsign
|
||||
|
||||
# for BDS version 4,0
|
||||
pms.ehs.alt_mcp(msg) # MCP/FCU selected altitude (ft)
|
||||
pms.ehs.alt_fms(msg) # FMS selected altitude (ft)
|
||||
pms.ehs.alt_pbaro(msg) # Barometric pressure (mb)
|
||||
Core functions for EHS decoding:
|
||||
********************************
|
||||
|
||||
# for BDS version 5,0
|
||||
pms.ehs.roll(msg) # roll angle (deg)
|
||||
pms.ehs.track(msg) # track angle (deg)
|
||||
pms.ehs.gs(msg) # ground speed (kt)
|
||||
pms.ehs.rtrack(msg) # track angle rate (deg/sec)
|
||||
pms.ehs.tas(msg) # true airspeed (kt)
|
||||
.. code:: python
|
||||
|
||||
# for BDS version 6,0
|
||||
pms.ehs.heading(msg) # heading (deg)
|
||||
pms.ehs.ias(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach(msg) # MACH number
|
||||
pms.ehs.baro_vr(msg) # barometric altitude rate (ft/min)
|
||||
pms.ehs.ins_vr(msg) # inertial vertical speed (ft/min)
|
||||
pms.ehs.icao(msg) # ICAO address
|
||||
pms.ehs.df20alt(msg) # Altitude from any DF20 message
|
||||
pms.ehs.df21id(msg) # Squawk code from any DF21 message
|
||||
|
||||
pms.ehs.BDS(msg) # Comm-B Data Selector Version
|
||||
|
||||
# for BDS version 2,0
|
||||
pms.ehs.isBDS20(msg) # Check if message is BDS 2,0
|
||||
pms.ehs.callsign(msg) # Aircraft callsign
|
||||
|
||||
# for BDS version 4,0
|
||||
pms.ehs.isBDS40(msg) # Check if message is BDS 4,0
|
||||
pms.ehs.alt40mcp(msg) # MCP/FCU selected altitude (ft)
|
||||
pms.ehs.alt40fms(msg) # FMS selected altitude (ft)
|
||||
pms.ehs.p40baro(msg) # Barometric pressure (mb)
|
||||
|
||||
# for BDS version 4,4
|
||||
pms.ehs.isBDS44(msg, rev=False) # Check if message is BDS 4,4
|
||||
pms.ehs.wind44(msg, rev=False) # wind speed (kt) and heading (deg)
|
||||
pms.ehs.temp44(msg, rev=False) # temperature (C)
|
||||
pms.ehs.p44(msg, rev=False) # pressure (hPa)
|
||||
pms.ehs.hum44(msg, rev=False) # humidity (%)
|
||||
|
||||
# for BDS version 5,0
|
||||
pms.ehs.isBDS50(msg) # Check if message is BDS 5,0
|
||||
pms.ehs.roll50(msg) # roll angle (deg)
|
||||
pms.ehs.trk50(msg) # track angle (deg)
|
||||
pms.ehs.gs50(msg) # ground speed (kt)
|
||||
pms.ehs.rtrk50(msg) # track angle rate (deg/sec)
|
||||
pms.ehs.tas50(msg) # true airspeed (kt)
|
||||
|
||||
# for BDS version 5,3
|
||||
pms.ehs.isBDS53(msg) # Check if message is BDS 5,3
|
||||
pms.ehs.hdg53(msg) # magnetic heading (deg)
|
||||
pms.ehs.ias53(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach53(msg) # MACH number
|
||||
pms.ehs.tas53(msg) # true airspeed (kt)
|
||||
pms.ehs.vr53(msg) # vertical rate (fpm)
|
||||
|
||||
# for BDS version 6,0
|
||||
pms.ehs.isBDS60(msg) # Check if message is BDS 6,0
|
||||
pms.ehs.hdg60(msg) # heading (deg)
|
||||
pms.ehs.ias60(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach60(msg) # MACH number
|
||||
pms.ehs.vr60baro(msg) # barometric altitude rate (ft/min)
|
||||
pms.ehs.vr60ins(msg) # inertial vertical speed (ft/min)
|
||||
|
||||
Developement
|
||||
------------
|
||||
To run tests, run the following commands:
|
||||
```
|
||||
$ tox
|
||||
```
|
||||
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ tox
|
||||
|
||||
@@ -1 +1 @@
|
||||
pyModeS==1.0.5
|
||||
pyModeS==1.1.0
|
||||
|
||||
@@ -3,3 +3,4 @@ from __future__ import absolute_import, print_function, division
|
||||
from .util import *
|
||||
from . import adsb
|
||||
from . import ehs
|
||||
from . import els
|
||||
|
||||
149
pyModeS/adsb.py
149
pyModeS/adsb.py
@@ -17,6 +17,7 @@
|
||||
A python package for decoding ABS-D messages.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import math
|
||||
from . import util
|
||||
|
||||
@@ -265,10 +266,9 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
||||
of the true position.
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
@@ -468,9 +468,14 @@ def altitude(msg):
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
if typecode(msg) < 9 or typecode(msg) > 18:
|
||||
|
||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
||||
raise RuntimeError("%s: Not a position message" % msg)
|
||||
|
||||
if typecode(msg) >=5 and typecode(msg) <=8:
|
||||
# surface position, altitude 0
|
||||
return 0
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
q = msgbin[47]
|
||||
if q:
|
||||
@@ -491,7 +496,7 @@ def nic(msg):
|
||||
int: NIC number (from 0 to 11), -1 if not applicable
|
||||
"""
|
||||
if typecode(msg) < 9 or typecode(msg) > 18:
|
||||
raise RuntimeError("%s: Not a airborne position message" % msg)
|
||||
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
tc = typecode(msg)
|
||||
@@ -534,62 +539,158 @@ def nic(msg):
|
||||
|
||||
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), heading (degree),
|
||||
(int, float, int, string): speed (kt), ground track or heading (degree),
|
||||
rate of climb/descend (ft/min), and speed type
|
||||
('GS' for ground speed, 'AS' for airspeed)
|
||||
"""
|
||||
|
||||
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" % msg)
|
||||
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 = util.bin2int(msgbin[45])
|
||||
v_ew_sign = -1 if int(msgbin[45]) else 1
|
||||
v_ew = util.bin2int(msgbin[46:56]) - 1 # east-west velocity
|
||||
|
||||
v_ns_sign = util.bin2int(msgbin[56])
|
||||
v_ns_sign = -1 if int(msgbin[56]) else 1
|
||||
v_ns = util.bin2int(msgbin[57:67]) - 1 # north-south velocity
|
||||
|
||||
v_we = -1*v_ew if v_ew_sign else v_ew
|
||||
v_sn = -1*v_ns if v_ns_sign else v_ns
|
||||
|
||||
v_we = v_ew_sign * v_ew
|
||||
v_sn = v_ns_sign * v_ns
|
||||
|
||||
spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts
|
||||
|
||||
hdg = math.atan2(v_we, v_sn)
|
||||
hdg = math.degrees(hdg) # convert to degrees
|
||||
hdg = hdg if hdg >= 0 else hdg + 360 # no negative val
|
||||
trk = math.atan2(v_we, v_sn)
|
||||
trk = math.degrees(trk) # convert to degrees
|
||||
trk = trk if trk >= 0 else trk + 360 # no negative val
|
||||
|
||||
tag = 'GS'
|
||||
trk_or_hdg = trk
|
||||
|
||||
else:
|
||||
hdg = util.bin2int(msgbin[46:56]) / 1024.0 * 360.0
|
||||
spd = util.bin2int(msgbin[57:67])
|
||||
|
||||
tag = 'AS'
|
||||
trk_or_hdg = hdg
|
||||
|
||||
vr_sign = util.bin2int(msgbin[68])
|
||||
vr_sign = -1 if int(msgbin[68]) else 1
|
||||
vr = (util.bin2int(msgbin[69:78]) - 1) * 64 # vertical rate, fpm
|
||||
rocd = -1*vr if vr_sign else vr # rate of climb/descend
|
||||
rocd = vr_sign * vr
|
||||
|
||||
return int(spd), round(hdg, 1), int(rocd), tag
|
||||
return int(spd), round(trk_or_hdg, 1), int(rocd), tag
|
||||
|
||||
|
||||
def speed_heading(msg):
|
||||
"""Get speed and heading only from the velocity message
|
||||
|
||||
def surface_velocity(msg):
|
||||
"""Decode surface velocity from from a surface position message
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), heading (degree)
|
||||
(int, float, int, string): speed (kt), ground track (degree),
|
||||
rate of climb/descend (ft/min), and speed type
|
||||
('GS' for ground speed, 'AS' for airspeed)
|
||||
"""
|
||||
spd, hdg, rocd, tag = velocity(msg)
|
||||
return spd, hdg
|
||||
|
||||
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.
|
||||
|
||||
669
pyModeS/ehs.py
669
pyModeS/ehs.py
@@ -17,47 +17,31 @@
|
||||
A python package for decoding ModeS (DF20, DF21) messages.
|
||||
"""
|
||||
|
||||
from . import util
|
||||
from .util import crc
|
||||
|
||||
|
||||
def df(msg):
|
||||
"""Get the downlink format (DF) number
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: DF number
|
||||
"""
|
||||
return util.df(msg)
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from . import util, modes_common
|
||||
|
||||
def icao(msg):
|
||||
return modes_common.icao(msg)
|
||||
|
||||
def data(msg):
|
||||
"""Return the data frame in the message, bytes 9 to 22"""
|
||||
return msg[8:22]
|
||||
|
||||
|
||||
def icao(msg):
|
||||
"""Calculate the ICAO address from an Mode-S message
|
||||
with DF4, DF5, DF20, DF21
|
||||
def isnull(msg):
|
||||
"""check if the data bits are all zeros
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
String: ICAO address in 6 bytes hexadecimal string
|
||||
bool: True or False
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if df(msg) not in (4, 5, 20, 21):
|
||||
# raise RuntimeError("Message DF must be in (4, 5, 20, 21)")
|
||||
return None
|
||||
|
||||
c0 = util.bin2int(crc(msg, encode=True))
|
||||
c1 = util.hex2int(msg[-6:])
|
||||
icao = '%06X' % (c0 ^ c1)
|
||||
return icao
|
||||
|
||||
if util.bin2int(d) > 0:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def checkbits(data, sb, msb, lsb):
|
||||
"""Check if the status bit and field bits are consistency. This Function
|
||||
@@ -74,9 +58,97 @@ def checkbits(data, sb, msb, lsb):
|
||||
|
||||
return True
|
||||
|
||||
# ------------------------------------------
|
||||
# Common functions
|
||||
# ------------------------------------------
|
||||
|
||||
def df20alt(msg):
|
||||
"""Computes the altitude from DF20 message, bit 20-32
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in ft
|
||||
"""
|
||||
|
||||
if util.df(msg) != 20:
|
||||
raise RuntimeError("Message must be Downlink Format 20.")
|
||||
|
||||
return modes_common.altcode(msg)
|
||||
|
||||
|
||||
def df21id(msg):
|
||||
"""Computes identity (squawk code) from DF21, bit 20-32
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
string: squawk code
|
||||
"""
|
||||
|
||||
if util.df(msg) != 21:
|
||||
raise RuntimeError("Message must be Downlink Format 21.")
|
||||
|
||||
return modes_common.idcode(msg)
|
||||
|
||||
# ------------------------------------------
|
||||
# DF 20/21, BDS 2,0
|
||||
# BDS 1,7
|
||||
# Common usage GICB capability report
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS17(msg):
|
||||
"""Check if a message is likely to be BDS code 1,7
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
if util.bin2int(d[28:56]) != 0:
|
||||
result &= False
|
||||
|
||||
caps = cap17(msg)
|
||||
|
||||
# basic BDS codes for ADS-B shall be supported
|
||||
# assuming ADS-B out is installed (2017EU/2020US mandate)
|
||||
if not set(['BDS05', 'BDS06', 'BDS09', 'BDS20']).issubset(caps):
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
def cap17(msg):
|
||||
"""Extract capacities from BDS 1,7 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
list: list of suport BDS codes
|
||||
"""
|
||||
allbds = ['05', '06', '07', '08', '09', '0A', '20', '21', '40', '41',
|
||||
'42', '43', '44', '45', '48', '50', '51', '52', '53', '54',
|
||||
'55', '56', '5F', '60', 'NA', 'NA', 'E1', 'E2']
|
||||
|
||||
d = util.hex2bin(data(msg))
|
||||
idx = [i for i, v in enumerate(d[:28]) if v=='1']
|
||||
capacity = ['BDS'+allbds[i] for i in idx if allbds[i] is not 'NA']
|
||||
|
||||
return capacity
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 2,0
|
||||
# Aircraft identification
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS20(msg):
|
||||
@@ -89,6 +161,9 @@ def isBDS20(msg):
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 14, and 27
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
@@ -132,7 +207,8 @@ def callsign(msg):
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# DF 20/21, BDS 4,0
|
||||
# BDS 4,0
|
||||
# Selected vertical intention
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS40(msg):
|
||||
@@ -145,6 +221,9 @@ def isBDS40(msg):
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 14, and 27
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
@@ -163,7 +242,7 @@ def isBDS40(msg):
|
||||
return result
|
||||
|
||||
|
||||
def alt_mcp(msg):
|
||||
def alt40mcp(msg):
|
||||
"""Selected altitude, MCP/FCU
|
||||
|
||||
Args:
|
||||
@@ -173,11 +252,15 @@ def alt_mcp(msg):
|
||||
int: altitude in feet
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
alt = util.bin2int(d[1:13]) * 16 # ft
|
||||
return alt
|
||||
|
||||
|
||||
def alt_fms(msg):
|
||||
def alt40fms(msg):
|
||||
"""Selected altitude, FMS
|
||||
|
||||
Args:
|
||||
@@ -187,11 +270,15 @@ def alt_fms(msg):
|
||||
int: altitude in feet
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[13] == '0':
|
||||
return None
|
||||
|
||||
alt = util.bin2int(d[14:26]) * 16 # ft
|
||||
return alt
|
||||
|
||||
|
||||
def pbaro(msg):
|
||||
def p40baro(msg):
|
||||
"""Barometric pressure setting
|
||||
|
||||
Args:
|
||||
@@ -201,134 +288,206 @@ def pbaro(msg):
|
||||
float: pressure in millibar
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[26] == '0':
|
||||
return None
|
||||
|
||||
p = util.bin2int(d[27:39]) * 0.1 + 800 # millibar
|
||||
return p
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# DF 20/21, BDS 4,4
|
||||
# BDS 4,4
|
||||
# Meteorological routine air report
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS44(msg):
|
||||
def isBDS44(msg, rev=False):
|
||||
"""Check if a message is likely to be BDS code 4,4
|
||||
Meteorological routine air report
|
||||
|
||||
WARNING: there is two different definition for BDS4,4. This part of the
|
||||
decoder is likely to be wrong...
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
# status bit 5, 15, 24, 36, 49
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
# --- current version ---
|
||||
result = result & checkbits(d, 5, 6, 23) \
|
||||
& checkbits(d, 35, 36, 46) & checkbits(d, 47, 48, 49) \
|
||||
& checkbits(d, 50, 51, 56)
|
||||
if not rev:
|
||||
# status bit 5, 35, 47, 50
|
||||
result = result & checkbits(d, 5, 6, 23) \
|
||||
& checkbits(d, 35, 36, 46) & checkbits(d, 47, 48, 49) \
|
||||
& checkbits(d, 50, 51, 56)
|
||||
# Bits 1-4 indicate source, values > 4 reserved and should not occur
|
||||
if util.bin2int(d[0:4]) > 4:
|
||||
result &= False
|
||||
else:
|
||||
# status bit 5, 15, 24, 36, 49
|
||||
result = result & checkbits(d, 5, 6, 14) \
|
||||
& checkbits(d, 15, 16, 23) & checkbits(d, 24, 25, 35) \
|
||||
& checkbits(d, 36, 37, 47) & checkbits(d, 49, 50, 56)
|
||||
# Bits 1-4 are reserved and should be zero
|
||||
if util.bin2int(d[0:4]) != 0:
|
||||
result &= False
|
||||
|
||||
# # --- revised future version ---
|
||||
# result = result & checkbits(d, 5, 6, 14) \
|
||||
# & checkbits(d, 15, 16, 23) & checkbits(d, 24, 25, 35) \
|
||||
# & checkbits(d, 36, 37, 47) & checkbits(d, 49, 50, 56)
|
||||
if not result:
|
||||
return False
|
||||
|
||||
if wind(msg) and wind(msg)[0] > 250:
|
||||
vw = wind44(msg, rev=rev)
|
||||
if vw is not None and vw[0] > 250:
|
||||
result &= False
|
||||
|
||||
# if temperature(msg):
|
||||
# if temperature(msg) > 60 or temperature(msg) < -80:
|
||||
# result &= False
|
||||
if temp44(msg):
|
||||
if temp44(msg) > 60 or temp44(msg) < -80:
|
||||
result &= False
|
||||
elif temp44(msg) == 0:
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def wind(msg):
|
||||
def wind44(msg, rev=False):
|
||||
"""reported wind speed and direction
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), direction (degree)
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
status = int(d[4])
|
||||
if not status:
|
||||
return None
|
||||
if not rev:
|
||||
status = int(d[4])
|
||||
if not status:
|
||||
return None
|
||||
|
||||
speed = util.bin2int(d[5:14]) # knots
|
||||
direction = util.bin2int(d[14:23]) * 180.0 / 256.0 # degree
|
||||
|
||||
else:
|
||||
spd_status = int(d[4])
|
||||
dir_status = int(d[14])
|
||||
|
||||
if (not spd_status) or (not dir_status):
|
||||
return None
|
||||
|
||||
speed = util.bin2int(d[5:14]) # knots
|
||||
direction = util.bin2int(d[15:23]) * 180.0 / 128.0 # degree
|
||||
|
||||
speed = util.bin2int(d[5:14]) # knots
|
||||
direction = util.bin2int(d[14:23]) * 180.0 / 256.0 # degree
|
||||
return round(speed, 0), round(direction, 1)
|
||||
|
||||
|
||||
def temperature(msg):
|
||||
def temp44(msg, rev=False):
|
||||
"""reported air temperature
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
float: tmeperature in Celsius degree
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
sign = int(d[23]) # 1 -> minus
|
||||
temp = util.bin2int(d[24:34]) * 0.25 # celsius
|
||||
temp = round(temp, 1)
|
||||
return -1 * temp if sign else temp
|
||||
if not rev:
|
||||
# if d[22] == '0':
|
||||
# return None
|
||||
|
||||
sign = int(d[23])
|
||||
value = util.bin2int(d[24:34])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
temp = value * 0.125 # celsius
|
||||
temp = round(temp, 1)
|
||||
else:
|
||||
# if d[23] == '0':
|
||||
# return None
|
||||
|
||||
sign = int(d[24])
|
||||
value = util.bin2int(d[25:35])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
temp = value * 0.125 # celsius
|
||||
temp = round(temp, 1)
|
||||
|
||||
return -1*temp if sign else temp
|
||||
|
||||
|
||||
def pressure(msg):
|
||||
def p44(msg, rev=False):
|
||||
"""reported average static pressure
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
int: static pressure in hPa
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
status = int(d[34])
|
||||
if not status:
|
||||
return None
|
||||
if not rev:
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
p = util.bin2int(d[35:46]) # hPa
|
||||
|
||||
else:
|
||||
if d[35] == '0':
|
||||
return None
|
||||
|
||||
p = util.bin2int(d[36:47]) # hPa
|
||||
|
||||
p = util.bin2int(d[35:46]) # hPa
|
||||
return p
|
||||
|
||||
|
||||
def humidity(msg):
|
||||
def hum44(msg, rev=False):
|
||||
"""reported humidity
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
float: percentage of humidity, [0 - 100] %
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
status = int(d[49])
|
||||
if not status:
|
||||
return None
|
||||
if not rev:
|
||||
if d[49] == '0':
|
||||
return None
|
||||
|
||||
hm = util.bin2int(d[50:56]) * 100.0 / 64 # %
|
||||
|
||||
else:
|
||||
if d[48] == '0':
|
||||
return None
|
||||
|
||||
hm = util.bin2int(d[49:56]) # %
|
||||
|
||||
hm = util.bin2int(d[50:56]) * 100.0 / 64 # %
|
||||
return round(hm, 1)
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# DF 20/21, BDS 5,0
|
||||
# BDS 5,0
|
||||
# Track and turn report
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS50(msg):
|
||||
"""Check if a message is likely to be BDS code 5,0
|
||||
(Track and turn report)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
@@ -337,6 +496,9 @@ def isBDS50(msg):
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 12, 24, 35, 46
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
@@ -346,26 +508,32 @@ def isBDS50(msg):
|
||||
& checkbits(d, 24, 25, 34) & checkbits(d, 35, 36, 45) \
|
||||
& checkbits(d, 46, 47, 56)
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
if d[2:11] == "000000000":
|
||||
result &= True
|
||||
else:
|
||||
if abs(roll(msg)) > 30:
|
||||
roll = abs(roll50(msg))
|
||||
if roll and roll > 60:
|
||||
result &= False
|
||||
|
||||
if gs(msg) > 500:
|
||||
gs = gs50(msg)
|
||||
if gs is not None and gs > 600:
|
||||
result &= False
|
||||
|
||||
if tas(msg) > 500:
|
||||
tas = tas50(msg)
|
||||
if tas is not None and tas > 500:
|
||||
result &= False
|
||||
|
||||
if abs(tas(msg) - gs(msg)) > 100:
|
||||
if (gs is not None) and (tas is not None) and (abs(tas - gs) > 200):
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def roll(msg):
|
||||
"""Aircraft roll angle
|
||||
def roll50(msg):
|
||||
"""Roll angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
@@ -375,14 +543,22 @@ def roll(msg):
|
||||
negative->left wing down, positive->right wing down
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> left wing down
|
||||
value = util.bin2int(d[2:11]) * 45 / 256.0 # degree
|
||||
angle = -1 * value if sign else value
|
||||
value = util.bin2int(d[2:11])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
angle = value * 45.0 / 256.0 # degree
|
||||
return round(angle, 1)
|
||||
|
||||
|
||||
def track(msg):
|
||||
"""True track angle
|
||||
def trk50(msg):
|
||||
"""True track angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
@@ -391,14 +567,27 @@ def track(msg):
|
||||
float: angle in degrees to true north (from 0 to 360)
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[11] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[12]) # 1 -> west
|
||||
value = util.bin2int(d[13:23]) * 90 / 512.0 # degree
|
||||
angle = 360 - value if sign else value
|
||||
return round(angle, 1)
|
||||
value = util.bin2int(d[13:23])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
trk = value * 90.0 / 512.0
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if trk < 0:
|
||||
trk = 360 + trk
|
||||
|
||||
return round(trk, 1)
|
||||
|
||||
|
||||
def gs(msg):
|
||||
"""Aircraft ground speed
|
||||
def gs50(msg):
|
||||
"""Ground speed, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
@@ -407,12 +596,16 @@ def gs(msg):
|
||||
int: ground speed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
spd = util.bin2int(d[24:34]) * 2 # kts
|
||||
return spd
|
||||
|
||||
|
||||
def rtrack(msg):
|
||||
"""Track angle rate
|
||||
def rtrk50(msg):
|
||||
"""Track angle rate, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
@@ -421,14 +614,24 @@ def rtrack(msg):
|
||||
float: angle rate in degrees/second
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
sign = int(d[35]) # 1 -> minus
|
||||
value = util.bin2int(d[36:45]) * 8 / 256.0 # degree / sec
|
||||
angle = -1 * value if sign else value
|
||||
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
if d[36:45] == "111111111":
|
||||
return None
|
||||
|
||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||
value = util.bin2int(d[36:45])
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
angle = value * 8.0 / 256.0 # degree / sec
|
||||
return round(angle, 3)
|
||||
|
||||
|
||||
def tas(msg):
|
||||
"""Aircraft true airspeed
|
||||
def tas50(msg):
|
||||
"""Aircraft true airspeed, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
@@ -437,12 +640,174 @@ def tas(msg):
|
||||
int: true airspeed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
spd = util.bin2int(d[46:56]) * 2 # kts
|
||||
return spd
|
||||
|
||||
if d[45] == '0':
|
||||
return None
|
||||
|
||||
tas = util.bin2int(d[46:56]) * 2 # kts
|
||||
return tas
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# DF 20/21, BDS 6,0
|
||||
# BDS 5,3
|
||||
# Air-referenced state vector
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS53(msg):
|
||||
"""Check if a message is likely to be BDS code 5,3
|
||||
(Air-referenced state vector)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 13, 24, 34, 47
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
result = result & checkbits(d, 1, 3, 12) & checkbits(d, 13, 14, 23) \
|
||||
& checkbits(d, 24, 25, 33) & checkbits(d, 34, 35, 46) \
|
||||
& checkbits(d, 47, 49, 56)
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
ias = ias53(msg)
|
||||
if ias is not None and ias > 500:
|
||||
result &= False
|
||||
|
||||
mach = mach53(msg)
|
||||
if mach is not None and mach > 1:
|
||||
result &= False
|
||||
|
||||
tas = tas53(msg)
|
||||
if tas is not None and tas > 500:
|
||||
result &= False
|
||||
|
||||
vr = vr53(msg)
|
||||
if vr is not None and abs(vr) > 8000:
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def hdg53(msg):
|
||||
"""Magnetic heading, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS53) string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees to true north (from 0 to 360)
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> west
|
||||
value = util.bin2int(d[2:12])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
hdg = value * 90.0 / 512.0 # degree
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if hdg < 0:
|
||||
hdg = 360 + hdg
|
||||
|
||||
return round(hdg, 1)
|
||||
|
||||
|
||||
def ias53(msg):
|
||||
"""Indicated airspeed, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
int: indicated arispeed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[12] == '0':
|
||||
return None
|
||||
|
||||
ias = util.bin2int(d[13:23]) # knots
|
||||
return ias
|
||||
|
||||
|
||||
def mach53(msg):
|
||||
"""MACH number, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
float: MACH number
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
mach = util.bin2int(d[24:33]) * 0.008
|
||||
return round(mach, 3)
|
||||
|
||||
|
||||
def tas53(msg):
|
||||
"""Aircraft true airspeed, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
float: true airspeed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[33] == '0':
|
||||
return None
|
||||
|
||||
tas = util.bin2int(d[34:46]) * 0.5 # kts
|
||||
return round(tas, 1)
|
||||
|
||||
def vr53(msg):
|
||||
"""Vertical rate
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[46] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[47]) # 1 -> negative value, two's complement
|
||||
value = util.bin2int(d[48:56])
|
||||
|
||||
if value == 0 or value == 255: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 256 if sign else value
|
||||
roc = value * 64 # feet/min
|
||||
|
||||
return roc
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 6,0
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS60(msg):
|
||||
@@ -454,6 +819,10 @@ def isBDS60(msg):
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 13, 24, 35, 46
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
@@ -463,22 +832,24 @@ def isBDS60(msg):
|
||||
& checkbits(d, 24, 25, 34) & checkbits(d, 35, 36, 45) \
|
||||
& checkbits(d, 46, 47, 56)
|
||||
|
||||
if not (1 < ias(msg) < 500):
|
||||
if not result:
|
||||
return False
|
||||
|
||||
ias = ias60(msg)
|
||||
if ias is not None and ias > 500:
|
||||
result &= False
|
||||
|
||||
if not (0.0 < mach(msg) < 1.0):
|
||||
mach = mach60(msg)
|
||||
if mach is not None and mach > 1:
|
||||
result &= False
|
||||
|
||||
if abs(baro_vr(msg)) > 5000:
|
||||
result &= False
|
||||
|
||||
if abs(ins_vr(msg)) > 5000:
|
||||
result &= False
|
||||
# leave out the check from vertical rates,
|
||||
# due to very noisy measurement
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def heading(msg):
|
||||
def hdg60(msg):
|
||||
"""Megnetic heading of aircraft
|
||||
|
||||
Args:
|
||||
@@ -488,13 +859,26 @@ def heading(msg):
|
||||
float: heading in degrees to megnetic north (from 0 to 360)
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> west
|
||||
value = util.bin2int(d[2:12]) * 90 / 512.0 # degree
|
||||
hdg = 360 - value if sign else value
|
||||
value = util.bin2int(d[2:12])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
hdg = value * 90 / 512.0 # degree
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if hdg < 0:
|
||||
hdg = 360 + hdg
|
||||
|
||||
return round(hdg, 1)
|
||||
|
||||
|
||||
def ias(msg):
|
||||
def ias60(msg):
|
||||
"""Indicated airspeed
|
||||
|
||||
Args:
|
||||
@@ -504,11 +888,15 @@ def ias(msg):
|
||||
int: indicated airspeed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[12] == '0':
|
||||
return None
|
||||
|
||||
ias = util.bin2int(d[13:23]) # kts
|
||||
return ias
|
||||
|
||||
|
||||
def mach(msg):
|
||||
def mach60(msg):
|
||||
"""Aircraft MACH number
|
||||
|
||||
Args:
|
||||
@@ -518,12 +906,16 @@ def mach(msg):
|
||||
float: MACH number
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
mach = util.bin2int(d[24:34]) * 2.048 / 512.0
|
||||
return round(mach, 3)
|
||||
|
||||
|
||||
def baro_vr(msg):
|
||||
"""Vertical rate from barometric measurement
|
||||
def vr60baro(msg):
|
||||
"""Vertical rate from barometric measurement, this value may be very noisy.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
@@ -532,13 +924,23 @@ def baro_vr(msg):
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
sign = d[35] # 1 -> minus
|
||||
value = util.bin2int(d[36:45]) * 32 # feet/min
|
||||
roc = -1*value if sign else value
|
||||
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||
value = util.bin2int(d[36:45])
|
||||
|
||||
if value == 0 or value == 511: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 512 if sign else value
|
||||
|
||||
roc = value * 32 # feet/min
|
||||
return roc
|
||||
|
||||
|
||||
def ins_vr(msg):
|
||||
def vr60ins(msg):
|
||||
"""Vertical rate messured by onbard equiments (IRS, AHRS)
|
||||
|
||||
Args:
|
||||
@@ -548,9 +950,19 @@ def ins_vr(msg):
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
sign = d[46] # 1 -> minus
|
||||
value = util.bin2int(d[47:56]) * 32 # feet/min
|
||||
roc = -1*value if sign else value
|
||||
|
||||
if d[45] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[46]) # 1 -> negative value, two's complement
|
||||
value = util.bin2int(d[47:56])
|
||||
|
||||
if value == 0 or value == 511: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 512 if sign else value
|
||||
|
||||
roc = value * 32 # feet/min
|
||||
return roc
|
||||
|
||||
|
||||
@@ -563,16 +975,25 @@ def BDS(msg):
|
||||
Returns:
|
||||
String or None: Version: "BDS20", "BDS40", "BDS50", or "BDS60". Or None, if nothing matched
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return None
|
||||
|
||||
is17 = isBDS17(msg)
|
||||
is20 = isBDS20(msg)
|
||||
is44 = isBDS44(msg)
|
||||
is40 = isBDS40(msg)
|
||||
is44 = isBDS44(msg)
|
||||
is44rev = isBDS44(msg, rev=True)
|
||||
is50 = isBDS50(msg)
|
||||
is53 = isBDS53(msg)
|
||||
is60 = isBDS60(msg)
|
||||
|
||||
BDS = ["BDS20", "BDS40", "BDS44", "BDS50", "BDS60"]
|
||||
isBDS = [is20, is40, is44, is50, is60]
|
||||
BDS = ["BDS17", "BDS20", "BDS40", "BDS44", "BDS44REV", "BDS50", "BDS53", "BDS60"]
|
||||
isBDS = [is17, is20, is40, is44, is44rev, is50, is53, is60]
|
||||
|
||||
if sum(isBDS) == 1:
|
||||
if sum(isBDS) == 0:
|
||||
return None
|
||||
elif sum(isBDS) == 1:
|
||||
return BDS[isBDS.index(True)]
|
||||
else:
|
||||
return None
|
||||
return [bds for (bds, i) in zip(BDS, isBDS) if i]
|
||||
|
||||
37
pyModeS/els.py
Normal file
37
pyModeS/els.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from . import util, modes_common
|
||||
|
||||
def icao(msg):
|
||||
return modes_common.icao(msg)
|
||||
|
||||
|
||||
def df4alt(msg):
|
||||
"""Computes the altitude from DF4 message, bit 20-32
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in ft
|
||||
"""
|
||||
|
||||
if util.df(msg) != 4:
|
||||
raise RuntimeError("Message must be Downlink Format 4.")
|
||||
|
||||
return modes_common.altcode(msg)
|
||||
|
||||
|
||||
def df5id(msg):
|
||||
"""Computes identity (squawk code) from DF5 message, bit 20-32
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
string: squawk code
|
||||
"""
|
||||
|
||||
if util.df(msg) != 5:
|
||||
raise RuntimeError("Message must be Downlink Format 5.")
|
||||
|
||||
return modes_common.idcode(msg)
|
||||
131
pyModeS/modes_common.py
Normal file
131
pyModeS/modes_common.py
Normal file
@@ -0,0 +1,131 @@
|
||||
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 = util.gray2int(gc500)
|
||||
|
||||
# in 100-ft step must be converted first
|
||||
gc100 = codestr[8:]
|
||||
n100 = util.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
|
||||
@@ -85,3 +85,13 @@ def floor(x):
|
||||
eg.: floor(3.6) = 3, while floor(-3.6) = -4
|
||||
"""
|
||||
return int(math.floor(x))
|
||||
|
||||
|
||||
def gray2int(graystr):
|
||||
"""Convert greycode to binary (DF4, 20 altitude coding)"""
|
||||
num = bin2int(graystr)
|
||||
num ^= (num >> 8)
|
||||
num ^= (num >> 4)
|
||||
num ^= (num >> 2)
|
||||
num ^= (num >> 1)
|
||||
return num
|
||||
|
||||
9
setup.py
9
setup.py
@@ -3,6 +3,13 @@
|
||||
See:
|
||||
https://packaging.python.org/en/latest/distributing.html
|
||||
https://github.com/pypa/sampleproject
|
||||
|
||||
Steps for deploying a new verison:
|
||||
1. Increase the version number
|
||||
2. remove the old deployment under [dist] folder
|
||||
3. run: python setup.py sdist
|
||||
run: python setup.py bdist_wheel --universal
|
||||
4. twine upload dist/*
|
||||
"""
|
||||
|
||||
# Always prefer setuptools over distutils
|
||||
@@ -23,7 +30,7 @@ setup(
|
||||
# Versions should comply with PEP440. For a discussion on single-sourcing
|
||||
# the version across setup.py and the project code, see
|
||||
# https://packaging.python.org/en/latest/single_source_version.html
|
||||
version='1.0.8',
|
||||
version='1.2.2',
|
||||
|
||||
description='Python Mode-S Decoder',
|
||||
long_description=long_description,
|
||||
|
||||
5000
tests/data/sample_data_ehs_df20.csv
Normal file
5000
tests/data/sample_data_ehs_df20.csv
Normal file
File diff suppressed because it is too large
Load Diff
5000
tests/data/sample_data_ehs_df21.csv
Normal file
5000
tests/data/sample_data_ehs_df21.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,79 +0,0 @@
|
||||
# If you get import error run with ipython
|
||||
from pyModeS import adsb
|
||||
from pyModeS import ehs
|
||||
from pyModeS import util
|
||||
|
||||
|
||||
# === Decode sample data file ===
|
||||
|
||||
def adsb_decode_all(n=None):
|
||||
print("===== Decode all ADS-B sample data=====")
|
||||
import csv
|
||||
f = open('tests/sample_data_adsb.csv', 'rt')
|
||||
|
||||
msg0 = None
|
||||
msg1 = None
|
||||
|
||||
for i, r in enumerate(csv.reader(f)):
|
||||
if n and i > n:
|
||||
break
|
||||
|
||||
ts = r[0]
|
||||
m = r[1]
|
||||
icao = adsb.icao(m)
|
||||
tc = adsb.typecode(m)
|
||||
if 1 <= tc <= 4:
|
||||
print(ts, m, icao, tc, adsb.category(m), adsb.callsign(m))
|
||||
if tc == 19:
|
||||
print(ts, m, icao, tc, adsb.velocity(m))
|
||||
if 5 <= tc <= 18:
|
||||
if adsb.oe_flag(m):
|
||||
msg1 = m
|
||||
t1 = ts
|
||||
else:
|
||||
msg0 = m
|
||||
t0 = ts
|
||||
|
||||
if msg0 and msg1:
|
||||
pos = adsb.position(msg0, msg1, t0, t1)
|
||||
alt = adsb.altitude(m)
|
||||
print(ts, m, icao, tc, pos, alt)
|
||||
|
||||
|
||||
def ehs_decode_all(n=None):
|
||||
print("===== Decode all Mode-S EHS sample data=====")
|
||||
import csv
|
||||
f = open('tests/sample_data_ehs.csv', 'rt')
|
||||
for i, r in enumerate(csv.reader(f)):
|
||||
if n and i > n:
|
||||
break
|
||||
|
||||
ts = r[1]
|
||||
m = r[2]
|
||||
icao = ehs.icao(m)
|
||||
vBDS = ehs.BDS(m)
|
||||
|
||||
if vBDS:
|
||||
if vBDS == "BDS20":
|
||||
print(ts, m, icao, vBDS, ehs.callsign(m))
|
||||
|
||||
if vBDS == "BDS40":
|
||||
print(ts, m, icao, vBDS, ehs.alt_mcp(m),
|
||||
ehs.alt_fms(m), ehs.pbaro(m))
|
||||
|
||||
if vBDS == "BDS44":
|
||||
print(ts, m, icao, vBDS, ehs.wind(m))
|
||||
|
||||
if vBDS == "BDS50":
|
||||
print(ts, m, icao, vBDS, ehs.roll(m), ehs.track(m),
|
||||
ehs.gs(m), ehs.rtrack(m), ehs.tas(m))
|
||||
|
||||
if vBDS == "BDS60":
|
||||
print(ts, m, icao, vBDS, ehs.heading(m), ehs.ias(m),
|
||||
ehs.mach(m), ehs.baro_vr(m), ehs.ins_vr(m))
|
||||
else:
|
||||
print(ts, m, icao, 'UNKNOWN')
|
||||
|
||||
if __name__ == '__main__':
|
||||
adsb_decode_all(100)
|
||||
ehs_decode_all(100)
|
||||
41
tests/sample_run_adsb.py
Normal file
41
tests/sample_run_adsb.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from __future__ import print_function
|
||||
from pyModeS import adsb, ehs, util
|
||||
|
||||
|
||||
# === Decode sample data file ===
|
||||
|
||||
def adsb_decode_all(n=None):
|
||||
print("===== Decode ADS-B sample data=====")
|
||||
import csv
|
||||
f = open('tests/data/sample_data_adsb.csv', 'rt')
|
||||
|
||||
msg0 = None
|
||||
msg1 = None
|
||||
|
||||
for i, r in enumerate(csv.reader(f)):
|
||||
if n and i > n:
|
||||
break
|
||||
|
||||
ts = r[0]
|
||||
m = r[1]
|
||||
icao = adsb.icao(m)
|
||||
tc = adsb.typecode(m)
|
||||
if 1 <= tc <= 4:
|
||||
print(ts, m, icao, tc, adsb.category(m), adsb.callsign(m))
|
||||
if tc == 19:
|
||||
print(ts, m, icao, tc, adsb.velocity(m))
|
||||
if 5 <= tc <= 18:
|
||||
if adsb.oe_flag(m):
|
||||
msg1 = m
|
||||
t1 = ts
|
||||
else:
|
||||
msg0 = m
|
||||
t0 = ts
|
||||
|
||||
if msg0 and msg1:
|
||||
pos = adsb.position(msg0, msg1, t0, t1)
|
||||
alt = adsb.altitude(m)
|
||||
print(ts, m, icao, tc, pos, alt)
|
||||
|
||||
if __name__ == '__main__':
|
||||
adsb_decode_all(n=100)
|
||||
75
tests/sample_run_ehs.py
Normal file
75
tests/sample_run_ehs.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from __future__ import print_function
|
||||
from pyModeS import adsb, ehs, util
|
||||
|
||||
|
||||
# === Decode sample data file ===
|
||||
|
||||
def bds_info(BDS, m):
|
||||
if BDS == "BDS17":
|
||||
info = ([i[-2:] for i in ehs.cap17(m)])
|
||||
|
||||
elif BDS == "BDS20":
|
||||
info = ehs.callsign(m)
|
||||
|
||||
elif BDS == "BDS40":
|
||||
info = (ehs.alt40mcp(m), ehs.alt40fms(m), ehs.p40baro(m))
|
||||
|
||||
elif BDS == "BDS44":
|
||||
info = (ehs.wind44(m), ehs.temp44(m), ehs.p44(m), ehs.hum44(m))
|
||||
|
||||
elif BDS == "BDS44REV":
|
||||
info = (ehs.wind44(m, rev=True), ehs.temp44(m, rev=True), ehs.p44(m, rev=True), ehs.hum44(m, rev=True))
|
||||
|
||||
elif BDS == "BDS50":
|
||||
info = (ehs.roll50(m), ehs.trk50(m), ehs.gs50(m), ehs.rtrk50(m), ehs.tas50(m))
|
||||
|
||||
elif BDS == "BDS53":
|
||||
info = (ehs.hdg53(m), ehs.ias53(m), ehs.mach53(m), ehs.tas53(m), ehs.vr53(m))
|
||||
|
||||
elif BDS == "BDS60":
|
||||
info = (ehs.hdg60(m), ehs.ias60(m), ehs.mach60(m), ehs.vr60baro(m), ehs.vr60ins(m))
|
||||
|
||||
else:
|
||||
info = None
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def ehs_decode_all(df, n=None):
|
||||
import csv
|
||||
|
||||
print("===== Decode EHS sample data (DF=%s)=====" % df)
|
||||
|
||||
f = open('tests/data/sample_data_ehs_df%s.csv' % df, 'rt')
|
||||
|
||||
|
||||
for i, r in enumerate(csv.reader(f)):
|
||||
if n and i > n:
|
||||
break
|
||||
|
||||
ts = r[0]
|
||||
m = r[2]
|
||||
|
||||
df = util.df(m)
|
||||
icao = ehs.icao(m)
|
||||
BDS = ehs.BDS(m)
|
||||
code = ehs.df20alt(m) if df==20 else ehs.df21id(m)
|
||||
|
||||
if not BDS:
|
||||
print(ts, m, icao, df, '%5s'%code, 'UNKNOWN')
|
||||
continue
|
||||
|
||||
if isinstance(BDS, list):
|
||||
print(ts, m, icao, df, '%5s'%code, end=' ')
|
||||
for i, bds in enumerate(BDS):
|
||||
if i == 0:
|
||||
print(bds, *bds_info(bds, m))
|
||||
else:
|
||||
print(' '*55, bds, *bds_info(bds, m))
|
||||
|
||||
else:
|
||||
print(ts, m, icao, df, '%5s'%code, BDS, *bds_info(BDS, m))
|
||||
|
||||
if __name__ == '__main__':
|
||||
ehs_decode_all(df=20, n=100)
|
||||
ehs_decode_all(df=21, n=100)
|
||||
@@ -1,6 +1,5 @@
|
||||
from pyModeS import adsb
|
||||
|
||||
|
||||
# === TEST ADS-B package ===
|
||||
|
||||
def test_adsb_icao():
|
||||
@@ -58,8 +57,11 @@ def test_adsb_alt():
|
||||
def test_adsb_velocity():
|
||||
vgs = adsb.velocity("8D485020994409940838175B284F")
|
||||
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
|
||||
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
|
||||
assert vgs == (159, 182.9, -832, 'GS')
|
||||
assert vas == (376, 244.0, -2304, 'AS')
|
||||
assert vgs_surface == (19.0, 42.2, 0 , 'GS')
|
||||
assert adsb.altitude_diff('8D485020994409940838175B284F') == 550
|
||||
|
||||
|
||||
def test_nic():
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from pyModeS import ehs
|
||||
|
||||
from pyModeS import modes_common
|
||||
|
||||
def test_ehs_icao():
|
||||
assert ehs.icao("A0001839CA3800315800007448D9") == '400940'
|
||||
@@ -7,14 +7,16 @@ def test_ehs_icao():
|
||||
assert ehs.icao("A000029CFFBAA11E2004727281F1") == '4243D0'
|
||||
|
||||
|
||||
def test_df20alt():
|
||||
assert ehs.df20alt("A02014B400000000000000F9D514") == 32300
|
||||
|
||||
|
||||
def test_ehs_BDS():
|
||||
assert ehs.BDS("A0001838201584F23468207CDFA5") == 'BDS20'
|
||||
assert ehs.BDS("A0001839CA3800315800007448D9") == 'BDS40'
|
||||
# assert ehs.BDS("A000031DBAA9DD18622C441330E9") == 'BDS44'
|
||||
assert ehs.BDS("A000139381951536E024D4CCF6B5") == 'BDS50'
|
||||
assert ehs.BDS("A000029CFFBAA11E2004727281F1") == 'BDS60'
|
||||
assert ehs.BDS("A0281838CAE9E12FA03FFF2DDDE5") == 'BDS44'
|
||||
assert ehs.BDS("A00017B0C8480030A4000024512F") is None
|
||||
|
||||
assert ehs.BDS("A00004128F39F91A7E27C46ADC21") == 'BDS60'
|
||||
|
||||
def test_ehs_BDS20_callsign():
|
||||
assert ehs.callsign("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
|
||||
@@ -22,22 +24,39 @@ def test_ehs_BDS20_callsign():
|
||||
|
||||
|
||||
def test_ehs_BDS40_functions():
|
||||
assert ehs.alt_mcp("A000029C85E42F313000007047D3") == 3008
|
||||
assert ehs.alt_fms("A000029C85E42F313000007047D3") == 3008
|
||||
assert ehs.pbaro("A000029C85E42F313000007047D3") == 1020.0
|
||||
|
||||
assert ehs.alt40mcp("A000029C85E42F313000007047D3") == 3008
|
||||
assert ehs.alt40fms("A000029C85E42F313000007047D3") == 3008
|
||||
assert ehs.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
||||
|
||||
def test_ehs_BDS50_functions():
|
||||
assert ehs.roll("A000139381951536E024D4CCF6B5") == 2.1
|
||||
assert ehs.track("A000139381951536E024D4CCF6B5") == 114.3
|
||||
assert ehs.gs("A000139381951536E024D4CCF6B5") == 438
|
||||
assert ehs.rtrack("A000139381951536E024D4CCF6B5") == 0.125
|
||||
assert ehs.tas("A000139381951536E024D4CCF6B5") == 424
|
||||
|
||||
assert ehs.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||
assert ehs.trk50("A000139381951536E024D4CCF6B5") == 114.3
|
||||
assert ehs.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||
assert ehs.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||
assert ehs.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||
# signed values
|
||||
assert ehs.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4
|
||||
|
||||
def test_ehs_BDS60_functions():
|
||||
assert ehs.heading("A000029CFFBAA11E2004727281F1") == 180.9
|
||||
assert ehs.ias("A000029CFFBAA11E2004727281F1") == 336
|
||||
assert ehs.mach("A000029CFFBAA11E2004727281F1") == 0.48
|
||||
assert ehs.baro_vr("A000029CFFBAA11E2004727281F1") == 0
|
||||
assert ehs.ins_vr("A000029CFFBAA11E2004727281F1") == -3648
|
||||
assert ehs.hdg60("A00004128F39F91A7E27C46ADC21") == 42.7
|
||||
assert ehs.ias60("A00004128F39F91A7E27C46ADC21") == 252
|
||||
assert ehs.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
||||
assert ehs.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
assert ehs.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
|
||||
def test_graycode_to_altitude():
|
||||
assert modes_common.gray2alt('00000000010') == -1000
|
||||
assert modes_common.gray2alt('00000001010') == -500
|
||||
assert modes_common.gray2alt('00000011011') == -100
|
||||
assert modes_common.gray2alt('00000011010') == 0
|
||||
assert modes_common.gray2alt('00000011110') == 100
|
||||
assert modes_common.gray2alt('00000010011') == 600
|
||||
assert modes_common.gray2alt('00000110010') == 1000
|
||||
assert modes_common.gray2alt('00001001001') == 5800
|
||||
assert modes_common.gray2alt('00011100100') == 10300
|
||||
assert modes_common.gray2alt('01100011010') == 32000
|
||||
assert modes_common.gray2alt('01110000100') == 46300
|
||||
assert modes_common.gray2alt('01010101100') == 50200
|
||||
assert modes_common.gray2alt('11011110100') == 73200
|
||||
assert modes_common.gray2alt('10000000011') == 126600
|
||||
assert modes_common.gray2alt('10000000001') == 126700
|
||||
|
||||
@@ -12,4 +12,4 @@ def test_crc_decode():
|
||||
|
||||
def test_crc_encode():
|
||||
parity = util.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||
assert util.hex2bin("AA4BDA") == parity
|
||||
assert util.hex2bin("AA4BDA") == parity
|
||||
|
||||
Reference in New Issue
Block a user