Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
555b2eea40 | ||
|
|
dea7cde317 | ||
|
|
e16d34bc06 | ||
|
|
9bb87b00be | ||
|
|
3dae0438bf | ||
|
|
02c5117de5 | ||
|
|
3f24f78d3a | ||
|
|
cf3828d2a0 | ||
|
|
f70d1f2f1f | ||
|
|
dfeb65fbd7 | ||
|
|
13b283666a | ||
|
|
6144b88188 | ||
|
|
b503beb3fd | ||
|
|
c804cd876c | ||
|
|
d48caed7e6 | ||
|
|
eb675d5ca3 | ||
|
|
b04a1bd49c | ||
|
|
fb24d4f25c | ||
|
|
9ad535dc93 | ||
|
|
9a89766766 | ||
|
|
26173b4038 | ||
|
|
3619d52760 | ||
|
|
e2ece806c2 | ||
|
|
a683e40c41 | ||
|
|
0c1a3b06e1 | ||
|
|
f960cd71bc | ||
|
|
bd54ac1d10 | ||
|
|
2c1db13122 | ||
|
|
695fc34988 | ||
|
|
0eb333ba8c | ||
|
|
d058e9f8b3 | ||
|
|
3d99deb049 | ||
|
|
03f81d120b | ||
|
|
b9b95320d8 | ||
|
|
0ea2cf7ade | ||
|
|
458b02028d | ||
|
|
b062bdf998 | ||
|
|
cfcd21b692 | ||
|
|
86f302f05e | ||
|
|
28a6e53d49 | ||
|
|
81d7cef6e8 | ||
|
|
4906a49e9c | ||
|
|
7cb75ea8ca | ||
|
|
785584aff5 | ||
|
|
fdc34497c0 | ||
|
|
4a3c9438f7 | ||
|
|
b723374337 | ||
|
|
8f4dff5b30 |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -5,6 +5,9 @@ __pycache__/
|
|||||||
*.py[cod]
|
*.py[cod]
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
|
||||||
|
#cython
|
||||||
|
.c
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
@@ -58,3 +61,9 @@ target/
|
|||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
|||||||
19
Makefile
Normal file
19
Makefile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
install:
|
||||||
|
pip install . --upgrade
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
pip uninstall pyModeS -y
|
||||||
|
|
||||||
|
ext:
|
||||||
|
python setup.py build_ext --inplace
|
||||||
|
|
||||||
|
test:
|
||||||
|
python -m pytest
|
||||||
|
|
||||||
|
clean:
|
||||||
|
find pyModeS/decoder -type f -name '*.c' -delete
|
||||||
|
find pyModeS/decoder -type f -name '*.so' -delete
|
||||||
|
find . | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf
|
||||||
|
rm -rf *.egg-info
|
||||||
|
rm -rf .pytest_cache
|
||||||
|
rm -rf build/*
|
||||||
119
README.rst
119
README.rst
@@ -1,9 +1,7 @@
|
|||||||
The Python ADS-B/Mode-S Decoder
|
The Python ADS-B/Mode-S Decoder
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
If you find this project useful for your research, please cite our work (bibtex format):
|
If you find this project useful for your research, please considering cite this tool as::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
@article{sun2019pymodes,
|
@article{sun2019pymodes,
|
||||||
author={J. {Sun} and H. {V\^u} and J. {Ellerbroek} and J. M. {Hoekstra}},
|
author={J. {Sun} and H. {V\^u} and J. {Ellerbroek} and J. M. {Hoekstra}},
|
||||||
@@ -18,11 +16,11 @@ If you find this project useful for your research, please cite our work (bibtex
|
|||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
---------------------
|
---------------------
|
||||||
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message.
|
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message. It can be imported to your python project or be used as a standalone tool to view and save live traffic data.
|
||||||
Message with following Downlink Formats (DF) are supported:
|
|
||||||
|
|
||||||
|
Messages with following Downlink Formats (DF) are supported:
|
||||||
|
|
||||||
**DF17 / DF18: Automatic Dependent Surveillance - Broadcast (ADS-B)**
|
**DF17 / DF18: Automatic Dependent Surveillance-Broadcast (ADS-B)**
|
||||||
|
|
||||||
- TC=1-4 / BDS 0,8: Aircraft identification and category
|
- TC=1-4 / BDS 0,8: Aircraft identification and category
|
||||||
- TC=5-8 / BDS 0,6: Surface position
|
- TC=5-8 / BDS 0,6: Surface position
|
||||||
@@ -53,13 +51,13 @@ Message with following Downlink Formats (DF) are supported:
|
|||||||
|
|
||||||
Resources
|
Resources
|
||||||
-----------
|
-----------
|
||||||
Checkout and contribute to this open-source project at:
|
Check out and contribute to this open-source project at:
|
||||||
https://github.com/junzis/pyModeS
|
https://github.com/junzis/pyModeS
|
||||||
|
|
||||||
Detailed manual on Mode-S decoding is published at:
|
Detailed manual on Mode-S decoding is published at:
|
||||||
https://mode-s.org/decode.
|
https://mode-s.org/decode.
|
||||||
|
|
||||||
API documentation of pyModeS is at:
|
The API documentation of pyModeS is at:
|
||||||
http://pymodes.readthedocs.io
|
http://pymodes.readthedocs.io
|
||||||
|
|
||||||
|
|
||||||
@@ -67,44 +65,55 @@ http://pymodes.readthedocs.io
|
|||||||
Install
|
Install
|
||||||
-------
|
-------
|
||||||
|
|
||||||
To install latest version from the GitHub:
|
Installation examples::
|
||||||
|
|
||||||
::
|
# stable version
|
||||||
|
pip install pyModeS
|
||||||
|
|
||||||
|
# development version
|
||||||
pip install git+https://github.com/junzis/pyModeS
|
pip install git+https://github.com/junzis/pyModeS
|
||||||
|
|
||||||
|
|
||||||
To install the stable version (2.0) from pip:
|
Dependencies ``numpy``, ``pyzmq`` and ``pyrtlsdr`` are installed automatically during previous installations processes.
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
pip install pyModeS
|
|
||||||
|
|
||||||
|
|
||||||
|
View live traffic (modeslive)
|
||||||
Live view traffic (modeslive)
|
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
Supports **Mode-S Beast** and **AVR** raw stream
|
|
||||||
|
|
||||||
::
|
General usage::
|
||||||
|
|
||||||
modeslive --server [server_address] --port [tcp_port] --rawtype [beast,avr,skysense] --latlon [lat] [lon] --dumpto [folder]
|
$ modeslive [-h] --source SOURCE [--connect SERVER PORT DATAYPE]
|
||||||
|
[--latlon LAT LON] [--show-uncertainty] [--dumpto DUMPTO]
|
||||||
|
|
||||||
Arguments:
|
arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--server SERVER server address or IP
|
--source SOURCE Choose data source, "rtlsdr" or "net"
|
||||||
--port PORT raw data port
|
--connect SERVER PORT DATATYPE
|
||||||
--rawtype RAWTYPE beast, avr or skysense
|
Define server, port and data type. Supported data
|
||||||
--latlon LAT LON receiver position
|
types are: ['raw', 'beast', 'skysense']
|
||||||
--show-uncertainty display uncertaint values, default off
|
--latlon LAT LON Receiver latitude and longitude, needed for the surface
|
||||||
--dumpto folder to dump decoded output
|
position, default none
|
||||||
|
--show-uncertainty Display uncertainty values, default off
|
||||||
|
--dumpto DUMPTO Folder to dump decoded output, default none
|
||||||
|
|
||||||
|
|
||||||
If you have a RTL-SDR receiver or Mode-S Beast, use modesmixer2 (http://xdeco.org/?page_id=48) to create raw beast TCP stream:
|
Live with RTL-SDR
|
||||||
|
*******************
|
||||||
|
|
||||||
|
If you have an RTL-SDR receiver plugged to the computer, you can connect it with ``rtlsdr`` source switch, shown as follows::
|
||||||
|
|
||||||
|
$ modeslive --source rtlsdr
|
||||||
|
|
||||||
|
|
||||||
|
Live with network data
|
||||||
|
***************************
|
||||||
|
|
||||||
|
If you want to connect to a TCP server that broadcast raw data. use can use ``net`` source switch, for example::
|
||||||
|
|
||||||
|
$ modeslive --source net --connect localhost 30002 raw
|
||||||
|
$ modeslive --source net --connect 127.0.0.1 30005 beast
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
$ modesmixer2 --inSeriel port[:speed[:flow_control]] --outServer beast:[tcp_port]
|
|
||||||
|
|
||||||
Example screenshot:
|
Example screenshot:
|
||||||
|
|
||||||
@@ -150,6 +159,7 @@ Core functions for ADS-B decoding
|
|||||||
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)
|
||||||
|
pms.adsb.surface_velocity(msg)
|
||||||
|
|
||||||
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
||||||
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||||
@@ -160,15 +170,10 @@ Core functions for ADS-B decoding
|
|||||||
# Typecode: 19
|
# Typecode: 19
|
||||||
pms.adsb.velocity(msg) # Handles both surface & airborne messages
|
pms.adsb.velocity(msg) # Handles both surface & airborne messages
|
||||||
pms.adsb.speed_heading(msg) # Handles both surface & airborne messages
|
pms.adsb.speed_heading(msg) # Handles both surface & airborne messages
|
||||||
pms.adsb.surface_velocity(msg)
|
|
||||||
pms.adsb.airborne_velocity(msg)
|
pms.adsb.airborne_velocity(msg)
|
||||||
|
|
||||||
|
|
||||||
Note: When you have a fix position of the aircraft, it is convenient to
|
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 within 180NM (airborne) or 45NM (surface) of the true position.
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
Decode altitude replies in DF4 / DF20
|
Decode altitude replies in DF4 / DF20
|
||||||
@@ -275,50 +280,60 @@ Meteorological hazard air report (MHR) [Experimental]
|
|||||||
|
|
||||||
Customize the streaming module
|
Customize the streaming module
|
||||||
******************************
|
******************************
|
||||||
The TCP client module from pyModeS can be re-used to stream and process Mode-S
|
The TCP client module from pyModeS can be re-used to stream and process Mode-S data as you like. You need to re-implement the ``handle_messages()`` function from the ``TcpClient`` class to write your own logic to handle the messages.
|
||||||
data as your like. You need to re-implement the ``handle_messages()`` function from
|
|
||||||
the ``BaseClient`` class to write your own logic to handle the messages.
|
|
||||||
|
|
||||||
Here is an example:
|
Here is an example:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
from pyModeS.extra.tcpclient import BaseClient
|
import pyModeS as pms
|
||||||
|
from pyModeS.extra.tcpclient import TcpClient
|
||||||
|
|
||||||
# define your custom class by extending the BaseClient
|
# define your custom class by extending the TcpClient
|
||||||
# - implement your handle_messages() methods
|
# - implement your handle_messages() methods
|
||||||
class ADSBClient(BaseClient):
|
class ADSBClient(TcpClient):
|
||||||
def __init__(self, host, port, rawtype):
|
def __init__(self, host, port, rawtype):
|
||||||
super(ModesClient, self).__init__(host, port, rawtype)
|
super(ADSBClient, self).__init__(host, port, rawtype)
|
||||||
|
|
||||||
def handle_messages(self, messages):
|
def handle_messages(self, messages):
|
||||||
for msg, ts in messages:
|
for msg, ts in messages:
|
||||||
if len(msg) < 28: # wrong data length
|
if len(msg) != 28: # wrong data length
|
||||||
continue
|
continue
|
||||||
|
|
||||||
df = pms.df(msg)
|
df = pms.df(msg)
|
||||||
|
|
||||||
if df != 17: # not ADSB
|
if df != 17: # not ADSB
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if '1' in pms.crc(msg): # CRC fail
|
if pms.crc(msg) !=0: # CRC fail
|
||||||
continue
|
continue
|
||||||
|
|
||||||
icao = pms.adsb.icao(msg)
|
icao = pms.adsb.icao(msg)
|
||||||
tc = pms.adsb.typecode(msg)
|
tc = pms.adsb.typecode(msg)
|
||||||
|
|
||||||
# TODO: write you magic code here
|
# TODO: write you magic code here
|
||||||
print ts, icao, tc, msg
|
print(ts, icao, tc, msg)
|
||||||
|
|
||||||
# run new client, change the host, port, and rawtype if needed
|
# run new client, change the host, port, and rawtype if needed
|
||||||
client = ADSBClient(host='127.0.0.1', port=30334, rawtype='beast')
|
client = ADSBClient(host='127.0.0.1', port=30005, rawtype='beast')
|
||||||
client.run()
|
client.run()
|
||||||
|
|
||||||
|
|
||||||
Unit test
|
Unit test
|
||||||
---------
|
---------
|
||||||
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
To perform unit tests, ``pytest`` must be install first.
|
||||||
|
|
||||||
.. code:: bash
|
Build Cython extensions
|
||||||
|
::
|
||||||
|
|
||||||
$ tox
|
$ make ext
|
||||||
|
|
||||||
|
Run unit tests
|
||||||
|
::
|
||||||
|
|
||||||
|
$ make test
|
||||||
|
|
||||||
|
Clean build files
|
||||||
|
::
|
||||||
|
|
||||||
|
$ make clean
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
|
|
||||||
from .decoder.common import *
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .decoder import c_common as common
|
||||||
|
from .decoder.c_common import *
|
||||||
|
except:
|
||||||
|
from .decoder import common
|
||||||
|
from .decoder.common import *
|
||||||
|
|
||||||
|
from .decoder import tell
|
||||||
from .decoder import adsb
|
from .decoder import adsb
|
||||||
from .decoder import commb
|
from .decoder import commb
|
||||||
from .decoder import common
|
|
||||||
from .decoder import bds
|
from .decoder import bds
|
||||||
from .extra import aero
|
from .extra import aero
|
||||||
from .extra import tcpclient
|
from .extra import tcpclient
|
||||||
|
|
||||||
# from .decoder import els # depricated
|
warnings.simplefilter("once", DeprecationWarning)
|
||||||
# from .decoder import ehs # depricated
|
|
||||||
|
|
||||||
import os
|
|
||||||
dirpath = os.path.dirname(os.path.realpath(__file__))
|
dirpath = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|||||||
@@ -1,3 +1,145 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
|
|
||||||
from pyModeS.decoder import *
|
from pyModeS.decoder import adsb, commb, common, bds
|
||||||
|
|
||||||
|
|
||||||
|
def tell(msg):
|
||||||
|
def _print(label, value, unit=None):
|
||||||
|
print("%20s: " % label, end="")
|
||||||
|
print("%s " % value, end="")
|
||||||
|
if unit:
|
||||||
|
print(unit)
|
||||||
|
else:
|
||||||
|
print()
|
||||||
|
|
||||||
|
df = common.df(msg)
|
||||||
|
icao = common.icao(msg)
|
||||||
|
|
||||||
|
_print("Message", msg)
|
||||||
|
_print("ICAO address", icao)
|
||||||
|
_print("Downlink Format", df)
|
||||||
|
|
||||||
|
if df == 17:
|
||||||
|
_print("Protocal", "Mode-S Extended Squitter (ADS-B)")
|
||||||
|
|
||||||
|
tc = common.typecode(msg)
|
||||||
|
if 1 <= tc <= 4: # callsign
|
||||||
|
callsign = adsb.callsign(msg)
|
||||||
|
_print("Type", "Identitification and category")
|
||||||
|
_print("Callsign:", callsign)
|
||||||
|
|
||||||
|
if 5 <= tc <= 8: # surface position
|
||||||
|
_print("Type", "Surface postition")
|
||||||
|
oe = adsb.oe_flag(msg)
|
||||||
|
msgbin = common.hex2bin(msg)
|
||||||
|
cprlat = common.bin2int(msgbin[54:71]) / 131072.0
|
||||||
|
cprlon = common.bin2int(msgbin[71:88]) / 131072.0
|
||||||
|
v = adsb.surface_velocity(msg)
|
||||||
|
_print("CPR format", "Odd" if oe else "Even")
|
||||||
|
_print("CPR Latitude", cprlat)
|
||||||
|
_print("CPR Longitude", cprlon)
|
||||||
|
_print("Speed", v[0], "knots")
|
||||||
|
_print("Track", v[1], "degrees")
|
||||||
|
|
||||||
|
if 9 <= tc <= 18: # airborne position
|
||||||
|
_print("Type", "Airborne position (with barometric altitude)")
|
||||||
|
alt = adsb.altitude(msg)
|
||||||
|
oe = adsb.oe_flag(msg)
|
||||||
|
msgbin = common.hex2bin(msg)
|
||||||
|
cprlat = common.bin2int(msgbin[54:71]) / 131072.0
|
||||||
|
cprlon = common.bin2int(msgbin[71:88]) / 131072.0
|
||||||
|
_print("CPR format", "Odd" if oe else "Even")
|
||||||
|
_print("CPR Latitude", cprlat)
|
||||||
|
_print("CPR Longitude", cprlon)
|
||||||
|
_print("Altitude", alt, "feet")
|
||||||
|
|
||||||
|
if tc == 19:
|
||||||
|
_print("Type", "Airborne velocity")
|
||||||
|
spd, trk, vr, t = adsb.velocity(msg)
|
||||||
|
types = {"GS": "Ground speed", "TAS": "True airspeed"}
|
||||||
|
_print("Speed", spd, "knots")
|
||||||
|
_print("Track", trk, "degrees")
|
||||||
|
_print("Vertical rate", vr, "feet/minute")
|
||||||
|
_print("Type", types[t])
|
||||||
|
|
||||||
|
if 20 <= tc <= 22: # airborne position
|
||||||
|
_print("Type", "Airborne position (with GNSS altitude)")
|
||||||
|
alt = adsb.altitude(msg)
|
||||||
|
oe = adsb.oe_flag(msg)
|
||||||
|
msgbin = common.hex2bin(msg)
|
||||||
|
cprlat = common.bin2int(msgbin[54:71]) / 131072.0
|
||||||
|
cprlon = common.bin2int(msgbin[71:88]) / 131072.0
|
||||||
|
_print("CPR format", "Odd" if oe else "Even")
|
||||||
|
_print("CPR Latitude", cprlat)
|
||||||
|
_print("CPR Longitude", cprlon)
|
||||||
|
_print("Altitude", alt, "feet")
|
||||||
|
|
||||||
|
if df == 20:
|
||||||
|
_print("Protocal", "Mode-S Comm-B altitude reply")
|
||||||
|
_print("Altitude", common.altcode(msg), "feet")
|
||||||
|
|
||||||
|
if df == 21:
|
||||||
|
_print("Protocal", "Mode-S Comm-B identity reply")
|
||||||
|
_print("Squawk code", common.idcode(msg))
|
||||||
|
|
||||||
|
if df == 20 or df == 21:
|
||||||
|
labels = {
|
||||||
|
"BDS10": "Data link capability",
|
||||||
|
"BDS17": "GICB capability",
|
||||||
|
"BDS20": "Aircraft identification",
|
||||||
|
"BDS30": "ACAS resolution",
|
||||||
|
"BDS40": "Vertical intention report",
|
||||||
|
"BDS50": "Track and turn report",
|
||||||
|
"BDS60": "Heading and speed report",
|
||||||
|
"BDS44": "Meteorological routine air report",
|
||||||
|
"BDS45": "Meteorological hazard report",
|
||||||
|
"EMPTY": "[No information available]",
|
||||||
|
}
|
||||||
|
|
||||||
|
BDS = bds.infer(msg, mrar=True)
|
||||||
|
if BDS in labels.keys():
|
||||||
|
_print("BDS", "%s (%s)" % (BDS, labels[BDS]))
|
||||||
|
else:
|
||||||
|
_print("BDS", BDS)
|
||||||
|
|
||||||
|
if BDS == "BDS20":
|
||||||
|
callsign = commb.cs20(msg)
|
||||||
|
_print("Callsign", callsign)
|
||||||
|
|
||||||
|
if BDS == "BDS40":
|
||||||
|
_print("MCP target alt", commb.selalt40mcp(msg), "feet")
|
||||||
|
_print("FMS Target alt", commb.selalt40fms(msg), "feet")
|
||||||
|
_print("Pressure", commb.p40baro(msg), "millibar")
|
||||||
|
|
||||||
|
if BDS == "BDS50":
|
||||||
|
_print("Roll angle", commb.roll50(msg), "degrees")
|
||||||
|
_print("Track angle", commb.trk50(msg), "degrees")
|
||||||
|
_print("Track rate", commb.rtrk50(msg), "degree/second")
|
||||||
|
_print("Ground speed", commb.gs50(msg), "knots")
|
||||||
|
_print("True airspeed", commb.tas50(msg), "knots")
|
||||||
|
|
||||||
|
if BDS == "BDS60":
|
||||||
|
_print("Megnatic Heading", commb.hdg60(msg), "degrees")
|
||||||
|
_print("Indicated airspeed", commb.ias60(msg), "knots")
|
||||||
|
_print("Mach number", commb.mach60(msg))
|
||||||
|
_print("Vertical rate (Baro)", commb.vr60baro(msg), "feet/minute")
|
||||||
|
_print("Vertical rate (INS)", commb.vr60ins(msg), "feet/minute")
|
||||||
|
|
||||||
|
if BDS == "BDS44":
|
||||||
|
_print("Wind speed", commb.wind44(msg)[0], "knots")
|
||||||
|
_print("Wind direction", commb.wind44(msg)[1], "degrees")
|
||||||
|
_print("Temperature 1", commb.temp44(msg)[0], "Celsius")
|
||||||
|
_print("Temperature 2", commb.temp44(msg)[1], "Celsius")
|
||||||
|
_print("Pressure", commb.p44(msg), "hPa")
|
||||||
|
_print("Humidity", commb.hum44(msg), "%")
|
||||||
|
_print("Turbulence", commb.turb44(msg))
|
||||||
|
|
||||||
|
if BDS == "BDS45":
|
||||||
|
_print("Turbulence", commb.turb45(msg))
|
||||||
|
_print("Wind shear", commb.ws45(msg))
|
||||||
|
_print("Microbust", commb.mb45(msg))
|
||||||
|
_print("Icing", commb.ic45(msg))
|
||||||
|
_print("Wake vortex", commb.wv45(msg))
|
||||||
|
_print("Temperature", commb.temp45(msg), "Celsius")
|
||||||
|
_print("Pressure", commb.p45(msg), "hPa")
|
||||||
|
_print("Radio height", commb.rh45(msg), "feet")
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decoding Air-Air Surveillance (ACAS) DF=0/16
|
Decoding Air-Air Surveillance (ACAS) DF=0/16
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
"""ADS-B Wrapper.
|
"""ADS-B Wrapper.
|
||||||
|
|
||||||
The ADS-B wrapper also imports functions from the following modules:
|
The ADS-B wrapper also imports functions from the following modules:
|
||||||
@@ -35,20 +20,32 @@ from pyModeS.decoder import common
|
|||||||
from pyModeS.decoder import uncertainty
|
from pyModeS.decoder import uncertainty
|
||||||
|
|
||||||
# from pyModeS.decoder.bds import bds05, bds06, bds09
|
# 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.bds05 import (
|
||||||
from pyModeS.decoder.bds.bds06 import surface_position, surface_position_with_ref, surface_velocity
|
airborne_position,
|
||||||
|
airborne_position_with_ref,
|
||||||
|
altitude as altitude05,
|
||||||
|
)
|
||||||
|
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.bds08 import category, callsign
|
||||||
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
|
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
|
||||||
|
|
||||||
|
|
||||||
def df(msg):
|
def df(msg):
|
||||||
return common.df(msg)
|
return common.df(msg)
|
||||||
|
|
||||||
|
|
||||||
def icao(msg):
|
def icao(msg):
|
||||||
return common.icao(msg)
|
return common.icao(msg)
|
||||||
|
|
||||||
|
|
||||||
def typecode(msg):
|
def typecode(msg):
|
||||||
return common.typecode(msg)
|
return common.typecode(msg)
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
(works with both airborne and surface position messages)
|
(works with both airborne and surface position messages)
|
||||||
@@ -65,19 +62,21 @@ def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
|||||||
tc0 = typecode(msg0)
|
tc0 = typecode(msg0)
|
||||||
tc1 = typecode(msg1)
|
tc1 = typecode(msg1)
|
||||||
|
|
||||||
if (5<=tc0<=8 and 5<=tc1<=8):
|
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 \
|
||||||
receiver can be used.")
|
receiver can be used."
|
||||||
|
)
|
||||||
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<=tc0<=18 and 9<=tc1<=18):
|
elif 9 <= tc0 <= 18 and 9 <= tc1 <= 18:
|
||||||
# Airborne position with barometric height
|
# Airborne position with barometric height
|
||||||
return airborne_position(msg0, msg1, t0, t1)
|
return airborne_position(msg0, msg1, t0, t1)
|
||||||
|
|
||||||
elif (20<=tc0<=22 and 20<=tc1<=22):
|
elif 20 <= tc0 <= 22 and 20 <= tc1 <= 22:
|
||||||
# Airborne position with GNSS height
|
# Airborne position with GNSS height
|
||||||
return airborne_position(msg0, msg1, t0, t1)
|
return airborne_position(msg0, msg1, t0, t1)
|
||||||
|
|
||||||
@@ -104,10 +103,10 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
|
|
||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if 5<=tc<=8:
|
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<=tc<=18 or 20<=tc<=22:
|
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:
|
||||||
@@ -126,21 +125,16 @@ def altitude(msg):
|
|||||||
|
|
||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc<5 or tc==19 or tc>22:
|
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 tc>=5 and tc<=8:
|
elif tc >= 5 and tc <= 8:
|
||||||
# surface position, altitude 0
|
# surface position, altitude 0
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
|
||||||
q = msgbin[47]
|
|
||||||
if q:
|
|
||||||
n = common.bin2int(msgbin[40:47]+msgbin[48:52])
|
|
||||||
alt = n * 25 - 1000
|
|
||||||
return alt
|
|
||||||
else:
|
else:
|
||||||
return None
|
# airborn position
|
||||||
|
return altitude05(msg)
|
||||||
|
|
||||||
|
|
||||||
def velocity(msg, rtn_sources=False):
|
def velocity(msg, rtn_sources=False):
|
||||||
@@ -163,7 +157,7 @@ def velocity(msg, rtn_sources=False):
|
|||||||
as refrence, 'mag_north' for magnetic north as reference),
|
as refrence, 'mag_north' for magnetic north as reference),
|
||||||
rate of climb/descent source ('Baro' for barometer, 'GNSS'
|
rate of climb/descent source ('Baro' for barometer, 'GNSS'
|
||||||
for GNSS constellation).
|
for GNSS constellation).
|
||||||
|
|
||||||
In the case of surface messages, None will be put in place
|
In the case of surface messages, None will be put in place
|
||||||
for vertical rate and its respective sources.
|
for vertical rate and its respective sources.
|
||||||
"""
|
"""
|
||||||
@@ -175,7 +169,9 @@ def velocity(msg, rtn_sources=False):
|
|||||||
return airborne_velocity(msg, rtn_sources)
|
return airborne_velocity(msg, rtn_sources)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
|
raise RuntimeError(
|
||||||
|
"incorrect or inconsistant message types, expecting 4<TC<9 or TC=19"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def speed_heading(msg):
|
def speed_heading(msg):
|
||||||
@@ -215,7 +211,9 @@ def version(msg):
|
|||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc != 31:
|
if tc != 31:
|
||||||
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
|
raise RuntimeError(
|
||||||
|
"%s: Not a status operation message, expecting TC = 31" % msg
|
||||||
|
)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
version = common.bin2int(msgbin[72:75])
|
version = common.bin2int(msgbin[72:75])
|
||||||
@@ -241,18 +239,18 @@ def nuc_p(msg):
|
|||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"%s: Not a surface position message (5<TC<8), \
|
"%s: Not a surface position message (5<TC<8), \
|
||||||
airborne position message (8<TC<19), \
|
airborne position message (8<TC<19), \
|
||||||
or airborne position with GNSS height (20<TC<22)" % msg
|
or airborne position with GNSS height (20<TC<22)"
|
||||||
|
% msg
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
NUCp = uncertainty.TC_NUCp_lookup[tc]
|
NUCp = uncertainty.TC_NUCp_lookup[tc]
|
||||||
HPL = uncertainty.NUCp[NUCp]['HPL']
|
HPL = uncertainty.NUCp[NUCp]["HPL"]
|
||||||
RCu = uncertainty.NUCp[NUCp]['RCu']
|
RCu = uncertainty.NUCp[NUCp]["RCu"]
|
||||||
RCv = uncertainty.NUCp[NUCp]['RCv']
|
RCv = uncertainty.NUCp[NUCp]["RCv"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
HPL, RCu, RCv = uncertainty.NA, uncertainty.NA, uncertainty.NA
|
HPL, RCu, RCv = uncertainty.NA, uncertainty.NA, uncertainty.NA
|
||||||
|
|
||||||
|
|
||||||
if tc in [20, 21]:
|
if tc in [20, 21]:
|
||||||
RCv = uncertainty.NA
|
RCv = uncertainty.NA
|
||||||
|
|
||||||
@@ -272,15 +270,16 @@ def nuc_v(msg):
|
|||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc != 19:
|
if tc != 19:
|
||||||
raise RuntimeError("%s: Not an airborne velocity message, expecting TC = 19" % msg)
|
raise RuntimeError(
|
||||||
|
"%s: Not an airborne velocity message, expecting TC = 19" % msg
|
||||||
|
)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
NUCv = common.bin2int(msgbin[42:45])
|
NUCv = common.bin2int(msgbin[42:45])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
HVE = uncertainty.NUCv[NUCv]['HVE']
|
HVE = uncertainty.NUCv[NUCv]["HVE"]
|
||||||
VVE = uncertainty.NUCv[NUCv]['VVE']
|
VVE = uncertainty.NUCv[NUCv]["VVE"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
HVE, VVE = uncertainty.NA, uncertainty.NA
|
HVE, VVE = uncertainty.NA, uncertainty.NA
|
||||||
|
|
||||||
@@ -302,7 +301,8 @@ def nic_v1(msg, NICs):
|
|||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"%s: Not a surface position message (5<TC<8), \
|
"%s: Not a surface position message (5<TC<8), \
|
||||||
airborne position message (8<TC<19), \
|
airborne position message (8<TC<19), \
|
||||||
or airborne position with GNSS height (20<TC<22)" % msg
|
or airborne position with GNSS height (20<TC<22)"
|
||||||
|
% msg
|
||||||
)
|
)
|
||||||
|
|
||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
@@ -312,8 +312,8 @@ def nic_v1(msg, NICs):
|
|||||||
NIC = NIC[NICs]
|
NIC = NIC[NICs]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Rc = uncertainty.NICv1[NIC][NICs]['Rc']
|
Rc = uncertainty.NICv1[NIC][NICs]["Rc"]
|
||||||
VPL = uncertainty.NICv1[NIC][NICs]['VPL']
|
VPL = uncertainty.NICv1[NIC][NICs]["VPL"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
Rc, VPL = uncertainty.NA, uncertainty.NA
|
Rc, VPL = uncertainty.NA, uncertainty.NA
|
||||||
|
|
||||||
@@ -335,22 +335,23 @@ def nic_v2(msg, NICa, NICbc):
|
|||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"%s: Not a surface position message (5<TC<8), \
|
"%s: Not a surface position message (5<TC<8), \
|
||||||
airborne position message (8<TC<19), \
|
airborne position message (8<TC<19), \
|
||||||
or airborne position with GNSS height (20<TC<22)" % msg
|
or airborne position with GNSS height (20<TC<22)"
|
||||||
|
% msg
|
||||||
)
|
)
|
||||||
|
|
||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
NIC = uncertainty.TC_NICv2_lookup[tc]
|
NIC = uncertainty.TC_NICv2_lookup[tc]
|
||||||
|
|
||||||
if 20<=tc<=22:
|
if 20 <= tc <= 22:
|
||||||
NICs = 0
|
NICs = 0
|
||||||
else:
|
else:
|
||||||
NICs = NICa*2 + NICbc
|
NICs = NICa * 2 + NICbc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if isinstance(NIC, dict):
|
if isinstance(NIC, dict):
|
||||||
NIC = NIC[NICs]
|
NIC = NIC[NICs]
|
||||||
|
|
||||||
Rc = uncertainty.NICv2[NIC][NICs]['Rc']
|
Rc = uncertainty.NICv2[NIC][NICs]["Rc"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
Rc = uncertainty.NA
|
Rc = uncertainty.NA
|
||||||
|
|
||||||
@@ -369,7 +370,9 @@ def nic_s(msg):
|
|||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc != 31:
|
if tc != 31:
|
||||||
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
|
raise RuntimeError(
|
||||||
|
"%s: Not a status operation message, expecting TC = 31" % msg
|
||||||
|
)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
nic_s = int(msgbin[75])
|
nic_s = int(msgbin[75])
|
||||||
@@ -389,7 +392,9 @@ def nic_a_c(msg):
|
|||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc != 31:
|
if tc != 31:
|
||||||
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
|
raise RuntimeError(
|
||||||
|
"%s: Not a status operation message, expecting TC = 31" % msg
|
||||||
|
)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
nic_a = int(msgbin[75])
|
nic_a = int(msgbin[75])
|
||||||
@@ -410,7 +415,9 @@ def nic_b(msg):
|
|||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc < 9 or tc > 18:
|
if tc < 9 or tc > 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 = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
nic_b = int(msgbin[39])
|
nic_b = int(msgbin[39])
|
||||||
@@ -431,8 +438,11 @@ def nac_p(msg):
|
|||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc not in [29, 31]:
|
if tc not in [29, 31]:
|
||||||
raise RuntimeError("%s: Not a target state and status message, \
|
raise RuntimeError(
|
||||||
or operation status message, expecting TC = 29 or 31" % msg)
|
"%s: Not a target state and status message, \
|
||||||
|
or operation status message, expecting TC = 29 or 31"
|
||||||
|
% msg
|
||||||
|
)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
|
|
||||||
@@ -442,8 +452,8 @@ def nac_p(msg):
|
|||||||
NACp = common.bin2int(msgbin[76:80])
|
NACp = common.bin2int(msgbin[76:80])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
EPU = uncertainty.NACp[NACp]['EPU']
|
EPU = uncertainty.NACp[NACp]["EPU"]
|
||||||
VEPU = uncertainty.NACp[NACp]['VEPU']
|
VEPU = uncertainty.NACp[NACp]["VEPU"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
EPU, VEPU = uncertainty.NA, uncertainty.NA
|
EPU, VEPU = uncertainty.NA, uncertainty.NA
|
||||||
|
|
||||||
@@ -463,14 +473,16 @@ def nac_v(msg):
|
|||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc != 19:
|
if tc != 19:
|
||||||
raise RuntimeError("%s: Not an airborne velocity message, expecting TC = 19" % msg)
|
raise RuntimeError(
|
||||||
|
"%s: Not an airborne velocity message, expecting TC = 19" % msg
|
||||||
|
)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
NACv = common.bin2int(msgbin[42:45])
|
NACv = common.bin2int(msgbin[42:45])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
HFOMr = uncertainty.NACv[NACv]['HFOMr']
|
HFOMr = uncertainty.NACv[NACv]["HFOMr"]
|
||||||
VFOMr = uncertainty.NACv[NACv]['VFOMr']
|
VFOMr = uncertainty.NACv[NACv]["VFOMr"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
HFOMr, VFOMr = uncertainty.NA, uncertainty.NA
|
HFOMr, VFOMr = uncertainty.NA, uncertainty.NA
|
||||||
|
|
||||||
@@ -491,8 +503,11 @@ def sil(msg, version):
|
|||||||
tc = typecode(msg)
|
tc = typecode(msg)
|
||||||
|
|
||||||
if tc not in [29, 31]:
|
if tc not in [29, 31]:
|
||||||
raise RuntimeError("%s: Not a target state and status messag, \
|
raise RuntimeError(
|
||||||
or operation status message, expecting TC = 29 or 31" % msg)
|
"%s: Not a target state and status messag, \
|
||||||
|
or operation status message, expecting TC = 29 or 31"
|
||||||
|
% msg
|
||||||
|
)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
|
|
||||||
@@ -502,12 +517,12 @@ def sil(msg, version):
|
|||||||
SIL = common.bin2int(msgbin[82:84])
|
SIL = common.bin2int(msgbin[82:84])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
PE_RCu = uncertainty.SIL[SIL]['PE_RCu']
|
PE_RCu = uncertainty.SIL[SIL]["PE_RCu"]
|
||||||
PE_VPL = uncertainty.SIL[SIL]['PE_VPL']
|
PE_VPL = uncertainty.SIL[SIL]["PE_VPL"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
PE_RCu, PE_VPL = uncertainty.NA, uncertainty.NA
|
PE_RCu, PE_VPL = uncertainty.NA, uncertainty.NA
|
||||||
|
|
||||||
base = 'unknown'
|
base = "unknown"
|
||||||
|
|
||||||
if version == 2:
|
if version == 2:
|
||||||
if tc == 29:
|
if tc == 29:
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decoding all call replies DF=11
|
Decoding all call replies DF=11
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,22 @@ import numpy as np
|
|||||||
|
|
||||||
from pyModeS.extra import aero
|
from pyModeS.extra import aero
|
||||||
from pyModeS.decoder import common
|
from pyModeS.decoder import common
|
||||||
from pyModeS.decoder.bds import bds05, bds06, bds08, bds09, \
|
from pyModeS.decoder.bds import (
|
||||||
bds10, bds17, bds20, bds30, bds40, bds44, bds45, bds50, bds53, bds60
|
bds05,
|
||||||
|
bds06,
|
||||||
|
bds08,
|
||||||
|
bds09,
|
||||||
|
bds10,
|
||||||
|
bds17,
|
||||||
|
bds20,
|
||||||
|
bds30,
|
||||||
|
bds40,
|
||||||
|
bds44,
|
||||||
|
bds45,
|
||||||
|
bds50,
|
||||||
|
bds53,
|
||||||
|
bds60,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
||||||
@@ -40,6 +54,7 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
|||||||
String or None: BDS version, or possible versions, or None if nothing matches.
|
String or None: BDS version, or possible versions, or None if nothing matches.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def vxy(v, angle):
|
def vxy(v, angle):
|
||||||
vx = v * np.sin(np.radians(angle))
|
vx = v * np.sin(np.radians(angle))
|
||||||
vy = v * np.cos(np.radians(angle))
|
vy = v * np.cos(np.radians(angle))
|
||||||
@@ -52,26 +67,26 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
|||||||
v50 = bds50.gs50(msg)
|
v50 = bds50.gs50(msg)
|
||||||
|
|
||||||
if h50 is None or v50 is None:
|
if h50 is None or v50 is None:
|
||||||
return 'BDS50,BDS60'
|
return "BDS50,BDS60"
|
||||||
|
|
||||||
h60 = bds60.hdg60(msg)
|
h60 = bds60.hdg60(msg)
|
||||||
m60 = bds60.mach60(msg)
|
m60 = bds60.mach60(msg)
|
||||||
i60 = bds60.ias60(msg)
|
i60 = bds60.ias60(msg)
|
||||||
|
|
||||||
if h60 is None or (m60 is None and i60 is None):
|
if h60 is None or (m60 is None and i60 is None):
|
||||||
return 'BDS50,BDS60'
|
return "BDS50,BDS60"
|
||||||
|
|
||||||
m60 = np.nan if m60 is None else m60
|
m60 = np.nan if m60 is None else m60
|
||||||
i60 = np.nan if i60 is None else i60
|
i60 = np.nan if i60 is None else i60
|
||||||
|
|
||||||
XY5 = vxy(v50*aero.kts, h50)
|
XY5 = vxy(v50 * aero.kts, h50)
|
||||||
XY6m = vxy(aero.mach2tas(m60, alt_ref*aero.ft), h60)
|
XY6m = vxy(aero.mach2tas(m60, alt_ref * aero.ft), h60)
|
||||||
XY6i = vxy(aero.cas2tas(i60*aero.kts, alt_ref*aero.ft), h60)
|
XY6i = vxy(aero.cas2tas(i60 * aero.kts, alt_ref * aero.ft), h60)
|
||||||
|
|
||||||
allbds = ['BDS50', 'BDS60', 'BDS60']
|
allbds = ["BDS50", "BDS60", "BDS60"]
|
||||||
|
|
||||||
X = np.array([XY5, XY6m, XY6i])
|
X = np.array([XY5, XY6m, XY6i])
|
||||||
Mu = np.array(vxy(spd_ref*aero.kts, trk_ref))
|
Mu = np.array(vxy(spd_ref * aero.kts, trk_ref))
|
||||||
|
|
||||||
# compute Mahalanobis distance matrix
|
# compute Mahalanobis distance matrix
|
||||||
# Cov = [[20**2, 0], [0, 20**2]]
|
# Cov = [[20**2, 0], [0, 20**2]]
|
||||||
@@ -81,10 +96,10 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
|||||||
# since the covariance matrix is identity matrix,
|
# since the covariance matrix is identity matrix,
|
||||||
# M-dist is same as eculidian distance
|
# M-dist is same as eculidian distance
|
||||||
try:
|
try:
|
||||||
dist = np.linalg.norm(X-Mu, axis=1)
|
dist = np.linalg.norm(X - Mu, axis=1)
|
||||||
BDS = allbds[np.nanargmin(dist)]
|
BDS = allbds[np.nanargmin(dist)]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 'BDS50,BDS60'
|
return "BDS50,BDS60"
|
||||||
|
|
||||||
return BDS
|
return BDS
|
||||||
|
|
||||||
@@ -103,28 +118,28 @@ def infer(msg, mrar=False):
|
|||||||
df = common.df(msg)
|
df = common.df(msg)
|
||||||
|
|
||||||
if common.allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return 'EMPTY'
|
return "EMPTY"
|
||||||
|
|
||||||
# For ADS-B / Mode-S extended squitter
|
# For ADS-B / Mode-S extended squitter
|
||||||
if df == 17:
|
if df == 17:
|
||||||
tc = common.typecode(msg)
|
tc = common.typecode(msg)
|
||||||
|
|
||||||
if 1 <= tc <= 4:
|
if 1 <= tc <= 4:
|
||||||
return 'BDS08' # indentification and category
|
return "BDS08" # indentification and category
|
||||||
if 5 <= tc <= 8:
|
if 5 <= tc <= 8:
|
||||||
return 'BDS06' # surface movement
|
return "BDS06" # surface movement
|
||||||
if 9 <= tc <= 18:
|
if 9 <= tc <= 18:
|
||||||
return 'BDS05' # airborne position, baro-alt
|
return "BDS05" # airborne position, baro-alt
|
||||||
if tc == 19:
|
if tc == 19:
|
||||||
return 'BDS09' # airborne velocity
|
return "BDS09" # airborne velocity
|
||||||
if 20 <= tc <= 22:
|
if 20 <= tc <= 22:
|
||||||
return 'BDS05' # airborne position, gnss-alt
|
return "BDS05" # airborne position, gnss-alt
|
||||||
if tc == 28:
|
if tc == 28:
|
||||||
return 'BDS61' # aircraft status
|
return "BDS61" # aircraft status
|
||||||
if tc == 29:
|
if tc == 29:
|
||||||
return 'BDS62' # target state and status
|
return "BDS62" # target state and status
|
||||||
if tc == 31:
|
if tc == 31:
|
||||||
return 'BDS65' # operational status
|
return "BDS65" # operational status
|
||||||
|
|
||||||
# For Comm-B replies
|
# For Comm-B replies
|
||||||
IS10 = bds10.is10(msg)
|
IS10 = bds10.is10(msg)
|
||||||
@@ -138,15 +153,27 @@ def infer(msg, mrar=False):
|
|||||||
IS45 = bds45.is45(msg)
|
IS45 = bds45.is45(msg)
|
||||||
|
|
||||||
if mrar:
|
if mrar:
|
||||||
allbds = np.array(["BDS10", "BDS17", "BDS20", "BDS30", "BDS40",
|
allbds = np.array(
|
||||||
"BDS44", "BDS45", "BDS50", "BDS60"])
|
[
|
||||||
|
"BDS10",
|
||||||
|
"BDS17",
|
||||||
|
"BDS20",
|
||||||
|
"BDS30",
|
||||||
|
"BDS40",
|
||||||
|
"BDS44",
|
||||||
|
"BDS45",
|
||||||
|
"BDS50",
|
||||||
|
"BDS60",
|
||||||
|
]
|
||||||
|
)
|
||||||
mask = [IS10, IS17, IS20, IS30, IS40, IS44, IS45, IS50, IS60]
|
mask = [IS10, IS17, IS20, IS30, IS40, IS44, IS45, IS50, IS60]
|
||||||
else:
|
else:
|
||||||
allbds = np.array(["BDS10", "BDS17", "BDS20", "BDS30", "BDS40",
|
allbds = np.array(
|
||||||
"BDS50", "BDS60"])
|
["BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS50", "BDS60"]
|
||||||
|
)
|
||||||
mask = [IS10, IS17, IS20, IS30, IS40, IS50, IS60]
|
mask = [IS10, IS17, IS20, IS30, IS40, IS50, IS60]
|
||||||
|
|
||||||
bds = ','.join(sorted(allbds[mask]))
|
bds = ",".join(sorted(allbds[mask]))
|
||||||
|
|
||||||
if len(bds) == 0:
|
if len(bds) == 0:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
# 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
|
# BDS 0,5
|
||||||
# ADS-B TC=9-18
|
# ADS-B TC=9-18
|
||||||
@@ -24,6 +8,7 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder import common
|
from pyModeS.decoder import common
|
||||||
|
|
||||||
|
|
||||||
def airborne_position(msg0, msg1, t0, t1):
|
def airborne_position(msg0, msg1, t0, t1):
|
||||||
"""Decode airborn position from a pair of even and odd position message
|
"""Decode airborn position from a pair of even and odd position message
|
||||||
|
|
||||||
@@ -40,6 +25,16 @@ def airborne_position(msg0, msg1, t0, t1):
|
|||||||
mb0 = common.hex2bin(msg0)[32:]
|
mb0 = common.hex2bin(msg0)[32:]
|
||||||
mb1 = common.hex2bin(msg1)[32:]
|
mb1 = common.hex2bin(msg1)[32:]
|
||||||
|
|
||||||
|
oe0 = int(mb0[21])
|
||||||
|
oe1 = int(mb1[21])
|
||||||
|
if oe0 == 0 and oe1 == 1:
|
||||||
|
pass
|
||||||
|
elif oe0 == 1 and oe1 == 0:
|
||||||
|
mb0, mb1 = mb1, mb0
|
||||||
|
t0, t1 = t1, t0
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Both even and odd CPR frames are required.")
|
||||||
|
|
||||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||||
cprlat_even = common.bin2int(mb0[22:39]) / 131072.0
|
cprlat_even = common.bin2int(mb0[22:39]) / 131072.0
|
||||||
cprlon_even = common.bin2int(mb0[39:56]) / 131072.0
|
cprlon_even = common.bin2int(mb0[39:56]) / 131072.0
|
||||||
@@ -66,17 +61,17 @@ def airborne_position(msg0, msg1, t0, t1):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# compute ni, longitude index m, and longitude
|
# compute ni, longitude index m, and longitude
|
||||||
if (t0 > t1):
|
if t0 > t1:
|
||||||
lat = lat_even
|
lat = lat_even
|
||||||
nl = common.cprNL(lat)
|
nl = common.cprNL(lat)
|
||||||
ni = max(common.cprNL(lat)- 0, 1)
|
ni = max(common.cprNL(lat) - 0, 1)
|
||||||
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
|
||||||
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
||||||
else:
|
else:
|
||||||
lat = lat_odd
|
lat = lat_odd
|
||||||
nl = common.cprNL(lat)
|
nl = common.cprNL(lat)
|
||||||
ni = max(common.cprNL(lat) - 1, 1)
|
ni = max(common.cprNL(lat) - 1, 1)
|
||||||
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
|
||||||
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
||||||
|
|
||||||
if lon > 180:
|
if lon > 180:
|
||||||
@@ -100,17 +95,17 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
(float, float): (latitude, longitude) of the aircraft
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
mb = common.hex2bin(msg)[32:]
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
|
||||||
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
||||||
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
||||||
|
|
||||||
i = int(mb[21])
|
i = int(mb[21])
|
||||||
d_lat = 360.0/59 if i else 360.0/60
|
d_lat = 360.0 / 59 if i else 360.0 / 60
|
||||||
|
|
||||||
j = common.floor(lat_ref / d_lat) \
|
j = common.floor(lat_ref / d_lat) + common.floor(
|
||||||
+ common.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
0.5 + ((lat_ref % d_lat) / d_lat) - cprlat
|
||||||
|
)
|
||||||
|
|
||||||
lat = d_lat * (j + cprlat)
|
lat = d_lat * (j + cprlat)
|
||||||
|
|
||||||
@@ -121,8 +116,9 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
else:
|
else:
|
||||||
d_lon = 360.0
|
d_lon = 360.0
|
||||||
|
|
||||||
m = common.floor(lon_ref / d_lon) \
|
m = common.floor(lon_ref / d_lon) + common.floor(
|
||||||
+ common.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
0.5 + ((lon_ref % d_lon) / d_lon) - cprlon
|
||||||
|
)
|
||||||
|
|
||||||
lon = d_lon * (m + cprlon)
|
lon = d_lon * (m + cprlon)
|
||||||
|
|
||||||
@@ -141,7 +137,7 @@ def altitude(msg):
|
|||||||
|
|
||||||
tc = common.typecode(msg)
|
tc = common.typecode(msg)
|
||||||
|
|
||||||
if tc<9 or tc==19 or tc>22:
|
if tc < 9 or tc == 19 or tc > 22:
|
||||||
raise RuntimeError("%s: Not a airborn position message" % msg)
|
raise RuntimeError("%s: Not a airborn position message" % msg)
|
||||||
|
|
||||||
mb = common.hex2bin(msg)[32:]
|
mb = common.hex2bin(msg)[32:]
|
||||||
@@ -150,7 +146,7 @@ def altitude(msg):
|
|||||||
# barometric altitude
|
# barometric altitude
|
||||||
q = mb[15]
|
q = mb[15]
|
||||||
if q:
|
if q:
|
||||||
n = common.bin2int(mb[8:15]+mb[16:20])
|
n = common.bin2int(mb[8:15] + mb[16:20])
|
||||||
alt = n * 25 - 1000
|
alt = n * 25 - 1000
|
||||||
else:
|
else:
|
||||||
alt = None
|
alt = None
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
# 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
|
# BDS 0,6
|
||||||
# ADS-B TC=5-8
|
# ADS-B TC=5-8
|
||||||
@@ -73,22 +57,25 @@ def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# compute ni, longitude index m, and longitude
|
# compute ni, longitude index m, and longitude
|
||||||
if (t0 > t1):
|
if t0 > t1:
|
||||||
lat = lat_even
|
lat = lat_even
|
||||||
nl = common.cprNL(lat_even)
|
nl = common.cprNL(lat_even)
|
||||||
ni = max(common.cprNL(lat_even) - 0, 1)
|
ni = max(common.cprNL(lat_even) - 0, 1)
|
||||||
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
|
||||||
lon = (90.0 / ni) * (m % ni + cprlon_even)
|
lon = (90.0 / ni) * (m % ni + cprlon_even)
|
||||||
else:
|
else:
|
||||||
lat = lat_odd
|
lat = lat_odd
|
||||||
nl = common.cprNL(lat_odd)
|
nl = common.cprNL(lat_odd)
|
||||||
ni = max(common.cprNL(lat_odd) - 1, 1)
|
ni = max(common.cprNL(lat_odd) - 1, 1)
|
||||||
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
|
||||||
lon = (90.0 / ni) * (m % ni + cprlon_odd)
|
lon = (90.0 / ni) * (m % ni + cprlon_odd)
|
||||||
|
|
||||||
# four possible longitude solutions
|
# four possible longitude solutions
|
||||||
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
|
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
|
||||||
|
|
||||||
|
# make sure lons are between -180 and 180
|
||||||
|
lons = [(l + 180) % 360 - 180 for l in lons]
|
||||||
|
|
||||||
# the closest solution to receiver is the correct one
|
# the closest solution to receiver is the correct one
|
||||||
dls = [abs(lon_ref - l) for l in lons]
|
dls = [abs(lon_ref - l) for l in lons]
|
||||||
imin = min(range(4), key=dls.__getitem__)
|
imin = min(range(4), key=dls.__getitem__)
|
||||||
@@ -112,17 +99,17 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
(float, float): (latitude, longitude) of the aircraft
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
mb = common.hex2bin(msg)[32:]
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
|
||||||
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
||||||
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
||||||
|
|
||||||
i = int(mb[21])
|
i = int(mb[21])
|
||||||
d_lat = 90.0/59 if i else 90.0/60
|
d_lat = 90.0 / 59 if i else 90.0 / 60
|
||||||
|
|
||||||
j = common.floor(lat_ref / d_lat) \
|
j = common.floor(lat_ref / d_lat) + common.floor(
|
||||||
+ common.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
0.5 + ((lat_ref % d_lat) / d_lat) - cprlat
|
||||||
|
)
|
||||||
|
|
||||||
lat = d_lat * (j + cprlat)
|
lat = d_lat * (j + cprlat)
|
||||||
|
|
||||||
@@ -133,8 +120,9 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
else:
|
else:
|
||||||
d_lon = 90.0
|
d_lon = 90.0
|
||||||
|
|
||||||
m = common.floor(lon_ref / d_lon) \
|
m = common.floor(lon_ref / d_lon) + common.floor(
|
||||||
+ common.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
0.5 + ((lon_ref % d_lon) / d_lon) - cprlon
|
||||||
|
)
|
||||||
|
|
||||||
lon = d_lon * (m + cprlon)
|
lon = d_lon * (m + cprlon)
|
||||||
|
|
||||||
@@ -184,11 +172,11 @@ def surface_velocity(msg, rtn_sources=False):
|
|||||||
movs = [2, 9, 13, 39, 94, 109, 124]
|
movs = [2, 9, 13, 39, 94, 109, 124]
|
||||||
kts = [0.125, 1, 2, 15, 70, 100, 175]
|
kts = [0.125, 1, 2, 15, 70, 100, 175]
|
||||||
i = next(m[0] for m in enumerate(movs) if m[1] > mov)
|
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])
|
step = (kts[i] - kts[i - 1]) * 1.0 / (movs[i] - movs[i - 1])
|
||||||
spd = kts[i-1] + (mov-movs[i-1]) * step
|
spd = kts[i - 1] + (mov - movs[i - 1]) * step
|
||||||
spd = round(spd, 2)
|
spd = round(spd, 2)
|
||||||
|
|
||||||
if rtn_sources:
|
if rtn_sources:
|
||||||
return spd, trk, 0, 'GS', 'true_north', None
|
return spd, trk, 0, "GS", "true_north", None
|
||||||
else:
|
else:
|
||||||
return spd, trk, 0, 'GS'
|
return spd, trk, 0, "GS"
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
# 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
|
# BDS 0,8
|
||||||
# ADS-B TC=1-4
|
# ADS-B TC=1-4
|
||||||
@@ -23,6 +7,7 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder import common
|
from pyModeS.decoder import common
|
||||||
|
|
||||||
|
|
||||||
def category(msg):
|
def category(msg):
|
||||||
"""Aircraft category number
|
"""Aircraft category number
|
||||||
|
|
||||||
@@ -37,7 +22,8 @@ def category(msg):
|
|||||||
raise RuntimeError("%s: Not a identification message" % msg)
|
raise RuntimeError("%s: Not a identification message" % msg)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
return common.bin2int(msgbin[5:8])
|
mebin = msgbin[32:87]
|
||||||
|
return common.bin2int(mebin[5:8])
|
||||||
|
|
||||||
|
|
||||||
def callsign(msg):
|
def callsign(msg):
|
||||||
@@ -53,11 +39,11 @@ def callsign(msg):
|
|||||||
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
|
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
|
||||||
raise RuntimeError("%s: Not a identification message" % msg)
|
raise RuntimeError("%s: Not a identification message" % msg)
|
||||||
|
|
||||||
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
csbin = msgbin[40:96]
|
csbin = msgbin[40:96]
|
||||||
|
|
||||||
cs = ''
|
cs = ""
|
||||||
cs += chars[common.bin2int(csbin[0:6])]
|
cs += chars[common.bin2int(csbin[0:6])]
|
||||||
cs += chars[common.bin2int(csbin[6:12])]
|
cs += chars[common.bin2int(csbin[6:12])]
|
||||||
cs += chars[common.bin2int(csbin[12:18])]
|
cs += chars[common.bin2int(csbin[12:18])]
|
||||||
@@ -69,5 +55,5 @@ def callsign(msg):
|
|||||||
|
|
||||||
# clean string, remove spaces and marks, if any.
|
# clean string, remove spaces and marks, if any.
|
||||||
# cs = cs.replace('_', '')
|
# cs = cs.replace('_', '')
|
||||||
cs = cs.replace('#', '')
|
cs = cs.replace("#", "")
|
||||||
return cs
|
return cs
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
# 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
|
# BDS 0,9
|
||||||
# ADS-B TC=19
|
# ADS-B TC=19
|
||||||
@@ -57,32 +41,32 @@ def airborne_velocity(msg, rtn_sources=False):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if subtype in (1, 2):
|
if subtype in (1, 2):
|
||||||
v_ew_sign = -1 if mb[13]=='1' else 1
|
v_ew_sign = -1 if mb[13] == "1" else 1
|
||||||
v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity
|
v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity
|
||||||
if subtype == 2: # Supersonic
|
if subtype == 2: # Supersonic
|
||||||
v_ew *= 4
|
v_ew *= 4
|
||||||
|
|
||||||
v_ns_sign = -1 if mb[24]=='1' else 1
|
v_ns_sign = -1 if mb[24] == "1" else 1
|
||||||
v_ns = common.bin2int(mb[25:35]) - 1 # north-south velocity
|
v_ns = common.bin2int(mb[25:35]) - 1 # north-south velocity
|
||||||
if subtype == 2: # Supersonic
|
if subtype == 2: # Supersonic
|
||||||
v_ns *= 4
|
v_ns *= 4
|
||||||
|
|
||||||
v_we = v_ew_sign * v_ew
|
v_we = v_ew_sign * v_ew
|
||||||
v_sn = v_ns_sign * v_ns
|
v_sn = v_ns_sign * v_ns
|
||||||
|
|
||||||
spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts
|
spd = math.sqrt(v_sn * v_sn + v_we * v_we) # unit in kts
|
||||||
spd = int(spd)
|
spd = int(spd)
|
||||||
|
|
||||||
trk = math.atan2(v_we, v_sn)
|
trk = math.atan2(v_we, v_sn)
|
||||||
trk = math.degrees(trk) # convert to degrees
|
trk = math.degrees(trk) # convert to degrees
|
||||||
trk = trk if trk >= 0 else trk + 360 # no negative val
|
trk = trk if trk >= 0 else trk + 360 # no negative val
|
||||||
|
|
||||||
tag = 'GS'
|
tag = "GS"
|
||||||
trk_or_hdg = round(trk, 2)
|
trk_or_hdg = round(trk, 2)
|
||||||
dir_type = 'true_north'
|
dir_type = "true_north"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if mb[13] == '0':
|
if mb[13] == "0":
|
||||||
hdg = None
|
hdg = None
|
||||||
else:
|
else:
|
||||||
hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0
|
hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0
|
||||||
@@ -91,27 +75,27 @@ def airborne_velocity(msg, rtn_sources=False):
|
|||||||
trk_or_hdg = hdg
|
trk_or_hdg = hdg
|
||||||
|
|
||||||
spd = common.bin2int(mb[25:35])
|
spd = common.bin2int(mb[25:35])
|
||||||
spd = None if spd==0 else spd-1
|
spd = None if spd == 0 else spd - 1
|
||||||
if subtype == 4: # Supersonic
|
if subtype == 4: # Supersonic
|
||||||
spd *= 4
|
spd *= 4
|
||||||
|
|
||||||
if mb[24]=='0':
|
if mb[24] == "0":
|
||||||
tag = 'IAS'
|
tag = "IAS"
|
||||||
else:
|
else:
|
||||||
tag = 'TAS'
|
tag = "TAS"
|
||||||
|
|
||||||
dir_type = 'mag_north'
|
|
||||||
|
|
||||||
vr_source = 'GNSS' if mb[35]=='0' else 'Baro'
|
dir_type = "mag_north"
|
||||||
vr_sign = -1 if mb[36]=='1' else 1
|
|
||||||
|
vr_source = "GNSS" if mb[35] == "0" else "Baro"
|
||||||
|
vr_sign = -1 if mb[36] == "1" else 1
|
||||||
vr = common.bin2int(mb[37:46])
|
vr = common.bin2int(mb[37:46])
|
||||||
rocd = None if vr==0 else int(vr_sign*(vr-1)*64)
|
rocd = None if vr == 0 else int(vr_sign * (vr - 1) * 64)
|
||||||
|
|
||||||
if rtn_sources:
|
if rtn_sources:
|
||||||
return spd, trk_or_hdg, rocd, tag, dir_type, vr_source
|
return spd, trk_or_hdg, rocd, tag, dir_type, vr_source
|
||||||
else:
|
else:
|
||||||
return spd, trk_or_hdg, rocd, tag
|
return spd, trk_or_hdg, rocd, tag
|
||||||
|
|
||||||
|
|
||||||
def altitude_diff(msg):
|
def altitude_diff(msg):
|
||||||
"""Decode the differece between GNSS and barometric altitude
|
"""Decode the differece between GNSS and barometric altitude
|
||||||
@@ -135,4 +119,4 @@ def altitude_diff(msg):
|
|||||||
if value == 0 or value == 127:
|
if value == 0 or value == 127:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return sign * (value - 1) * 25 # in ft.
|
return sign * (value - 1) * 25 # in ft.
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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 1,0
|
# BDS 1,0
|
||||||
# Data link capability report
|
# Data link capability report
|
||||||
@@ -21,6 +6,7 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||||
|
|
||||||
|
|
||||||
def is10(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
|
||||||
|
|
||||||
@@ -37,7 +23,7 @@ def is10(msg):
|
|||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
# first 8 bits must be 0x10
|
# first 8 bits must be 0x10
|
||||||
if d[0:8] != '00010000':
|
if d[0:8] != "00010000":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# bit 10 to 14 are reserved
|
# bit 10 to 14 are reserved
|
||||||
@@ -45,13 +31,14 @@ def is10(msg):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# overlay capabilty conflict
|
# overlay capabilty conflict
|
||||||
if d[14] == '1' and bin2int(d[16:23]) < 5:
|
if d[14] == "1" and bin2int(d[16:23]) < 5:
|
||||||
return False
|
return False
|
||||||
if d[14] == '0' and bin2int(d[16:23]) > 4:
|
if d[14] == "0" and bin2int(d[16:23]) > 4:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def ovc10(msg):
|
def ovc10(msg):
|
||||||
"""Return the overlay control capability
|
"""Return the overlay control capability
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
# 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 1,7
|
# BDS 1,7
|
||||||
# Common usage GICB capability report
|
# Common usage GICB capability report
|
||||||
@@ -50,11 +34,12 @@ def is17(msg):
|
|||||||
# return False
|
# return False
|
||||||
|
|
||||||
# at least you can respond who you are
|
# at least you can respond who you are
|
||||||
if 'BDS20' not in caps:
|
if "BDS20" not in caps:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def cap17(msg):
|
def cap17(msg):
|
||||||
"""Extract capacities from BDS 1,7 message
|
"""Extract capacities from BDS 1,7 message
|
||||||
|
|
||||||
@@ -64,12 +49,39 @@ def cap17(msg):
|
|||||||
Returns:
|
Returns:
|
||||||
list: list of suport BDS codes
|
list: list of suport BDS codes
|
||||||
"""
|
"""
|
||||||
allbds = ['05', '06', '07', '08', '09', '0A', '20', '21', '40', '41',
|
allbds = [
|
||||||
'42', '43', '44', '45', '48', '50', '51', '52', '53', '54',
|
"05",
|
||||||
'55', '56', '5F', '60', 'NA', 'NA', 'E1', 'E2']
|
"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 = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
idx = [i for i, v in enumerate(d[:28]) if v=='1']
|
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']
|
capacity = ["BDS" + allbds[i] for i in idx if allbds[i] is not "NA"]
|
||||||
|
|
||||||
return capacity
|
return capacity
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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 2,0
|
# BDS 2,0
|
||||||
# Aircraft identification
|
# Aircraft identification
|
||||||
@@ -21,6 +6,7 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||||
|
|
||||||
|
|
||||||
def is20(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
|
||||||
|
|
||||||
@@ -36,12 +22,12 @@ def is20(msg):
|
|||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[0:8] != '00100000':
|
if d[0:8] != "00100000":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
cs = cs20(msg)
|
cs = cs20(msg)
|
||||||
|
|
||||||
if '#' in cs:
|
if "#" in cs:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -56,11 +42,11 @@ def cs20(msg):
|
|||||||
Returns:
|
Returns:
|
||||||
string: callsign, max. 8 chars
|
string: callsign, max. 8 chars
|
||||||
"""
|
"""
|
||||||
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
cs = ''
|
cs = ""
|
||||||
cs += chars[bin2int(d[8:14])]
|
cs += chars[bin2int(d[8:14])]
|
||||||
cs += chars[bin2int(d[14:20])]
|
cs += chars[bin2int(d[14:20])]
|
||||||
cs += chars[bin2int(d[20:26])]
|
cs += chars[bin2int(d[20:26])]
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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 3,0
|
# BDS 3,0
|
||||||
# ACAS active resolution advisory
|
# ACAS active resolution advisory
|
||||||
@@ -21,6 +6,7 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||||
|
|
||||||
|
|
||||||
def is30(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
|
||||||
|
|
||||||
@@ -36,11 +22,11 @@ def is30(msg):
|
|||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[0:8] != '00110000':
|
if d[0:8] != "00110000":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# threat type 3 not assigned
|
# threat type 3 not assigned
|
||||||
if d[28:30] == '11':
|
if d[28:30] == "11":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# reserved for ACAS III, in far future
|
# reserved for ACAS III, in far future
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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 4,0
|
# BDS 4,0
|
||||||
# Selected vertical intention
|
# Selected vertical intention
|
||||||
@@ -121,7 +106,6 @@ def p40baro(msg):
|
|||||||
|
|
||||||
|
|
||||||
def alt40mcp(msg):
|
def alt40mcp(msg):
|
||||||
warnings.simplefilter("once", DeprecationWarning)
|
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"alt40mcp() has been renamed to selalt40mcp(). It will be removed in the future.",
|
"alt40mcp() has been renamed to selalt40mcp(). It will be removed in the future.",
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
@@ -130,7 +114,6 @@ def alt40mcp(msg):
|
|||||||
|
|
||||||
|
|
||||||
def alt40fms(msg):
|
def alt40fms(msg):
|
||||||
warnings.simplefilter("once", DeprecationWarning)
|
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"alt40fms() has been renamed to selalt40fms(). It will be removed in the future.",
|
"alt40fms() has been renamed to selalt40fms(). It will be removed in the future.",
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
|
|||||||
@@ -1,31 +1,10 @@
|
|||||||
# 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 4,4
|
# BDS 4,4
|
||||||
# Meteorological routine air report
|
# Meteorological routine air report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.common import (
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||||
hex2bin,
|
|
||||||
bin2int,
|
|
||||||
data,
|
|
||||||
allzeros,
|
|
||||||
wrongstatus,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is44(msg):
|
def is44(msg):
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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 4,5
|
# BDS 4,5
|
||||||
# Meteorological hazard report
|
# Meteorological hazard report
|
||||||
@@ -87,7 +72,7 @@ def turb45(msg):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
if d[0] == '0':
|
if d[0] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
turb = bin2int(d[1:3])
|
turb = bin2int(d[1:3])
|
||||||
@@ -105,7 +90,7 @@ def ws45(msg):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
if d[3] == '0':
|
if d[3] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ws = bin2int(d[4:6])
|
ws = bin2int(d[4:6])
|
||||||
@@ -123,7 +108,7 @@ def mb45(msg):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
if d[6] == '0':
|
if d[6] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mb = bin2int(d[7:9])
|
mb = bin2int(d[7:9])
|
||||||
@@ -141,7 +126,7 @@ def ic45(msg):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
if d[9] == '0':
|
if d[9] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ic = bin2int(d[10:12])
|
ic = bin2int(d[10:12])
|
||||||
@@ -159,7 +144,7 @@ def wv45(msg):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
if d[12] == '0':
|
if d[12] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ws = bin2int(d[13:15])
|
ws = bin2int(d[13:15])
|
||||||
@@ -184,7 +169,7 @@ def temp45(msg):
|
|||||||
if sign:
|
if sign:
|
||||||
value = value - 512
|
value = value - 512
|
||||||
|
|
||||||
temp = value * 0.25 # celsius
|
temp = value * 0.25 # celsius
|
||||||
temp = round(temp, 1)
|
temp = round(temp, 1)
|
||||||
|
|
||||||
return temp
|
return temp
|
||||||
@@ -201,9 +186,9 @@ def p45(msg):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
if d[26] == '0':
|
if d[26] == "0":
|
||||||
return None
|
return None
|
||||||
p = bin2int(d[27:38]) # hPa
|
p = bin2int(d[27:38]) # hPa
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
@@ -218,7 +203,7 @@ def rh45(msg):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
if d[38] == '0':
|
if d[38] == "0":
|
||||||
return None
|
return None
|
||||||
rh = bin2int(d[39:51]) * 16
|
rh = bin2int(d[39:51]) * 16
|
||||||
return rh
|
return rh
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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 5,0
|
# BDS 5,0
|
||||||
# Track and turn report
|
# Track and turn report
|
||||||
@@ -85,16 +70,16 @@ def roll50(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[0] == '0':
|
if d[0] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sign = int(d[1]) # 1 -> left wing down
|
sign = int(d[1]) # 1 -> left wing down
|
||||||
value = bin2int(d[2:11])
|
value = bin2int(d[2:11])
|
||||||
|
|
||||||
if sign:
|
if sign:
|
||||||
value = value - 512
|
value = value - 512
|
||||||
|
|
||||||
angle = value * 45.0 / 256.0 # degree
|
angle = value * 45.0 / 256.0 # degree
|
||||||
return round(angle, 1)
|
return round(angle, 1)
|
||||||
|
|
||||||
|
|
||||||
@@ -109,10 +94,10 @@ def trk50(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[11] == '0':
|
if d[11] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sign = int(d[12]) # 1 -> west
|
sign = int(d[12]) # 1 -> west
|
||||||
value = bin2int(d[13:23])
|
value = bin2int(d[13:23])
|
||||||
|
|
||||||
if sign:
|
if sign:
|
||||||
@@ -138,10 +123,10 @@ def gs50(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[23] == '0':
|
if d[23] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
spd = bin2int(d[24:34]) * 2 # kts
|
spd = bin2int(d[24:34]) * 2 # kts
|
||||||
return spd
|
return spd
|
||||||
|
|
||||||
|
|
||||||
@@ -156,18 +141,18 @@ def rtrk50(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[34] == '0':
|
if d[34] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if d[36:45] == "111111111":
|
if d[36:45] == "111111111":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||||
value = bin2int(d[36:45])
|
value = bin2int(d[36:45])
|
||||||
if sign:
|
if sign:
|
||||||
value = value - 512
|
value = value - 512
|
||||||
|
|
||||||
angle = value * 8.0 / 256.0 # degree / sec
|
angle = value * 8.0 / 256.0 # degree / sec
|
||||||
return round(angle, 3)
|
return round(angle, 3)
|
||||||
|
|
||||||
|
|
||||||
@@ -182,8 +167,8 @@ def tas50(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[45] == '0':
|
if d[45] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tas = bin2int(d[46:56]) * 2 # kts
|
tas = bin2int(d[46:56]) * 2 # kts
|
||||||
return tas
|
return tas
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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 5,3
|
# BDS 5,3
|
||||||
# Air-referenced state vector
|
# Air-referenced state vector
|
||||||
@@ -85,16 +70,16 @@ def hdg53(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[0] == '0':
|
if d[0] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sign = int(d[1]) # 1 -> west
|
sign = int(d[1]) # 1 -> west
|
||||||
value = bin2int(d[2:12])
|
value = bin2int(d[2:12])
|
||||||
|
|
||||||
if sign:
|
if sign:
|
||||||
value = value - 1024
|
value = value - 1024
|
||||||
|
|
||||||
hdg = value * 90.0 / 512.0 # degree
|
hdg = value * 90.0 / 512.0 # degree
|
||||||
|
|
||||||
# convert from [-180, 180] to [0, 360]
|
# convert from [-180, 180] to [0, 360]
|
||||||
if hdg < 0:
|
if hdg < 0:
|
||||||
@@ -114,10 +99,10 @@ def ias53(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[12] == '0':
|
if d[12] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ias = bin2int(d[13:23]) # knots
|
ias = bin2int(d[13:23]) # knots
|
||||||
return ias
|
return ias
|
||||||
|
|
||||||
|
|
||||||
@@ -132,7 +117,7 @@ def mach53(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[23] == '0':
|
if d[23] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mach = bin2int(d[24:33]) * 0.008
|
mach = bin2int(d[24:33]) * 0.008
|
||||||
@@ -150,12 +135,13 @@ def tas53(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[33] == '0':
|
if d[33] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tas = bin2int(d[34:46]) * 0.5 # kts
|
tas = bin2int(d[34:46]) * 0.5 # kts
|
||||||
return round(tas, 1)
|
return round(tas, 1)
|
||||||
|
|
||||||
|
|
||||||
def vr53(msg):
|
def vr53(msg):
|
||||||
"""Vertical rate
|
"""Vertical rate
|
||||||
|
|
||||||
@@ -167,16 +153,16 @@ def vr53(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[46] == '0':
|
if d[46] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sign = int(d[47]) # 1 -> negative value, two's complement
|
sign = int(d[47]) # 1 -> negative value, two's complement
|
||||||
value = bin2int(d[48:56])
|
value = bin2int(d[48:56])
|
||||||
|
|
||||||
if value == 0 or value == 255: # all zeros or all ones
|
if value == 0 or value == 255: # all zeros or all ones
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
value = value - 256 if sign else value
|
value = value - 256 if sign else value
|
||||||
roc = value * 64 # feet/min
|
roc = value * 64 # feet/min
|
||||||
|
|
||||||
return roc
|
return roc
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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 6,0
|
# BDS 6,0
|
||||||
# Heading and speed report
|
# Heading and speed report
|
||||||
@@ -21,6 +6,7 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
from __future__ import absolute_import, print_function, division
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||||
|
|
||||||
|
|
||||||
def is60(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
|
||||||
|
|
||||||
@@ -83,10 +69,10 @@ def hdg60(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[0] == '0':
|
if d[0] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sign = int(d[1]) # 1 -> west
|
sign = int(d[1]) # 1 -> west
|
||||||
value = bin2int(d[2:12])
|
value = bin2int(d[2:12])
|
||||||
|
|
||||||
if sign:
|
if sign:
|
||||||
@@ -112,10 +98,10 @@ def ias60(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[12] == '0':
|
if d[12] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ias = bin2int(d[13:23]) # kts
|
ias = bin2int(d[13:23]) # kts
|
||||||
return ias
|
return ias
|
||||||
|
|
||||||
|
|
||||||
@@ -130,7 +116,7 @@ def mach60(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[23] == '0':
|
if d[23] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mach = bin2int(d[24:34]) * 2.048 / 512.0
|
mach = bin2int(d[24:34]) * 2.048 / 512.0
|
||||||
@@ -148,10 +134,10 @@ def vr60baro(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[34] == '0':
|
if d[34] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||||
value = bin2int(d[36:45])
|
value = bin2int(d[36:45])
|
||||||
|
|
||||||
if value == 0 or value == 511: # all zeros or all ones
|
if value == 0 or value == 511: # all zeros or all ones
|
||||||
@@ -159,7 +145,7 @@ def vr60baro(msg):
|
|||||||
|
|
||||||
value = value - 512 if sign else value
|
value = value - 512 if sign else value
|
||||||
|
|
||||||
roc = value * 32 # feet/min
|
roc = value * 32 # feet/min
|
||||||
return roc
|
return roc
|
||||||
|
|
||||||
|
|
||||||
@@ -174,10 +160,10 @@ def vr60ins(msg):
|
|||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if d[45] == '0':
|
if d[45] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sign = int(d[46]) # 1 -> negative value, two's complement
|
sign = int(d[46]) # 1 -> negative value, two's complement
|
||||||
value = bin2int(d[47:56])
|
value = bin2int(d[47:56])
|
||||||
|
|
||||||
if value == 0 or value == 511: # all zeros or all ones
|
if value == 0 or value == 511: # all zeros or all ones
|
||||||
@@ -185,5 +171,5 @@ def vr60ins(msg):
|
|||||||
|
|
||||||
value = value - 512 if sign else value
|
value = value - 512 if sign else value
|
||||||
|
|
||||||
roc = value * 32 # feet/min
|
roc = value * 32 # feet/min
|
||||||
return roc
|
return roc
|
||||||
|
|||||||
23
pyModeS/decoder/c_common.pxd
Normal file
23
pyModeS/decoder/c_common.pxd
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# cython: language_level=3
|
||||||
|
|
||||||
|
cdef int char_to_int(unsigned char binstr)
|
||||||
|
cdef unsigned char int_to_char(unsigned char i)
|
||||||
|
|
||||||
|
cpdef str hex2bin(str hexstr)
|
||||||
|
cpdef long bin2int(str binstr)
|
||||||
|
cpdef long hex2int(str binstr)
|
||||||
|
|
||||||
|
cpdef unsigned char df(str msg)
|
||||||
|
cpdef long crc(str msg, bint encode=*)
|
||||||
|
|
||||||
|
cpdef long floor(double x)
|
||||||
|
cpdef str icao(str msg)
|
||||||
|
cpdef bint is_icao_assigned(str icao)
|
||||||
|
|
||||||
|
cpdef int typecode(str msg)
|
||||||
|
cpdef int cprNL(double lat)
|
||||||
|
cpdef str idcode(str msg)
|
||||||
|
cpdef int altcode(str msg)
|
||||||
|
|
||||||
|
cdef str data(str msg)
|
||||||
|
cpdef bint allzeros(str msg)
|
||||||
431
pyModeS/decoder/c_common.pyx
Normal file
431
pyModeS/decoder/c_common.pyx
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
# cython: language_level=3
|
||||||
|
|
||||||
|
cimport cython
|
||||||
|
from cpython cimport array
|
||||||
|
from cpython.bytes cimport PyBytes_GET_SIZE
|
||||||
|
from cpython.bytearray cimport PyByteArray_GET_SIZE
|
||||||
|
|
||||||
|
from libc.math cimport cos, acos, fabs, M_PI as pi, floor as c_floor
|
||||||
|
|
||||||
|
|
||||||
|
cdef int char_to_int(unsigned char binstr):
|
||||||
|
if 48 <= binstr <= 57: # 0 to 9
|
||||||
|
return binstr - 48
|
||||||
|
if 97 <= binstr <= 102: # a to f
|
||||||
|
return binstr - 97 + 10
|
||||||
|
if 65 <= binstr <= 70: # A to F
|
||||||
|
return binstr - 65 + 10
|
||||||
|
return 0
|
||||||
|
|
||||||
|
cdef unsigned char int_to_char(unsigned char i):
|
||||||
|
if i < 10:
|
||||||
|
return 48 + i # "0" + i
|
||||||
|
return 97 - 10 + i # "a" - 10 + i
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
@cython.overflowcheck(False)
|
||||||
|
cpdef str hex2bin(str hexstr):
|
||||||
|
"""Convert a hexdecimal string to binary string, with zero fillings."""
|
||||||
|
# num_of_bits = len(hexstr) * 4
|
||||||
|
cdef hexbytes = bytes(hexstr.encode())
|
||||||
|
cdef Py_ssize_t len_hexstr = PyBytes_GET_SIZE(hexbytes)
|
||||||
|
# binstr = bin(int(hexbytes, 16))[2:].zfill(int(num_of_bits))
|
||||||
|
cdef bytearray _binstr = bytearray(4 * len_hexstr)
|
||||||
|
cdef unsigned char[:] binstr = _binstr
|
||||||
|
cdef unsigned char int_
|
||||||
|
cdef Py_ssize_t i
|
||||||
|
for i in range(len_hexstr):
|
||||||
|
int_ = char_to_int(hexbytes[i])
|
||||||
|
binstr[4*i] = int_to_char((int_ >> 3) & 1)
|
||||||
|
binstr[4*i+1] = int_to_char((int_ >> 2) & 1)
|
||||||
|
binstr[4*i+2] = int_to_char((int_ >> 1) & 1)
|
||||||
|
binstr[4*i+3] = int_to_char((int_) & 1)
|
||||||
|
return _binstr.decode()
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
cpdef long bin2int(str binstr):
|
||||||
|
"""Convert a binary string to integer."""
|
||||||
|
# return int(binstr, 2)
|
||||||
|
cdef bytearray binbytes = bytearray(binstr.encode())
|
||||||
|
cdef Py_ssize_t len_ = PyByteArray_GET_SIZE(binbytes)
|
||||||
|
cdef long cumul = 0
|
||||||
|
cdef unsigned char[:] v_binstr = binbytes
|
||||||
|
for i in range(len_):
|
||||||
|
cumul = 2*cumul + char_to_int(v_binstr[i])
|
||||||
|
return cumul
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
cpdef long hex2int(str hexstr):
|
||||||
|
"""Convert a binary string to integer."""
|
||||||
|
# return int(hexstr, 2)
|
||||||
|
cdef bytearray binbytes = bytearray(hexstr.encode())
|
||||||
|
cdef Py_ssize_t len_ = PyByteArray_GET_SIZE(binbytes)
|
||||||
|
cdef long cumul = 0
|
||||||
|
cdef unsigned char[:] v_hexstr = binbytes
|
||||||
|
for i in range(len_):
|
||||||
|
cumul = 16*cumul + char_to_int(v_hexstr[i])
|
||||||
|
return cumul
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
cpdef unsigned char df(str msg):
|
||||||
|
"""Decode Downlink Format vaule, bits 1 to 5."""
|
||||||
|
cdef str dfbin = hex2bin(msg[:2])
|
||||||
|
# return min(bin2int(dfbin[0:5]), 24)
|
||||||
|
cdef long df = bin2int(dfbin[0:5])
|
||||||
|
if df > 24:
|
||||||
|
return 24
|
||||||
|
return df
|
||||||
|
|
||||||
|
# the CRC generator
|
||||||
|
# G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)]
|
||||||
|
cdef array.array _G = array.array('l', [0b11111111, 0b11111010, 0b00000100, 0b10000000])
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
@cython.wraparound(False)
|
||||||
|
cpdef long crc(str msg, bint encode=False):
|
||||||
|
"""Mode-S Cyclic Redundancy Check.
|
||||||
|
|
||||||
|
Detect if bit error occurs in the Mode-S message. When encode option is on,
|
||||||
|
the checksum is generated.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
encode (bool): True to encode the date only and return the checksum
|
||||||
|
Returns:
|
||||||
|
int: message checksum, or partity bits (encoder)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# the CRC generator
|
||||||
|
# G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)]
|
||||||
|
# cdef array.array _G = array.array('l', [0b11111111, 0b11111010, 0b00000100, 0b10000000])
|
||||||
|
cdef long[4] G = _G
|
||||||
|
|
||||||
|
# msgbin_split = wrap(msgbin, 8)
|
||||||
|
# mbytes = list(map(bin2int, msgbin_split))
|
||||||
|
cdef bytearray _msgbin = bytearray(hex2bin(msg).encode())
|
||||||
|
cdef unsigned char[:] msgbin = _msgbin
|
||||||
|
|
||||||
|
cdef Py_ssize_t len_msgbin = PyByteArray_GET_SIZE(_msgbin)
|
||||||
|
cdef Py_ssize_t len_mbytes = len_msgbin // 8
|
||||||
|
cdef Py_ssize_t i
|
||||||
|
|
||||||
|
if encode:
|
||||||
|
for i in range(len_msgbin - 24, len_msgbin):
|
||||||
|
msgbin[i] = 0
|
||||||
|
|
||||||
|
cdef array.array _mbytes = array.array(
|
||||||
|
'l', [bin2int(_msgbin[8*i:8*i+8].decode()) for i in range(len_mbytes)]
|
||||||
|
)
|
||||||
|
|
||||||
|
cdef long[:] mbytes = _mbytes
|
||||||
|
|
||||||
|
cdef long bits, mask
|
||||||
|
cdef Py_ssize_t ibyte, ibit
|
||||||
|
|
||||||
|
for ibyte in range(len_mbytes - 3):
|
||||||
|
for ibit in range(8):
|
||||||
|
mask = 0x80 >> ibit
|
||||||
|
bits = mbytes[ibyte] & mask
|
||||||
|
|
||||||
|
if bits > 0:
|
||||||
|
mbytes[ibyte] = mbytes[ibyte] ^ (G[0] >> ibit)
|
||||||
|
mbytes[ibyte + 1] = mbytes[ibyte + 1] ^ (
|
||||||
|
0xFF & ((G[0] << 8 - ibit) | (G[1] >> ibit))
|
||||||
|
)
|
||||||
|
mbytes[ibyte + 2] = mbytes[ibyte + 2] ^ (
|
||||||
|
0xFF & ((G[1] << 8 - ibit) | (G[2] >> ibit))
|
||||||
|
)
|
||||||
|
mbytes[ibyte + 3] = mbytes[ibyte + 3] ^ (
|
||||||
|
0xFF & ((G[2] << 8 - ibit) | (G[3] >> ibit))
|
||||||
|
)
|
||||||
|
|
||||||
|
cdef long result = (mbytes[len_mbytes-3] << 16) | (mbytes[len_mbytes-2] << 8) | mbytes[len_mbytes-1]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cpdef long floor(double x):
|
||||||
|
"""Mode-S floor function.
|
||||||
|
|
||||||
|
Defined as the greatest integer value k, such that k <= x
|
||||||
|
For example: floor(3.6) = 3 and floor(-3.6) = -4
|
||||||
|
|
||||||
|
"""
|
||||||
|
return <long> c_floor(x)
|
||||||
|
|
||||||
|
cpdef str icao(str msg):
|
||||||
|
"""Calculate the ICAO address from an Mode-S message.
|
||||||
|
|
||||||
|
Applicable only with DF4, DF5, DF20, DF21 messages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (String): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String: ICAO address in 6 bytes hexadecimal string
|
||||||
|
|
||||||
|
"""
|
||||||
|
cdef unsigned char DF = df(msg)
|
||||||
|
cdef long c0, c1
|
||||||
|
|
||||||
|
if DF in (11, 17, 18):
|
||||||
|
addr = msg[2:8]
|
||||||
|
elif DF in (0, 4, 5, 16, 20, 21):
|
||||||
|
c0 = crc(msg, encode=True)
|
||||||
|
c1 = hex2int(msg[-6:])
|
||||||
|
addr = "%06X" % (c0 ^ c1)
|
||||||
|
else:
|
||||||
|
addr = None
|
||||||
|
|
||||||
|
return addr
|
||||||
|
|
||||||
|
|
||||||
|
cpdef bint is_icao_assigned(str 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
|
||||||
|
|
||||||
|
cdef long 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
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
@cython.wraparound(False)
|
||||||
|
cpdef int typecode(str 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 -1
|
||||||
|
# return None
|
||||||
|
|
||||||
|
cdef str tcbin = hex2bin(msg[8:10])
|
||||||
|
return bin2int(tcbin[0:5])
|
||||||
|
|
||||||
|
@cython.cdivision(True)
|
||||||
|
cpdef int cprNL(double 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
|
||||||
|
|
||||||
|
cdef int nz = 15
|
||||||
|
cdef double a = 1 - cos(pi / (2 * nz))
|
||||||
|
cdef double b = cos(pi / 180.0 * fabs(lat)) ** 2
|
||||||
|
cdef double nl = 2 * pi / (acos(1 - a / b))
|
||||||
|
NL = floor(nl)
|
||||||
|
return NL
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
@cython.wraparound(False)
|
||||||
|
cpdef str idcode(str msg):
|
||||||
|
"""Compute identity (squawk code).
|
||||||
|
|
||||||
|
Applicable only for DF5 or DF21 messages, 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.")
|
||||||
|
|
||||||
|
cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
|
||||||
|
cdef unsigned char[:] mbin = _mbin
|
||||||
|
|
||||||
|
cdef bytearray _idcode = bytearray(4)
|
||||||
|
cdef unsigned char[:] idcode = _idcode
|
||||||
|
|
||||||
|
cdef unsigned char C1 = mbin[19]
|
||||||
|
cdef unsigned char A1 = mbin[20]
|
||||||
|
cdef unsigned char C2 = mbin[21]
|
||||||
|
cdef unsigned char A2 = mbin[22]
|
||||||
|
cdef unsigned char C4 = mbin[23]
|
||||||
|
cdef unsigned char A4 = mbin[24]
|
||||||
|
# _ = mbin[25]
|
||||||
|
cdef unsigned char B1 = mbin[26]
|
||||||
|
cdef unsigned char D1 = mbin[27]
|
||||||
|
cdef unsigned char B2 = mbin[28]
|
||||||
|
cdef unsigned char D2 = mbin[29]
|
||||||
|
cdef unsigned char B4 = mbin[30]
|
||||||
|
cdef unsigned char 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)
|
||||||
|
|
||||||
|
idcode[0] = int_to_char((char_to_int(A4)*2 + char_to_int(A2))*2 + char_to_int(A1))
|
||||||
|
idcode[1] = int_to_char((char_to_int(B4)*2 + char_to_int(B2))*2 + char_to_int(B1))
|
||||||
|
idcode[2] = int_to_char((char_to_int(C4)*2 + char_to_int(C2))*2 + char_to_int(C1))
|
||||||
|
idcode[3] = int_to_char((char_to_int(D4)*2 + char_to_int(D2))*2 + char_to_int(D1))
|
||||||
|
|
||||||
|
return _idcode.decode()
|
||||||
|
|
||||||
|
#return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
||||||
|
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
@cython.wraparound(False)
|
||||||
|
cpdef int altcode(str msg):
|
||||||
|
"""Compute the altitude.
|
||||||
|
|
||||||
|
Applicable only for 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 [0, 4, 16, 20]:
|
||||||
|
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
||||||
|
|
||||||
|
# Altitude code, bit 20-32
|
||||||
|
cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
|
||||||
|
cdef unsigned char[:] mbin = _mbin
|
||||||
|
|
||||||
|
cdef char mbit = mbin[25] # M bit: 26
|
||||||
|
cdef char qbit = mbin[27] # Q bit: 28
|
||||||
|
cdef int alt = 0
|
||||||
|
cdef bytearray vbin
|
||||||
|
cdef bytearray _graybytes = bytearray(11)
|
||||||
|
cdef unsigned char[:] graybytes = _graybytes
|
||||||
|
|
||||||
|
if mbit == 48: # unit in ft, "0" -> 48
|
||||||
|
if qbit == 49: # 25ft interval, "1" -> 49
|
||||||
|
vbin = _mbin[19:25] + _mbin[26:27] + _mbin[28:32]
|
||||||
|
alt = bin2int(vbin.decode()) * 25 - 1000
|
||||||
|
if qbit == 48: # 100ft interval, above 50175ft, "0" -> 48
|
||||||
|
graybytes[8] = mbin[19]
|
||||||
|
graybytes[2] = mbin[20]
|
||||||
|
graybytes[9] = mbin[21]
|
||||||
|
graybytes[3] = mbin[22]
|
||||||
|
graybytes[10] = mbin[23]
|
||||||
|
graybytes[4] = mbin[24]
|
||||||
|
# _ = mbin[25]
|
||||||
|
graybytes[5] = mbin[26]
|
||||||
|
# cdef char D1 = mbin[27] # always zero
|
||||||
|
graybytes[6] = mbin[28]
|
||||||
|
graybytes[0] = mbin[29]
|
||||||
|
graybytes[7] = mbin[30]
|
||||||
|
graybytes[1] = mbin[31]
|
||||||
|
# graybytes = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
||||||
|
|
||||||
|
alt = gray2alt(_graybytes.decode())
|
||||||
|
|
||||||
|
if mbit == 49: # unit in meter, "1" -> 49
|
||||||
|
vbin = _mbin[19:25] + _mbin[26:31]
|
||||||
|
alt = int(bin2int(vbin.decode()) * 3.28084) # convert to ft
|
||||||
|
|
||||||
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cpdef int gray2alt(str codestr):
|
||||||
|
cdef str gc500 = codestr[:8]
|
||||||
|
cdef int n500 = gray2int(gc500)
|
||||||
|
|
||||||
|
# in 100-ft step must be converted first
|
||||||
|
cdef str gc100 = codestr[8:]
|
||||||
|
cdef int n100 = gray2int(gc100)
|
||||||
|
|
||||||
|
if n100 in [0, 5, 6]:
|
||||||
|
return -1
|
||||||
|
#return None
|
||||||
|
|
||||||
|
if n100 == 7:
|
||||||
|
n100 = 5
|
||||||
|
|
||||||
|
if n500 % 2:
|
||||||
|
n100 = 6 - n100
|
||||||
|
|
||||||
|
alt = (n500 * 500 + n100 * 100) - 1300
|
||||||
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
cdef int gray2int(str graystr):
|
||||||
|
"""Convert greycode to binary."""
|
||||||
|
cdef int num = bin2int(graystr)
|
||||||
|
num ^= num >> 8
|
||||||
|
num ^= num >> 4
|
||||||
|
num ^= num >> 2
|
||||||
|
num ^= num >> 1
|
||||||
|
return num
|
||||||
|
|
||||||
|
|
||||||
|
cdef str data(str msg):
|
||||||
|
"""Return the data frame in the message, bytes 9 to 22."""
|
||||||
|
return msg[8:-6]
|
||||||
|
|
||||||
|
|
||||||
|
cpdef bint allzeros(str 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
|
||||||
@@ -2,6 +2,7 @@ from __future__ import absolute_import, print_function, division
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from textwrap import wrap
|
from textwrap import wrap
|
||||||
|
|
||||||
|
|
||||||
def hex2bin(hexstr):
|
def hex2bin(hexstr):
|
||||||
"""Convert a hexdecimal string to binary string, with zero fillings."""
|
"""Convert a hexdecimal string to binary string, with zero fillings."""
|
||||||
num_of_bits = len(hexstr) * 4
|
num_of_bits = len(hexstr) * 4
|
||||||
@@ -14,35 +15,15 @@ def hex2int(hexstr):
|
|||||||
return int(hexstr, 16)
|
return int(hexstr, 16)
|
||||||
|
|
||||||
|
|
||||||
def int2hex(n):
|
|
||||||
"""Convert a integer to hexadecimal string."""
|
|
||||||
return hex(n)[2:].rjust(6, '0').upper()
|
|
||||||
|
|
||||||
|
|
||||||
def bin2int(binstr):
|
def bin2int(binstr):
|
||||||
"""Convert a binary string to integer."""
|
"""Convert a binary string to integer."""
|
||||||
return int(binstr, 2)
|
return int(binstr, 2)
|
||||||
|
|
||||||
|
|
||||||
def bin2hex(hexstr):
|
|
||||||
"""Convert a hexdecimal string to integer."""
|
|
||||||
return int2hex(bin2int(hexstr))
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
def df(msg):
|
||||||
"""Decode Downlink Format vaule, bits 1 to 5."""
|
"""Decode Downlink Format vaule, bits 1 to 5."""
|
||||||
msgbin = hex2bin(msg)
|
dfbin = hex2bin(msg[:2])
|
||||||
return min(bin2int(msgbin[0:5]), 24)
|
return min(bin2int(dfbin[0:5]), 24)
|
||||||
|
|
||||||
|
|
||||||
def crc(msg, encode=False):
|
def crc(msg, encode=False):
|
||||||
@@ -59,10 +40,7 @@ def crc(msg, encode=False):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# the CRC generator
|
# the CRC generator
|
||||||
G = [
|
G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)]
|
||||||
int("11111111", 2), int("11111010", 2),
|
|
||||||
int("00000100", 2), int("10000000", 2)
|
|
||||||
]
|
|
||||||
|
|
||||||
if encode:
|
if encode:
|
||||||
msg = msg[:-6] + "000000"
|
msg = msg[:-6] + "000000"
|
||||||
@@ -71,16 +49,22 @@ def crc(msg, encode=False):
|
|||||||
msgbin_split = wrap(msgbin, 8)
|
msgbin_split = wrap(msgbin, 8)
|
||||||
mbytes = list(map(bin2int, msgbin_split))
|
mbytes = list(map(bin2int, msgbin_split))
|
||||||
|
|
||||||
for ibyte in range(len(mbytes)-3):
|
for ibyte in range(len(mbytes) - 3):
|
||||||
for ibit in range(8):
|
for ibit in range(8):
|
||||||
mask = 0x80 >> ibit
|
mask = 0x80 >> ibit
|
||||||
bits = mbytes[ibyte] & mask
|
bits = mbytes[ibyte] & mask
|
||||||
|
|
||||||
if bits > 0:
|
if bits > 0:
|
||||||
mbytes[ibyte] = mbytes[ibyte] ^ (G[0] >> ibit)
|
mbytes[ibyte] = mbytes[ibyte] ^ (G[0] >> ibit)
|
||||||
mbytes[ibyte+1] = mbytes[ibyte+1] ^ (0xFF & ((G[0] << 8-ibit) | (G[1] >> ibit)))
|
mbytes[ibyte + 1] = mbytes[ibyte + 1] ^ (
|
||||||
mbytes[ibyte+2] = mbytes[ibyte+2] ^ (0xFF & ((G[1] << 8-ibit) | (G[2] >> ibit)))
|
0xFF & ((G[0] << 8 - ibit) | (G[1] >> ibit))
|
||||||
mbytes[ibyte+3] = mbytes[ibyte+3] ^ (0xFF & ((G[2] << 8-ibit) | (G[3] >> ibit)))
|
)
|
||||||
|
mbytes[ibyte + 2] = mbytes[ibyte + 2] ^ (
|
||||||
|
0xFF & ((G[1] << 8 - ibit) | (G[2] >> ibit))
|
||||||
|
)
|
||||||
|
mbytes[ibyte + 3] = mbytes[ibyte + 3] ^ (
|
||||||
|
0xFF & ((G[2] << 8 - ibit) | (G[3] >> ibit))
|
||||||
|
)
|
||||||
|
|
||||||
result = (mbytes[-3] << 16) | (mbytes[-2] << 8) | mbytes[-1]
|
result = (mbytes[-3] << 16) | (mbytes[-2] << 8) | mbytes[-1]
|
||||||
|
|
||||||
@@ -90,24 +74,28 @@ def crc(msg, encode=False):
|
|||||||
def crc_legacy(msg, encode=False):
|
def crc_legacy(msg, encode=False):
|
||||||
"""Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow)."""
|
"""Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow)."""
|
||||||
# the polynominal generattor code for CRC [1111111111111010000001001]
|
# 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])
|
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)
|
ng = len(generator)
|
||||||
|
|
||||||
msgnpbin = bin2np(hex2bin(msg))
|
msgnpbin = np.array([int(i) for i in hex2bin(msg)])
|
||||||
|
|
||||||
if encode:
|
if encode:
|
||||||
msgnpbin[-24:] = [0] * 24
|
msgnpbin[-24:] = [0] * 24
|
||||||
|
|
||||||
# loop all bits, except last 24 piraty bits
|
# loop all bits, except last 24 piraty bits
|
||||||
for i in range(len(msgnpbin)-24):
|
for i in range(len(msgnpbin) - 24):
|
||||||
if msgnpbin[i] == 0:
|
if msgnpbin[i] == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# perform XOR, when 1
|
# perform XOR, when 1
|
||||||
msgnpbin[i:i+ng] = np.bitwise_xor(msgnpbin[i:i+ng], generator)
|
msgnpbin[i : i + ng] = np.bitwise_xor(msgnpbin[i : i + ng], generator)
|
||||||
|
|
||||||
# last 24 bits
|
# last 24 bits
|
||||||
reminder = bin2int(np2bin(msgnpbin[-24:]))
|
msgbin = np.array2string(msgnpbin[-24:], separator="")[1:-1]
|
||||||
|
reminder = bin2int(msgbin)
|
||||||
|
|
||||||
return reminder
|
return reminder
|
||||||
|
|
||||||
|
|
||||||
@@ -139,8 +127,8 @@ def icao(msg):
|
|||||||
addr = msg[2:8]
|
addr = msg[2:8]
|
||||||
elif DF in (0, 4, 5, 16, 20, 21):
|
elif DF in (0, 4, 5, 16, 20, 21):
|
||||||
c0 = crc(msg, encode=True)
|
c0 = crc(msg, encode=True)
|
||||||
c1 = hex2int(msg[-6:])
|
c1 = int(msg[-6:], 16)
|
||||||
addr = '%06X' % (c0 ^ c1)
|
addr = "%06X" % (c0 ^ c1)
|
||||||
else:
|
else:
|
||||||
addr = None
|
addr = None
|
||||||
|
|
||||||
@@ -149,23 +137,33 @@ def icao(msg):
|
|||||||
|
|
||||||
def is_icao_assigned(icao):
|
def is_icao_assigned(icao):
|
||||||
"""Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
|
"""Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
|
||||||
if (icao is None) or (not isinstance(icao, str)) or (len(icao)!=6):
|
if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
icaoint = hex2int(icao)
|
icaoint = int(icao, 16)
|
||||||
|
|
||||||
if 0x200000 < icaoint < 0x27FFFF: return False # AFI
|
if 0x200000 < icaoint < 0x27FFFF:
|
||||||
if 0x280000 < icaoint < 0x28FFFF: return False # SAM
|
return False # AFI
|
||||||
if 0x500000 < icaoint < 0x5FFFFF: return False # EUR, NAT
|
if 0x280000 < icaoint < 0x28FFFF:
|
||||||
if 0x600000 < icaoint < 0x67FFFF: return False # MID
|
return False # SAM
|
||||||
if 0x680000 < icaoint < 0x6F0000: return False # ASIA
|
if 0x500000 < icaoint < 0x5FFFFF:
|
||||||
if 0x900000 < icaoint < 0x9FFFFF: return False # NAM, PAC
|
return False # EUR, NAT
|
||||||
if 0xB00000 < icaoint < 0xBFFFFF: return False # CAR
|
if 0x600000 < icaoint < 0x67FFFF:
|
||||||
if 0xD00000 < icaoint < 0xDFFFFF: return False # future
|
return False # MID
|
||||||
if 0xF00000 < icaoint < 0xFFFFFF: return False # future
|
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
def typecode(msg):
|
def typecode(msg):
|
||||||
"""Type code of ADS-B message
|
"""Type code of ADS-B message
|
||||||
|
|
||||||
@@ -178,8 +176,8 @@ def typecode(msg):
|
|||||||
if df(msg) not in (17, 18):
|
if df(msg) not in (17, 18):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
msgbin = hex2bin(msg)
|
tcbin = hex2bin(msg[8:10])
|
||||||
return bin2int(msgbin[32:37])
|
return bin2int(tcbin[0:5])
|
||||||
|
|
||||||
|
|
||||||
def cprNL(lat):
|
def cprNL(lat):
|
||||||
@@ -197,7 +195,7 @@ def cprNL(lat):
|
|||||||
nz = 15
|
nz = 15
|
||||||
a = 1 - np.cos(np.pi / (2 * nz))
|
a = 1 - np.cos(np.pi / (2 * nz))
|
||||||
b = np.cos(np.pi / 180.0 * abs(lat)) ** 2
|
b = np.cos(np.pi / 180.0 * abs(lat)) ** 2
|
||||||
nl = 2 * np.pi / (np.arccos(1 - a/b))
|
nl = 2 * np.pi / (np.arccos(1 - a / b))
|
||||||
NL = floor(nl)
|
NL = floor(nl)
|
||||||
return NL
|
return NL
|
||||||
|
|
||||||
@@ -234,10 +232,10 @@ def idcode(msg):
|
|||||||
B4 = mbin[30]
|
B4 = mbin[30]
|
||||||
D4 = mbin[31]
|
D4 = mbin[31]
|
||||||
|
|
||||||
byte1 = int(A4+A2+A1, 2)
|
byte1 = int(A4 + A2 + A1, 2)
|
||||||
byte2 = int(B4+B2+B1, 2)
|
byte2 = int(B4 + B2 + B1, 2)
|
||||||
byte3 = int(C4+C2+C1, 2)
|
byte3 = int(C4 + C2 + C1, 2)
|
||||||
byte4 = int(D4+D2+D1, 2)
|
byte4 = int(D4 + D2 + D1, 2)
|
||||||
|
|
||||||
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
||||||
|
|
||||||
@@ -261,15 +259,14 @@ def altcode(msg):
|
|||||||
# Altitude code, bit 20-32
|
# Altitude code, bit 20-32
|
||||||
mbin = hex2bin(msg)
|
mbin = hex2bin(msg)
|
||||||
|
|
||||||
mbit = mbin[25] # M bit: 26
|
mbit = mbin[25] # M bit: 26
|
||||||
qbit = mbin[27] # Q bit: 28
|
qbit = mbin[27] # Q bit: 28
|
||||||
|
|
||||||
|
if mbit == "0": # unit in ft
|
||||||
if mbit == '0': # unit in ft
|
if qbit == "1": # 25ft interval
|
||||||
if qbit == '1': # 25ft interval
|
|
||||||
vbin = mbin[19:25] + mbin[26] + mbin[28:32]
|
vbin = mbin[19:25] + mbin[26] + mbin[28:32]
|
||||||
alt = bin2int(vbin) * 25 - 1000
|
alt = bin2int(vbin) * 25 - 1000
|
||||||
if qbit == '0': # 100ft interval, above 50175ft
|
if qbit == "0": # 100ft interval, above 50175ft
|
||||||
C1 = mbin[19]
|
C1 = mbin[19]
|
||||||
A1 = mbin[20]
|
A1 = mbin[20]
|
||||||
C2 = mbin[21]
|
C2 = mbin[21]
|
||||||
@@ -284,10 +281,10 @@ def altcode(msg):
|
|||||||
B4 = mbin[30]
|
B4 = mbin[30]
|
||||||
D4 = mbin[31]
|
D4 = mbin[31]
|
||||||
|
|
||||||
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
||||||
alt = gray2alt(graystr)
|
alt = gray2alt(graystr)
|
||||||
|
|
||||||
if mbit == '1': # unit in meter
|
if mbit == "1": # unit in meter
|
||||||
vbin = mbin[19:25] + mbin[26:31]
|
vbin = mbin[19:25] + mbin[26:31]
|
||||||
alt = int(bin2int(vbin) * 3.28084) # convert to ft
|
alt = int(bin2int(vbin) * 3.28084) # convert to ft
|
||||||
|
|
||||||
@@ -308,20 +305,20 @@ def gray2alt(codestr):
|
|||||||
if n100 == 7:
|
if n100 == 7:
|
||||||
n100 = 5
|
n100 = 5
|
||||||
|
|
||||||
if n500%2:
|
if n500 % 2:
|
||||||
n100 = 6 - n100
|
n100 = 6 - n100
|
||||||
|
|
||||||
alt = (n500*500 + n100*100) - 1300
|
alt = (n500 * 500 + n100 * 100) - 1300
|
||||||
return alt
|
return alt
|
||||||
|
|
||||||
|
|
||||||
def gray2int(graystr):
|
def gray2int(graystr):
|
||||||
"""Convert greycode to binary."""
|
"""Convert greycode to binary."""
|
||||||
num = bin2int(graystr)
|
num = bin2int(graystr)
|
||||||
num ^= (num >> 8)
|
num ^= num >> 8
|
||||||
num ^= (num >> 4)
|
num ^= num >> 4
|
||||||
num ^= (num >> 2)
|
num ^= num >> 2
|
||||||
num ^= (num >> 1)
|
num ^= num >> 1
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
@@ -355,8 +352,8 @@ def wrongstatus(data, sb, msb, lsb):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# status bit, most significant bit, least significant bit
|
# status bit, most significant bit, least significant bit
|
||||||
status = int(data[sb-1])
|
status = int(data[sb - 1])
|
||||||
value = bin2int(data[msb-1:lsb])
|
value = bin2int(data[msb - 1 : lsb])
|
||||||
|
|
||||||
if not status:
|
if not status:
|
||||||
if value != 0:
|
if value != 0:
|
||||||
|
|||||||
@@ -17,13 +17,20 @@ from pyModeS.decoder.bds.bds50 import *
|
|||||||
from pyModeS.decoder.bds.bds60 import *
|
from pyModeS.decoder.bds.bds60 import *
|
||||||
from pyModeS.decoder.bds import infer
|
from pyModeS.decoder.bds import infer
|
||||||
|
|
||||||
warnings.simplefilter('once', DeprecationWarning)
|
warnings.simplefilter("once", DeprecationWarning)
|
||||||
warnings.warn("pms.ehs module is deprecated. Please use pms.commb instead.", DeprecationWarning)
|
warnings.warn(
|
||||||
|
"pms.ehs module is deprecated. Please use pms.commb instead.", DeprecationWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def BDS(msg):
|
def BDS(msg):
|
||||||
warnings.warn("pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.", DeprecationWarning)
|
warnings.warn(
|
||||||
|
"pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.", DeprecationWarning
|
||||||
|
)
|
||||||
return infer(msg)
|
return infer(msg)
|
||||||
|
|
||||||
|
|
||||||
def icao(msg):
|
def icao(msg):
|
||||||
from pyModeS.decoder.common import icao
|
from pyModeS.decoder.common import icao
|
||||||
|
|
||||||
return icao(msg)
|
return icao(msg)
|
||||||
|
|||||||
@@ -18,5 +18,8 @@ from pyModeS.decoder.bds.bds20 import *
|
|||||||
from pyModeS.decoder.bds.bds30 import *
|
from pyModeS.decoder.bds.bds30 import *
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
warnings.simplefilter('once', DeprecationWarning)
|
|
||||||
warnings.warn("pms.els module is deprecated. Please use pms.commb instead.", DeprecationWarning)
|
warnings.simplefilter("once", DeprecationWarning)
|
||||||
|
warnings.warn(
|
||||||
|
"pms.els module is deprecated. Please use pms.commb instead.", DeprecationWarning
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Warpper for short roll call surveillance replies DF=4/5
|
Warpper for short roll call surveillance replies DF=4/5
|
||||||
|
|
||||||
|
|||||||
@@ -6,120 +6,147 @@ See source code at: https://github.com/junzis/pyModeS/blob/master/pyModeS/decode
|
|||||||
NA = None
|
NA = None
|
||||||
|
|
||||||
TC_NUCp_lookup = {
|
TC_NUCp_lookup = {
|
||||||
0:0, 5:9, 6:8, 7:7, 8:6,
|
0: 0,
|
||||||
9:9, 10:8, 11:7, 12:6, 13:5, 14:4, 15:3, 16:2, 17:1, 18:0,
|
5: 9,
|
||||||
20:9, 21:8, 22:0
|
6: 8,
|
||||||
|
7: 7,
|
||||||
|
8: 6,
|
||||||
|
9: 9,
|
||||||
|
10: 8,
|
||||||
|
11: 7,
|
||||||
|
12: 6,
|
||||||
|
13: 5,
|
||||||
|
14: 4,
|
||||||
|
15: 3,
|
||||||
|
16: 2,
|
||||||
|
17: 1,
|
||||||
|
18: 0,
|
||||||
|
20: 9,
|
||||||
|
21: 8,
|
||||||
|
22: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
TC_NICv1_lookup = {
|
TC_NICv1_lookup = {
|
||||||
5:11, 6:10, 7:9, 8:0,
|
5: 11,
|
||||||
9:11, 10:10, 11:{1:9, 0:8}, 12:7, 13:6, 14:5, 15:4, 16:{1:3, 0:2}, 17:1, 18:0,
|
6: 10,
|
||||||
20:11, 21:10, 22:0
|
7: 9,
|
||||||
|
8: 0,
|
||||||
|
9: 11,
|
||||||
|
10: 10,
|
||||||
|
11: {1: 9, 0: 8},
|
||||||
|
12: 7,
|
||||||
|
13: 6,
|
||||||
|
14: 5,
|
||||||
|
15: 4,
|
||||||
|
16: {1: 3, 0: 2},
|
||||||
|
17: 1,
|
||||||
|
18: 0,
|
||||||
|
20: 11,
|
||||||
|
21: 10,
|
||||||
|
22: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
TC_NICv2_lookup = {
|
TC_NICv2_lookup = {
|
||||||
5:11, 6:10, 7:{2:9, 0:8}, 8:{3:7, 2:6, 1:6, 0:0},
|
5: 11,
|
||||||
9:11, 10:10, 11:{3:9, 0:8}, 12:7, 13:6, 14:5, 15:4, 16:{3:3, 0:2}, 17:1, 18:0,
|
6: 10,
|
||||||
20:11, 21:10, 22:0
|
7: {2: 9, 0: 8},
|
||||||
|
8: {3: 7, 2: 6, 1: 6, 0: 0},
|
||||||
|
9: 11,
|
||||||
|
10: 10,
|
||||||
|
11: {3: 9, 0: 8},
|
||||||
|
12: 7,
|
||||||
|
13: 6,
|
||||||
|
14: 5,
|
||||||
|
15: 4,
|
||||||
|
16: {3: 3, 0: 2},
|
||||||
|
17: 1,
|
||||||
|
18: 0,
|
||||||
|
20: 11,
|
||||||
|
21: 10,
|
||||||
|
22: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NUCp = {
|
NUCp = {
|
||||||
9: {'HPL':7.5, 'RCu':3, 'RCv':4},
|
9: {"HPL": 7.5, "RCu": 3, "RCv": 4},
|
||||||
8: {'HPL':25, 'RCu':10, 'RCv':15},
|
8: {"HPL": 25, "RCu": 10, "RCv": 15},
|
||||||
7: {'HPL':185, 'RCu':93, 'RCv':NA},
|
7: {"HPL": 185, "RCu": 93, "RCv": NA},
|
||||||
6: {'HPL':370, 'RCu':185, 'RCv':NA},
|
6: {"HPL": 370, "RCu": 185, "RCv": NA},
|
||||||
5: {'HPL':926, 'RCu':463, 'RCv':NA},
|
5: {"HPL": 926, "RCu": 463, "RCv": NA},
|
||||||
4: {'HPL':1852, 'RCu':926, 'RCv':NA},
|
4: {"HPL": 1852, "RCu": 926, "RCv": NA},
|
||||||
3: {'HPL':3704, 'RCu':1852, 'RCv':NA},
|
3: {"HPL": 3704, "RCu": 1852, "RCv": NA},
|
||||||
2: {'HPL':18520, 'RCu':9260, 'RCv':NA},
|
2: {"HPL": 18520, "RCu": 9260, "RCv": NA},
|
||||||
1: {'HPL':37040, 'RCu':18520, 'RCv':NA},
|
1: {"HPL": 37040, "RCu": 18520, "RCv": NA},
|
||||||
0: {'HPL':NA, 'RCu':NA, 'RCv':NA},
|
0: {"HPL": NA, "RCu": NA, "RCv": NA},
|
||||||
}
|
}
|
||||||
|
|
||||||
NUCv = {
|
NUCv = {
|
||||||
0: {'HVE':NA, 'VVE':NA},
|
0: {"HVE": NA, "VVE": NA},
|
||||||
1: {'HVE':10, 'VVE':15.2},
|
1: {"HVE": 10, "VVE": 15.2},
|
||||||
2: {'HVE':3, 'VVE':4.5},
|
2: {"HVE": 3, "VVE": 4.5},
|
||||||
3: {'HVE':1, 'VVE':1.5},
|
3: {"HVE": 1, "VVE": 1.5},
|
||||||
4: {'HVE':0.3, 'VVE':0.46},
|
4: {"HVE": 0.3, "VVE": 0.46},
|
||||||
}
|
}
|
||||||
|
|
||||||
NACp = {
|
NACp = {
|
||||||
11: {'EPU': 3, 'VEPU': 4},
|
11: {"EPU": 3, "VEPU": 4},
|
||||||
10: {'EPU': 10, 'VEPU': 15},
|
10: {"EPU": 10, "VEPU": 15},
|
||||||
9: {'EPU': 30, 'VEPU': 45},
|
9: {"EPU": 30, "VEPU": 45},
|
||||||
8: {'EPU': 93, 'VEPU': NA},
|
8: {"EPU": 93, "VEPU": NA},
|
||||||
7: {'EPU': 185, 'VEPU': NA},
|
7: {"EPU": 185, "VEPU": NA},
|
||||||
6: {'EPU': 556, 'VEPU': NA},
|
6: {"EPU": 556, "VEPU": NA},
|
||||||
5: {'EPU': 926, 'VEPU': NA},
|
5: {"EPU": 926, "VEPU": NA},
|
||||||
4: {'EPU': 1852, 'VEPU': NA},
|
4: {"EPU": 1852, "VEPU": NA},
|
||||||
3: {'EPU': 3704, 'VEPU': NA},
|
3: {"EPU": 3704, "VEPU": NA},
|
||||||
2: {'EPU': 7408, 'VEPU': NA},
|
2: {"EPU": 7408, "VEPU": NA},
|
||||||
1: {'EPU': 18520, 'VEPU': NA},
|
1: {"EPU": 18520, "VEPU": NA},
|
||||||
0: {'EPU': NA, 'VEPU': NA},
|
0: {"EPU": NA, "VEPU": NA},
|
||||||
}
|
}
|
||||||
|
|
||||||
NACv = {
|
NACv = {
|
||||||
0: {'HFOMr':NA, 'VFOMr':NA},
|
0: {"HFOMr": NA, "VFOMr": NA},
|
||||||
1: {'HFOMr':10, 'VFOMr':15.2},
|
1: {"HFOMr": 10, "VFOMr": 15.2},
|
||||||
2: {'HFOMr':3, 'VFOMr':4.5},
|
2: {"HFOMr": 3, "VFOMr": 4.5},
|
||||||
3: {'HFOMr':1, 'VFOMr':1.5},
|
3: {"HFOMr": 1, "VFOMr": 1.5},
|
||||||
4: {'HFOMr':0.3, 'VFOMr':0.46},
|
4: {"HFOMr": 0.3, "VFOMr": 0.46},
|
||||||
}
|
}
|
||||||
|
|
||||||
SIL = {
|
SIL = {
|
||||||
3: {'PE_RCu': 1e-7, 'PE_VPL': 2e-7},
|
3: {"PE_RCu": 1e-7, "PE_VPL": 2e-7},
|
||||||
2: {'PE_RCu': 1e-5, 'PE_VPL': 1e-5},
|
2: {"PE_RCu": 1e-5, "PE_VPL": 1e-5},
|
||||||
1: {'PE_RCu': 1e-3, 'PE_VPL': 1e-3},
|
1: {"PE_RCu": 1e-3, "PE_VPL": 1e-3},
|
||||||
0: {'PE_RCu': NA, 'PE_VPL': NA},
|
0: {"PE_RCu": NA, "PE_VPL": NA},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NICv1 = {
|
NICv1 = {
|
||||||
# NIC is used as the index at second Level
|
# NIC is used as the index at second Level
|
||||||
|
11: {0: {"Rc": 7.5, "VPL": 11}},
|
||||||
11: {0: {'Rc': 7.5, 'VPL': 11}},
|
10: {0: {"Rc": 25, "VPL": 37.5}},
|
||||||
10: {0: {'Rc': 25, 'VPL': 37.5}},
|
9: {1: {"Rc": 75, "VPL": 112}},
|
||||||
9: {1: {'Rc': 75, 'VPL': 112}},
|
8: {0: {"Rc": 185, "VPL": NA}},
|
||||||
8: {0: {'Rc': 185, 'VPL': NA}},
|
7: {0: {"Rc": 370, "VPL": NA}},
|
||||||
7: {0: {'Rc': 370, 'VPL': NA}},
|
6: {0: {"Rc": 926, "VPL": NA}, 1: {"Rc": 1111, "VPL": NA}},
|
||||||
6: {
|
5: {0: {"Rc": 1852, "VPL": NA}},
|
||||||
0: {'Rc': 926, 'VPL': NA},
|
4: {0: {"Rc": 3702, "VPL": NA}},
|
||||||
1: {'Rc': 1111, 'VPL': NA},
|
3: {1: {"Rc": 7408, "VPL": NA}},
|
||||||
},
|
2: {0: {"Rc": 14008, "VPL": NA}},
|
||||||
5: {0: {'Rc': 1852, 'VPL': NA}},
|
1: {0: {"Rc": 37000, "VPL": NA}},
|
||||||
4: {0: {'Rc': 3702, 'VPL': NA}},
|
0: {0: {"Rc": NA, "VPL": NA}},
|
||||||
3: {1: {'Rc': 7408, 'VPL': NA}},
|
|
||||||
2: {0: {'Rc': 14008, 'VPL': NA}},
|
|
||||||
1: {0: {'Rc': 37000, 'VPL': NA}},
|
|
||||||
0: {0: {'Rc': NA, 'VPL': NA}},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NICv2 = {
|
NICv2 = {
|
||||||
# Decimal value of [NICa NICb/NICc] is used as the index at second Level
|
# Decimal value of [NICa NICb/NICc] is used as the index at second Level
|
||||||
|
11: {0: {"Rc": 7.5}},
|
||||||
11: {0: {'Rc': 7.5}},
|
10: {0: {"Rc": 25}},
|
||||||
10: {0: {'Rc': 25}},
|
9: {2: {"Rc": 75}, 3: {"Rc": 75}},
|
||||||
9: {
|
8: {0: {"Rc": 185}},
|
||||||
2: {'Rc': 75},
|
7: {0: {"Rc": 370}, 3: {"Rc": 370}},
|
||||||
3: {'Rc': 75},
|
6: {0: {"Rc": 926}, 1: {"Rc": 556}, 2: {"Rc": 556}, 3: {"Rc": 1111}},
|
||||||
},
|
5: {0: {"Rc": 1852}},
|
||||||
8: {0: {'Rc': 185}},
|
4: {0: {"Rc": 3702}},
|
||||||
7: {
|
3: {3: {"Rc": 7408}},
|
||||||
0: {'Rc': 370},
|
2: {0: {"Rc": 14008}},
|
||||||
3: {'Rc': 370},
|
1: {0: {"Rc": 37000}},
|
||||||
},
|
0: {0: {"Rc": NA}},
|
||||||
6: {
|
|
||||||
0: {'Rc': 926},
|
|
||||||
1: {'Rc': 556},
|
|
||||||
2: {'Rc': 556},
|
|
||||||
3: {'Rc': 1111},
|
|
||||||
},
|
|
||||||
5: {0: {'Rc': 1852}},
|
|
||||||
4: {0: {'Rc': 3702}},
|
|
||||||
3: {3: {'Rc': 7408}},
|
|
||||||
2: {0: {'Rc': 14008}},
|
|
||||||
1: {0: {'Rc': 37000}},
|
|
||||||
0: {0: {'Rc': NA}},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,31 +30,31 @@ Speed conversion at altitude H[m] in ISA
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
"""Aero and geo Constants """
|
"""Aero and geo Constants """
|
||||||
kts = 0.514444 # knot -> m/s
|
kts = 0.514444 # knot -> m/s
|
||||||
ft = 0.3048 # ft -> m
|
ft = 0.3048 # ft -> m
|
||||||
fpm = 0.00508 # ft/min -> m/s
|
fpm = 0.00508 # ft/min -> m/s
|
||||||
inch = 0.0254 # inch -> m
|
inch = 0.0254 # inch -> m
|
||||||
sqft = 0.09290304 # 1 square foot
|
sqft = 0.09290304 # 1 square foot
|
||||||
nm = 1852. # nautical mile -> m
|
nm = 1852.0 # nautical mile -> m
|
||||||
lbs = 0.453592 # pound -> kg
|
lbs = 0.453592 # pound -> kg
|
||||||
g0 = 9.80665 # m/s2, Sea level gravity constant
|
g0 = 9.80665 # m/s2, Sea level gravity constant
|
||||||
R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA
|
R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA
|
||||||
p0 = 101325. # Pa, air pressure, sea level ISA
|
p0 = 101325.0 # Pa, air pressure, sea level ISA
|
||||||
rho0 = 1.225 # kg/m3, air density, sea level ISA
|
rho0 = 1.225 # kg/m3, air density, sea level ISA
|
||||||
T0 = 288.15 # K, temperature, sea level ISA
|
T0 = 288.15 # K, temperature, sea level ISA
|
||||||
gamma = 1.40 # cp/cv for air
|
gamma = 1.40 # cp/cv for air
|
||||||
gamma1 = 0.2 # (gamma-1)/2 for air
|
gamma1 = 0.2 # (gamma-1)/2 for air
|
||||||
gamma2 = 3.5 # gamma/(gamma-1) for air
|
gamma2 = 3.5 # gamma/(gamma-1) for air
|
||||||
beta = -0.0065 # [K/m] ISA temp gradient below tropopause
|
beta = -0.0065 # [K/m] ISA temp gradient below tropopause
|
||||||
r_earth = 6371000. # m, average earth radius
|
r_earth = 6371000.0 # m, average earth radius
|
||||||
a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0)
|
a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0)
|
||||||
|
|
||||||
|
|
||||||
def atmos(H):
|
def atmos(H):
|
||||||
# H in metres
|
# H in metres
|
||||||
T = np.maximum(288.15 - 0.0065 * H, 216.65)
|
T = np.maximum(288.15 - 0.0065 * H, 216.65)
|
||||||
rhotrop = 1.225 * (T / 288.15)**4.256848030018761
|
rhotrop = 1.225 * (T / 288.15) ** 4.256848030018761
|
||||||
dhstrat = np.maximum(0., H - 11000.0)
|
dhstrat = np.maximum(0.0, H - 11000.0)
|
||||||
rho = rhotrop * np.exp(-dhstrat / 6341.552161)
|
rho = rhotrop * np.exp(-dhstrat / 6341.552161)
|
||||||
p = rho * R * T
|
p = rho * R * T
|
||||||
return p, rho, T
|
return p, rho, T
|
||||||
@@ -101,11 +101,13 @@ def distance(lat1, lon1, lat2, lon2, H=0):
|
|||||||
theta1 = np.radians(lon1)
|
theta1 = np.radians(lon1)
|
||||||
theta2 = np.radians(lon2)
|
theta2 = np.radians(lon2)
|
||||||
|
|
||||||
cos = np.sin(phi1) * np.sin(phi2) * np.cos(theta1 - theta2) + np.cos(phi1) * np.cos(phi2)
|
cos = np.sin(phi1) * np.sin(phi2) * np.cos(theta1 - theta2) + np.cos(phi1) * np.cos(
|
||||||
cos = np.where(cos>1, 1, cos)
|
phi2
|
||||||
|
)
|
||||||
|
cos = np.where(cos > 1, 1, cos)
|
||||||
|
|
||||||
arc = np.arccos(cos)
|
arc = np.arccos(cos)
|
||||||
dist = arc * (r_earth + H) # meters, radius of earth
|
dist = arc * (r_earth + H) # meters, radius of earth
|
||||||
return dist
|
return dist
|
||||||
|
|
||||||
|
|
||||||
@@ -114,9 +116,8 @@ def bearing(lat1, lon1, lat2, lon2):
|
|||||||
lon1 = np.radians(lon1)
|
lon1 = np.radians(lon1)
|
||||||
lat2 = np.radians(lat2)
|
lat2 = np.radians(lat2)
|
||||||
lon2 = np.radians(lon2)
|
lon2 = np.radians(lon2)
|
||||||
x = np.sin(lon2-lon1) * np.cos(lat2)
|
x = np.sin(lon2 - lon1) * np.cos(lat2)
|
||||||
y = np.cos(lat1) * np.sin(lat2) \
|
y = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(lon2 - lon1)
|
||||||
- np.sin(lat1) * np.cos(lat2) * np.cos(lon2-lon1)
|
|
||||||
initial_bearing = np.arctan2(x, y)
|
initial_bearing = np.arctan2(x, y)
|
||||||
initial_bearing = np.degrees(initial_bearing)
|
initial_bearing = np.degrees(initial_bearing)
|
||||||
bearing = (initial_bearing + 360) % 360
|
bearing = (initial_bearing + 360) % 360
|
||||||
@@ -129,44 +130,44 @@ def bearing(lat1, lon1, lat2, lon2):
|
|||||||
def tas2mach(Vtas, H):
|
def tas2mach(Vtas, H):
|
||||||
"""True Airspeed to Mach number"""
|
"""True Airspeed to Mach number"""
|
||||||
a = vsound(H)
|
a = vsound(H)
|
||||||
Mach = Vtas/a
|
Mach = Vtas / a
|
||||||
return Mach
|
return Mach
|
||||||
|
|
||||||
|
|
||||||
def mach2tas(Mach, H):
|
def mach2tas(Mach, H):
|
||||||
"""Mach number to True Airspeed"""
|
"""Mach number to True Airspeed"""
|
||||||
a = vsound(H)
|
a = vsound(H)
|
||||||
Vtas = Mach*a
|
Vtas = Mach * a
|
||||||
return Vtas
|
return Vtas
|
||||||
|
|
||||||
|
|
||||||
def eas2tas(Veas, H):
|
def eas2tas(Veas, H):
|
||||||
"""Equivalent Airspeed to True Airspeed"""
|
"""Equivalent Airspeed to True Airspeed"""
|
||||||
rho = density(H)
|
rho = density(H)
|
||||||
Vtas = Veas * np.sqrt(rho0/rho)
|
Vtas = Veas * np.sqrt(rho0 / rho)
|
||||||
return Vtas
|
return Vtas
|
||||||
|
|
||||||
|
|
||||||
def tas2eas(Vtas, H):
|
def tas2eas(Vtas, H):
|
||||||
"""True Airspeed to Equivalent Airspeed"""
|
"""True Airspeed to Equivalent Airspeed"""
|
||||||
rho = density(H)
|
rho = density(H)
|
||||||
Veas = Vtas * np.sqrt(rho/rho0)
|
Veas = Vtas * np.sqrt(rho / rho0)
|
||||||
return Veas
|
return Veas
|
||||||
|
|
||||||
|
|
||||||
def cas2tas(Vcas, H):
|
def cas2tas(Vcas, H):
|
||||||
"""Calibrated Airspeed to True Airspeed"""
|
"""Calibrated Airspeed to True Airspeed"""
|
||||||
p, rho, T = atmos(H)
|
p, rho, T = atmos(H)
|
||||||
qdyn = p0*((1.+rho0*Vcas*Vcas/(7.*p0))**3.5-1.)
|
qdyn = p0 * ((1.0 + rho0 * Vcas * Vcas / (7.0 * p0)) ** 3.5 - 1.0)
|
||||||
Vtas = np.sqrt(7.*p/rho*((1.+qdyn/p)**(2./7.)-1.))
|
Vtas = np.sqrt(7.0 * p / rho * ((1.0 + qdyn / p) ** (2.0 / 7.0) - 1.0))
|
||||||
return Vtas
|
return Vtas
|
||||||
|
|
||||||
|
|
||||||
def tas2cas(Vtas, H):
|
def tas2cas(Vtas, H):
|
||||||
"""True Airspeed to Calibrated Airspeed"""
|
"""True Airspeed to Calibrated Airspeed"""
|
||||||
p, rho, T = atmos(H)
|
p, rho, T = atmos(H)
|
||||||
qdyn = p*((1.+rho*Vtas*Vtas/(7.*p))**3.5-1.)
|
qdyn = p * ((1.0 + rho * Vtas * Vtas / (7.0 * p)) ** 3.5 - 1.0)
|
||||||
Vcas = np.sqrt(7.*p0/rho0*((qdyn/p0+1.)**(2./7.)-1.))
|
Vcas = np.sqrt(7.0 * p0 / rho0 * ((qdyn / p0 + 1.0) ** (2.0 / 7.0) - 1.0))
|
||||||
return Vcas
|
return Vcas
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
162
pyModeS/extra/rtlreader.py
Normal file
162
pyModeS/extra/rtlreader.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pyModeS as pms
|
||||||
|
from rtlsdr import RtlSdr
|
||||||
|
import time
|
||||||
|
|
||||||
|
modes_sample_rate = 2e6
|
||||||
|
modes_frequency = 1090e6
|
||||||
|
buffer_size = 1024 * 100
|
||||||
|
read_size = 1024 * 20
|
||||||
|
|
||||||
|
pbits = 8
|
||||||
|
fbits = 112
|
||||||
|
preamble = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0]
|
||||||
|
th_amp = 0.2 # signal amplitude threshold for 0 and 1 bit
|
||||||
|
th_amp_diff = 0.8 # signal amplitude threshold difference between 0 and 1 bit
|
||||||
|
|
||||||
|
|
||||||
|
class RtlReader(object):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(RtlReader, self).__init__()
|
||||||
|
self.signal_buffer = []
|
||||||
|
self.sdr = RtlSdr()
|
||||||
|
self.sdr.sample_rate = modes_sample_rate
|
||||||
|
self.sdr.center_freq = modes_frequency
|
||||||
|
self.sdr.gain = "auto"
|
||||||
|
# sdr.freq_correction = 75
|
||||||
|
|
||||||
|
self.debug = kwargs.get("debug", False)
|
||||||
|
self.raw_pipe_in = None
|
||||||
|
self.stop_flag = False
|
||||||
|
|
||||||
|
def _process_buffer(self):
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
# signal_array = np.array(self.signal_buffer)
|
||||||
|
# pulses_array = np.where(np.array(self.signal_buffer) < th_amp, 0, 1)
|
||||||
|
# pulses = "".join(str(x) for x in pulses_array)
|
||||||
|
buffer_length = len(self.signal_buffer)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < buffer_length:
|
||||||
|
if self.signal_buffer[i] < th_amp:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# if pulses[i : i + pbits * 2] == preamble:
|
||||||
|
if self._check_preamble(self.signal_buffer[i : i + pbits * 2]):
|
||||||
|
frame_start = i + pbits * 2
|
||||||
|
frame_end = i + pbits * 2 + (fbits + 1) * 2
|
||||||
|
frame_length = (fbits + 1) * 2
|
||||||
|
frame_pulses = self.signal_buffer[frame_start:frame_end]
|
||||||
|
|
||||||
|
msgbin = ""
|
||||||
|
for j in range(0, frame_length, 2):
|
||||||
|
p2 = frame_pulses[j : j + 2]
|
||||||
|
if len(p2) < 2:
|
||||||
|
break
|
||||||
|
|
||||||
|
if p2[0] < th_amp and p2[1] < th_amp:
|
||||||
|
break
|
||||||
|
elif p2[0] >= p2[1]:
|
||||||
|
c = "1"
|
||||||
|
elif p2[0] < p2[1]:
|
||||||
|
c = "0"
|
||||||
|
else:
|
||||||
|
msgbin = ""
|
||||||
|
break
|
||||||
|
msgbin += c
|
||||||
|
|
||||||
|
# advance i with a jump
|
||||||
|
i = frame_start + j
|
||||||
|
|
||||||
|
if len(msgbin) > 0:
|
||||||
|
msghex = pms.bin2hex(msgbin)
|
||||||
|
if self._check_msg(msghex):
|
||||||
|
messages.append([msghex, time.time()])
|
||||||
|
if self.debug:
|
||||||
|
self._debug_msg(msghex)
|
||||||
|
|
||||||
|
elif i > buffer_length - 500:
|
||||||
|
# save some for next process
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# keep reminder of buffer for next iteration
|
||||||
|
self.signal_buffer = self.signal_buffer[i:]
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def _check_preamble(self, pulses):
|
||||||
|
if len(pulses) != 16:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for i in range(16):
|
||||||
|
if abs(pulses[i] - preamble[i]) > th_amp_diff:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _check_msg(self, msg):
|
||||||
|
df = pms.df(msg)
|
||||||
|
msglen = len(msg)
|
||||||
|
if df == 17 and msglen == 28:
|
||||||
|
if pms.crc(msg) == 0:
|
||||||
|
return True
|
||||||
|
elif df in [20, 21] and msglen == 28:
|
||||||
|
return True
|
||||||
|
elif df in [4, 5, 11] and msglen == 14:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _debug_msg(self, msg):
|
||||||
|
df = pms.df(msg)
|
||||||
|
msglen = len(msg)
|
||||||
|
if df == 17 and msglen == 28:
|
||||||
|
print(msg, pms.icao(msg), pms.crc(msg))
|
||||||
|
elif df in [20, 21] and msglen == 28:
|
||||||
|
print(msg, pms.icao(msg))
|
||||||
|
elif df in [4, 5, 11] and msglen == 14:
|
||||||
|
print(msg, pms.icao(msg))
|
||||||
|
else:
|
||||||
|
# print("[*]", msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _read_callback(self, data, rtlsdr_obj):
|
||||||
|
# scaling signal (imporatant)
|
||||||
|
amp = np.absolute(data)
|
||||||
|
amp_norm = np.interp(amp, (amp.min(), amp.max()), (0, 1))
|
||||||
|
self.signal_buffer.extend(amp_norm.tolist())
|
||||||
|
|
||||||
|
if len(self.signal_buffer) >= buffer_size:
|
||||||
|
messages = self._process_buffer()
|
||||||
|
self.handle_messages(messages)
|
||||||
|
|
||||||
|
def handle_messages(self, messages):
|
||||||
|
"""re-implement this method to handle the messages"""
|
||||||
|
for msg, t in messages:
|
||||||
|
# print("%15.9f %s" % (t, msg))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self, *args, **kwargs):
|
||||||
|
self.sdr.cancel_read_async()
|
||||||
|
|
||||||
|
def run(self, raw_pipe_in=None, stop_flag=None):
|
||||||
|
self.raw_pipe_in = raw_pipe_in
|
||||||
|
self.stop_flag = stop_flag
|
||||||
|
self.sdr.read_samples_async(self._read_callback, read_size)
|
||||||
|
|
||||||
|
# count = 1
|
||||||
|
# while count < 1000:
|
||||||
|
# count += 1
|
||||||
|
# data = self.sdr.read_samples(read_size)
|
||||||
|
# self._read_callback(data, None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import signal
|
||||||
|
|
||||||
|
rtl = RtlReader()
|
||||||
|
signal.signal(signal.SIGINT, rtl.stop)
|
||||||
|
|
||||||
|
rtl.debug = True
|
||||||
|
rtl.run()
|
||||||
@@ -1,55 +1,55 @@
|
|||||||
'''
|
"""Stream beast raw data from a TCP server, convert to mode-s messages."""
|
||||||
Stream beast raw data from a TCP server, convert to mode-s messages
|
|
||||||
'''
|
|
||||||
from __future__ import print_function, division
|
from __future__ import print_function, division
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import socket
|
|
||||||
import time
|
import time
|
||||||
import pyModeS as pms
|
import pyModeS as pms
|
||||||
from threading import Thread
|
|
||||||
import traceback
|
import traceback
|
||||||
|
import zmq
|
||||||
|
|
||||||
if (sys.version_info > (3, 0)):
|
if sys.version_info > (3, 0):
|
||||||
PY_VERSION = 3
|
PY_VERSION = 3
|
||||||
else:
|
else:
|
||||||
PY_VERSION = 2
|
PY_VERSION = 2
|
||||||
|
|
||||||
class BaseClient(Thread):
|
|
||||||
def __init__(self, host, port, rawtype):
|
class TcpClient(object):
|
||||||
Thread.__init__(self)
|
def __init__(self, host, port, datatype):
|
||||||
|
super(TcpClient, self).__init__()
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
self.rawtype = rawtype
|
self.socket = None
|
||||||
if self.rawtype not in ['avr', 'beast', 'skysense']:
|
self.datatype = datatype
|
||||||
print("rawtype must be either avr, beast or skysense")
|
if self.datatype not in ["raw", "beast", "skysense"]:
|
||||||
|
print("datatype must be either raw, beast or skysense")
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
|
self.raw_pipe_in = None
|
||||||
|
self.stop_flag = False
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
while True:
|
self.socket = zmq.Context().socket(zmq.STREAM)
|
||||||
try:
|
self.socket.setsockopt(zmq.LINGER, 0)
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.socket.setsockopt(zmq.RCVTIMEO, 10000)
|
||||||
s.settimeout(10) # 10 second timeout
|
self.socket.connect("tcp://%s:%s" % (self.host, self.port))
|
||||||
s.connect((self.host, self.port))
|
|
||||||
print("Server connected - %s:%s" % (self.host, self.port))
|
|
||||||
print("collecting ADS-B messages...")
|
|
||||||
return s
|
|
||||||
except socket.error as err:
|
|
||||||
print("Socket connection error: %s. reconnecting..." % err)
|
|
||||||
time.sleep(3)
|
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.socket.disconnect()
|
||||||
|
|
||||||
def read_avr_buffer(self):
|
def read_raw_buffer(self):
|
||||||
# -- testing --
|
""" Read raw ADS-B data type.
|
||||||
# for b in self.buffer:
|
|
||||||
# print(chr(b), b)
|
|
||||||
|
|
||||||
# Append message with 0-9,A-F,a-f, until stop sign
|
|
||||||
|
|
||||||
|
String strats with "*" and ends with ";". For example:
|
||||||
|
*5d484ba898f8c6;
|
||||||
|
*8d400cd5990d7e9a10043e5e6da0;
|
||||||
|
*a0001498be800030aa0000c7a75f;
|
||||||
|
"""
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
msg_stop = False
|
msg_stop = False
|
||||||
|
self.current_msg = ""
|
||||||
for b in self.buffer:
|
for b in self.buffer:
|
||||||
if b == 59:
|
if b == 59:
|
||||||
msg_stop = True
|
msg_stop = True
|
||||||
@@ -57,9 +57,9 @@ class BaseClient(Thread):
|
|||||||
messages.append([self.current_msg, ts])
|
messages.append([self.current_msg, ts])
|
||||||
if b == 42:
|
if b == 42:
|
||||||
msg_stop = False
|
msg_stop = False
|
||||||
self.current_msg = ''
|
self.current_msg = ""
|
||||||
|
|
||||||
if (not msg_stop) and (48<=b<=57 or 65<=b<=70 or 97<=b<=102):
|
if (not msg_stop) and (48 <= b <= 57 or 65 <= b <= 70 or 97 <= b <= 102):
|
||||||
self.current_msg = self.current_msg + chr(b)
|
self.current_msg = self.current_msg + chr(b)
|
||||||
|
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
@@ -67,7 +67,8 @@ class BaseClient(Thread):
|
|||||||
return messages
|
return messages
|
||||||
|
|
||||||
def read_beast_buffer(self):
|
def read_beast_buffer(self):
|
||||||
'''
|
"""Handle mode-s beast data type.
|
||||||
|
|
||||||
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
|
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||||
2 byte Mode-AC
|
2 byte Mode-AC
|
||||||
<esc> "2" : 6 byte MLAT timestamp, 1 byte signal level,
|
<esc> "2" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||||
@@ -81,8 +82,7 @@ class BaseClient(Thread):
|
|||||||
|
|
||||||
timestamp:
|
timestamp:
|
||||||
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
|
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
|
||||||
'''
|
"""
|
||||||
|
|
||||||
messages_mlat = []
|
messages_mlat = []
|
||||||
msg = []
|
msg = []
|
||||||
i = 0
|
i = 0
|
||||||
@@ -91,16 +91,16 @@ class BaseClient(Thread):
|
|||||||
# then, reset the self.buffer with the remainder
|
# then, reset the self.buffer with the remainder
|
||||||
|
|
||||||
while i < len(self.buffer):
|
while i < len(self.buffer):
|
||||||
if (self.buffer[i:i+2] == [0x1a, 0x1a]):
|
if self.buffer[i : i + 2] == [0x1A, 0x1A]:
|
||||||
msg.append(0x1a)
|
msg.append(0x1A)
|
||||||
i += 1
|
i += 1
|
||||||
elif (i == len(self.buffer) - 1) and (self.buffer[i] == 0x1a):
|
elif (i == len(self.buffer) - 1) and (self.buffer[i] == 0x1A):
|
||||||
# special case where the last bit is 0x1a
|
# special case where the last bit is 0x1a
|
||||||
msg.append(0x1a)
|
msg.append(0x1A)
|
||||||
elif self.buffer[i] == 0x1a:
|
elif self.buffer[i] == 0x1A:
|
||||||
if i == len(self.buffer) - 1:
|
if i == len(self.buffer) - 1:
|
||||||
# special case where the last bit is 0x1a
|
# special case where the last bit is 0x1a
|
||||||
msg.append(0x1a)
|
msg.append(0x1A)
|
||||||
elif len(msg) > 0:
|
elif len(msg) > 0:
|
||||||
messages_mlat.append(msg)
|
messages_mlat.append(msg)
|
||||||
msg = []
|
msg = []
|
||||||
@@ -112,12 +112,12 @@ class BaseClient(Thread):
|
|||||||
if len(msg) > 0:
|
if len(msg) > 0:
|
||||||
reminder = []
|
reminder = []
|
||||||
for i, m in enumerate(msg):
|
for i, m in enumerate(msg):
|
||||||
if (m == 0x1a) and (i < len(msg)-1):
|
if (m == 0x1A) and (i < len(msg) - 1):
|
||||||
# rewind 0x1a, except when it is at the last bit
|
# rewind 0x1a, except when it is at the last bit
|
||||||
reminder.extend([m, m])
|
reminder.extend([m, m])
|
||||||
else:
|
else:
|
||||||
reminder.append(m)
|
reminder.append(m)
|
||||||
self.buffer = [0x1a] + msg
|
self.buffer = [0x1A] + msg
|
||||||
else:
|
else:
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
|
|
||||||
@@ -131,14 +131,17 @@ class BaseClient(Thread):
|
|||||||
|
|
||||||
if msgtype == 0x32:
|
if msgtype == 0x32:
|
||||||
# Mode-S Short Message, 7 byte, 14-len hexstr
|
# Mode-S Short Message, 7 byte, 14-len hexstr
|
||||||
msg = ''.join('%02X' % i for i in mm[8:15])
|
msg = "".join("%02X" % i for i in mm[8:15])
|
||||||
elif msgtype == 0x33:
|
elif msgtype == 0x33:
|
||||||
# Mode-S Long Message, 14 byte, 28-len hexstr
|
# Mode-S Long Message, 14 byte, 28-len hexstr
|
||||||
msg = ''.join('%02X' % i for i in mm[8:22])
|
msg = "".join("%02X" % i for i in mm[8:22])
|
||||||
else:
|
else:
|
||||||
# Other message tupe
|
# Other message tupe
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if len(msg) not in [14, 28]:
|
||||||
|
continue
|
||||||
|
|
||||||
df = pms.df(msg)
|
df = pms.df(msg)
|
||||||
|
|
||||||
# skip incomplete message
|
# skip incomplete message
|
||||||
@@ -215,25 +218,33 @@ class BaseClient(Thread):
|
|||||||
messages = []
|
messages = []
|
||||||
while len(self.buffer) > SS_MSGLENGTH:
|
while len(self.buffer) > SS_MSGLENGTH:
|
||||||
i = 0
|
i = 0
|
||||||
if self.buffer[i] == SS_STARTCHAR and self.buffer[i+SS_MSGLENGTH] == SS_STARTCHAR:
|
if (
|
||||||
|
self.buffer[i] == SS_STARTCHAR
|
||||||
|
and self.buffer[i + SS_MSGLENGTH] == SS_STARTCHAR
|
||||||
|
):
|
||||||
i += 1
|
i += 1
|
||||||
if (self.buffer[i]>>7):
|
if self.buffer[i] >> 7:
|
||||||
#Long message
|
# Long message
|
||||||
payload = self.buffer[i:i+14]
|
payload = self.buffer[i : i + 14]
|
||||||
else:
|
else:
|
||||||
#Short message
|
# Short message
|
||||||
payload = self.buffer[i:i+7]
|
payload = self.buffer[i : i + 7]
|
||||||
msg = ''.join('%02X' % j for j in payload)
|
msg = "".join("%02X" % j for j in payload)
|
||||||
i += 14 #Both message types use 14 bytes
|
i += 14 # Both message types use 14 bytes
|
||||||
tsbin = self.buffer[i:i+6]
|
tsbin = self.buffer[i : i + 6]
|
||||||
sec = ( (tsbin[0] & 0x7f) << 10) | (tsbin[1] << 2 ) | (tsbin[2] >> 6)
|
sec = ((tsbin[0] & 0x7F) << 10) | (tsbin[1] << 2) | (tsbin[2] >> 6)
|
||||||
nano = ( (tsbin[2] & 0x3f) << 24) | (tsbin[3] << 16) | (tsbin[4] << 8) | tsbin[5]
|
nano = (
|
||||||
ts = sec + nano*1.0e-9
|
((tsbin[2] & 0x3F) << 24)
|
||||||
|
| (tsbin[3] << 16)
|
||||||
|
| (tsbin[4] << 8)
|
||||||
|
| tsbin[5]
|
||||||
|
)
|
||||||
|
ts = sec + nano * 1.0e-9
|
||||||
i += 6
|
i += 6
|
||||||
#Signal and noise level - Don't care for now
|
# Signal and noise level - Don't care for now
|
||||||
i += 3
|
i += 3
|
||||||
self.buffer = self.buffer[SS_MSGLENGTH:]
|
self.buffer = self.buffer[SS_MSGLENGTH:]
|
||||||
messages.append( [msg,ts] )
|
messages.append([msg, ts])
|
||||||
else:
|
else:
|
||||||
self.buffer = self.buffer[1:]
|
self.buffer = self.buffer[1:]
|
||||||
return messages
|
return messages
|
||||||
@@ -243,12 +254,14 @@ class BaseClient(Thread):
|
|||||||
for msg, t in messages:
|
for msg, t in messages:
|
||||||
print("%15.9f %s" % (t, msg))
|
print("%15.9f %s" % (t, msg))
|
||||||
|
|
||||||
def run(self):
|
def run(self, raw_pipe_in=None, stop_flag=None):
|
||||||
sock = self.connect()
|
self.raw_pipe_in = raw_pipe_in
|
||||||
|
self.stop_flag = stop_flag
|
||||||
|
self.connect()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
received = sock.recv(1024)
|
received = [i for i in self.socket.recv(4096)]
|
||||||
|
|
||||||
if PY_VERSION == 2:
|
if PY_VERSION == 2:
|
||||||
received = [ord(i) for i in received]
|
received = [ord(i) for i in received]
|
||||||
@@ -261,11 +274,11 @@ class BaseClient(Thread):
|
|||||||
# continue
|
# continue
|
||||||
# -- Removed!! Cause delay in low data rate scenario --
|
# -- Removed!! Cause delay in low data rate scenario --
|
||||||
|
|
||||||
if self.rawtype == 'beast':
|
if self.datatype == "beast":
|
||||||
messages = self.read_beast_buffer()
|
messages = self.read_beast_buffer()
|
||||||
elif self.rawtype == 'avr':
|
elif self.datatype == "raw":
|
||||||
messages = self.read_avr_buffer()
|
messages = self.read_raw_buffer()
|
||||||
elif self.rawtype == 'skysense':
|
elif self.datatype == "skysense":
|
||||||
messages = self.read_skysense_buffer()
|
messages = self.read_skysense_buffer()
|
||||||
|
|
||||||
if not messages:
|
if not messages:
|
||||||
@@ -273,14 +286,12 @@ class BaseClient(Thread):
|
|||||||
else:
|
else:
|
||||||
self.handle_messages(messages)
|
self.handle_messages(messages)
|
||||||
|
|
||||||
time.sleep(0.001)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
||||||
# Provides the user an option to supply the environment
|
# Provides the user an option to supply the environment
|
||||||
# variable PYMODES_DEBUG to halt the execution
|
# variable PYMODES_DEBUG to halt the execution
|
||||||
# for debugging purposes
|
# for debugging purposes
|
||||||
debug_intent = os.environ.get('PYMODES_DEBUG', 'false')
|
debug_intent = os.environ.get("PYMODES_DEBUG", "false")
|
||||||
if debug_intent.lower() == 'true':
|
if debug_intent.lower() == "true":
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
else:
|
else:
|
||||||
@@ -288,15 +299,15 @@ class BaseClient(Thread):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
sock = self.connect()
|
sock = self.connect()
|
||||||
|
time.sleep(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Unexpected Error:", e)
|
print("Unexpected Error:", e)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
# for testing purpose only
|
# for testing purpose only
|
||||||
host = sys.argv[1]
|
host = sys.argv[1]
|
||||||
port = int(sys.argv[2])
|
port = int(sys.argv[2])
|
||||||
rawtype = sys.argv[3]
|
datatype = sys.argv[3]
|
||||||
client = BaseClient(host=host, port=port, rawtype=rawtype)
|
client = TcpClient(host=host, port=port, datatype=datatype)
|
||||||
client.daemon = True
|
|
||||||
client.run()
|
client.run()
|
||||||
|
|||||||
284
pyModeS/streamer/decode.py
Normal file
284
pyModeS/streamer/decode.py
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
from __future__ import absolute_import, print_function, division
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
import csv
|
||||||
|
import pyModeS as pms
|
||||||
|
|
||||||
|
|
||||||
|
class Decode:
|
||||||
|
def __init__(self, latlon=None, dumpto=None):
|
||||||
|
|
||||||
|
self.acs = dict()
|
||||||
|
|
||||||
|
if latlon is not None:
|
||||||
|
self.lat0 = float(latlon[0])
|
||||||
|
self.lon0 = float(latlon[1])
|
||||||
|
else:
|
||||||
|
self.lat0 = None
|
||||||
|
self.lon0 = None
|
||||||
|
|
||||||
|
self.t = 0
|
||||||
|
self.cache_timeout = 60 # seconds
|
||||||
|
|
||||||
|
if dumpto is not None and os.path.isdir(dumpto):
|
||||||
|
self.dumpto = dumpto
|
||||||
|
else:
|
||||||
|
self.dumpto = None
|
||||||
|
|
||||||
|
def process_raw(self, adsb_ts, adsb_msg, commb_ts, commb_msg, tnow=None):
|
||||||
|
"""process a chunk of adsb and commb messages recieved in the same
|
||||||
|
time period.
|
||||||
|
"""
|
||||||
|
if tnow is None:
|
||||||
|
tnow = time.time()
|
||||||
|
|
||||||
|
self.t = tnow
|
||||||
|
|
||||||
|
local_updated_acs_buffer = []
|
||||||
|
output_buffer = []
|
||||||
|
|
||||||
|
# process adsb message
|
||||||
|
for t, msg in zip(adsb_ts, adsb_msg):
|
||||||
|
icao = pms.icao(msg)
|
||||||
|
tc = pms.adsb.typecode(msg)
|
||||||
|
|
||||||
|
if icao not in self.acs:
|
||||||
|
self.acs[icao] = {
|
||||||
|
"live": None,
|
||||||
|
"call": None,
|
||||||
|
"lat": None,
|
||||||
|
"lon": None,
|
||||||
|
"alt": None,
|
||||||
|
"gs": None,
|
||||||
|
"trk": None,
|
||||||
|
"roc": None,
|
||||||
|
"tas": None,
|
||||||
|
"roll": None,
|
||||||
|
"rtrk": None,
|
||||||
|
"ias": None,
|
||||||
|
"mach": None,
|
||||||
|
"hdg": None,
|
||||||
|
"ver": None,
|
||||||
|
"HPL": None,
|
||||||
|
"RCu": None,
|
||||||
|
"RCv": None,
|
||||||
|
"HVE": None,
|
||||||
|
"VVE": None,
|
||||||
|
"Rc": None,
|
||||||
|
"VPL": None,
|
||||||
|
"EPU": None,
|
||||||
|
"VEPU": None,
|
||||||
|
"HFOMr": None,
|
||||||
|
"VFOMr": None,
|
||||||
|
"PE_RCu": None,
|
||||||
|
"PE_VPL": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.acs[icao]["t"] = t
|
||||||
|
self.acs[icao]["live"] = int(t)
|
||||||
|
|
||||||
|
if 1 <= tc <= 4:
|
||||||
|
cs = pms.adsb.callsign(msg)
|
||||||
|
self.acs[icao]["call"] = cs
|
||||||
|
output_buffer.append([t, icao, "cs", cs])
|
||||||
|
|
||||||
|
if (5 <= tc <= 8) or (tc == 19):
|
||||||
|
vdata = pms.adsb.velocity(msg)
|
||||||
|
if vdata is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
spd, trk, roc, tag = vdata
|
||||||
|
if tag != "GS":
|
||||||
|
continue
|
||||||
|
if (spd is None) or (trk is None):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.acs[icao]["gs"] = spd
|
||||||
|
self.acs[icao]["trk"] = trk
|
||||||
|
self.acs[icao]["roc"] = roc
|
||||||
|
self.acs[icao]["tv"] = t
|
||||||
|
|
||||||
|
output_buffer.append([t, icao, "gs", spd])
|
||||||
|
output_buffer.append([t, icao, "trk", trk])
|
||||||
|
output_buffer.append([t, icao, "roc", roc])
|
||||||
|
|
||||||
|
if 5 <= tc <= 18:
|
||||||
|
oe = pms.adsb.oe_flag(msg)
|
||||||
|
self.acs[icao][oe] = msg
|
||||||
|
self.acs[icao]["t" + str(oe)] = t
|
||||||
|
|
||||||
|
if ("tpos" in self.acs[icao]) and (t - self.acs[icao]["tpos"] < 180):
|
||||||
|
# use single message decoding
|
||||||
|
rlat = self.acs[icao]["lat"]
|
||||||
|
rlon = self.acs[icao]["lon"]
|
||||||
|
latlon = pms.adsb.position_with_ref(msg, rlat, rlon)
|
||||||
|
elif (
|
||||||
|
("t0" in self.acs[icao])
|
||||||
|
and ("t1" in self.acs[icao])
|
||||||
|
and (abs(self.acs[icao]["t0"] - self.acs[icao]["t1"]) < 10)
|
||||||
|
):
|
||||||
|
# use multi message decoding
|
||||||
|
try:
|
||||||
|
latlon = pms.adsb.position(
|
||||||
|
self.acs[icao][0],
|
||||||
|
self.acs[icao][1],
|
||||||
|
self.acs[icao]["t0"],
|
||||||
|
self.acs[icao]["t1"],
|
||||||
|
self.lat0,
|
||||||
|
self.lon0,
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
# mix of surface and airborne position message
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
latlon = None
|
||||||
|
|
||||||
|
if latlon is not None:
|
||||||
|
self.acs[icao]["tpos"] = t
|
||||||
|
self.acs[icao]["lat"] = latlon[0]
|
||||||
|
self.acs[icao]["lon"] = latlon[1]
|
||||||
|
|
||||||
|
alt = pms.adsb.altitude(msg)
|
||||||
|
self.acs[icao]["alt"] = alt
|
||||||
|
|
||||||
|
output_buffer.append([t, icao, "lat", latlon[0]])
|
||||||
|
output_buffer.append([t, icao, "lon", latlon[1]])
|
||||||
|
output_buffer.append([t, icao, "alt", alt])
|
||||||
|
|
||||||
|
local_updated_acs_buffer.append(icao)
|
||||||
|
|
||||||
|
# Uncertainty & accuracy
|
||||||
|
ac = self.acs[icao]
|
||||||
|
|
||||||
|
if 9 <= tc <= 18:
|
||||||
|
ac["nic_bc"] = pms.adsb.nic_b(msg)
|
||||||
|
|
||||||
|
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
|
||||||
|
ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg)
|
||||||
|
|
||||||
|
if (ac["ver"] == 1) and ("nic_s" in ac.keys()):
|
||||||
|
ac["Rc"], ac["VPL"] = pms.adsb.nic_v1(msg, ac["nic_s"])
|
||||||
|
elif (
|
||||||
|
(ac["ver"] == 2)
|
||||||
|
and ("nic_a" in ac.keys())
|
||||||
|
and ("nic_bc" in ac.keys())
|
||||||
|
):
|
||||||
|
ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"])
|
||||||
|
|
||||||
|
if tc == 19:
|
||||||
|
ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
|
||||||
|
if ac["ver"] in [1, 2]:
|
||||||
|
ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
|
||||||
|
|
||||||
|
if tc == 29:
|
||||||
|
ac["PE_RCu"], ac["PE_VPL"], ac["base"] = pms.adsb.sil(msg, ac["ver"])
|
||||||
|
ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||||
|
|
||||||
|
if tc == 31:
|
||||||
|
ac["ver"] = pms.adsb.version(msg)
|
||||||
|
ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||||
|
ac["PE_RCu"], ac["PE_VPL"], ac["sil_base"] = pms.adsb.sil(
|
||||||
|
msg, ac["ver"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if ac["ver"] == 1:
|
||||||
|
ac["nic_s"] = pms.adsb.nic_s(msg)
|
||||||
|
elif ac["ver"] == 2:
|
||||||
|
ac["nic_a"], ac["nic_bc"] = pms.adsb.nic_a_c(msg)
|
||||||
|
|
||||||
|
# process commb message
|
||||||
|
for t, msg in zip(commb_ts, commb_msg):
|
||||||
|
icao = pms.icao(msg)
|
||||||
|
|
||||||
|
if icao not in self.acs:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.acs[icao]["live"] = int(t)
|
||||||
|
|
||||||
|
bds = pms.bds.infer(msg)
|
||||||
|
|
||||||
|
if bds == "BDS50":
|
||||||
|
roll50 = pms.commb.roll50(msg)
|
||||||
|
trk50 = pms.commb.trk50(msg)
|
||||||
|
rtrk50 = pms.commb.rtrk50(msg)
|
||||||
|
gs50 = pms.commb.gs50(msg)
|
||||||
|
tas50 = pms.commb.tas50(msg)
|
||||||
|
|
||||||
|
self.acs[icao]["t50"] = t
|
||||||
|
if tas50:
|
||||||
|
self.acs[icao]["tas"] = tas50
|
||||||
|
output_buffer.append([t, icao, "tas50", tas50])
|
||||||
|
if roll50:
|
||||||
|
self.acs[icao]["roll"] = roll50
|
||||||
|
output_buffer.append([t, icao, "roll50", roll50])
|
||||||
|
if rtrk50:
|
||||||
|
self.acs[icao]["rtrk"] = rtrk50
|
||||||
|
output_buffer.append([t, icao, "rtrk50", rtrk50])
|
||||||
|
|
||||||
|
if trk50:
|
||||||
|
output_buffer.append([t, icao, "trk50", trk50])
|
||||||
|
if gs50:
|
||||||
|
output_buffer.append([t, icao, "gs50", gs50])
|
||||||
|
|
||||||
|
elif bds == "BDS60":
|
||||||
|
ias60 = pms.commb.ias60(msg)
|
||||||
|
hdg60 = pms.commb.hdg60(msg)
|
||||||
|
mach60 = pms.commb.mach60(msg)
|
||||||
|
roc60baro = pms.commb.vr60baro(msg)
|
||||||
|
roc60ins = pms.commb.vr60ins(msg)
|
||||||
|
|
||||||
|
if ias60 or hdg60 or mach60:
|
||||||
|
self.acs[icao]["t60"] = t
|
||||||
|
if ias60:
|
||||||
|
self.acs[icao]["ias"] = ias60
|
||||||
|
if hdg60:
|
||||||
|
self.acs[icao]["hdg"] = hdg60
|
||||||
|
if mach60:
|
||||||
|
self.acs[icao]["mach"] = mach60
|
||||||
|
|
||||||
|
if roc60baro:
|
||||||
|
output_buffer.append([t, icao, "roc60baro", roc60baro])
|
||||||
|
if roc60ins:
|
||||||
|
output_buffer.append([t, icao, "roc60ins", roc60ins])
|
||||||
|
|
||||||
|
# clear up old data
|
||||||
|
for icao in list(self.acs.keys()):
|
||||||
|
if self.t - self.acs[icao]["live"] > self.cache_timeout:
|
||||||
|
del self.acs[icao]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.dumpto is not None:
|
||||||
|
dh = str(datetime.datetime.now().strftime("%Y%m%d_%H"))
|
||||||
|
fn = self.dumpto + "/pymodes_dump_%s.csv" % dh
|
||||||
|
output_buffer.sort(key=lambda x: x[0])
|
||||||
|
with open(fn, "a") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerows(output_buffer)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_aircraft(self):
|
||||||
|
"""all aircraft that are stored in memeory"""
|
||||||
|
acs = self.acs
|
||||||
|
return acs
|
||||||
|
|
||||||
|
def run(self, raw_pipe_out, ac_pipe_in):
|
||||||
|
local_buffer = []
|
||||||
|
while True:
|
||||||
|
while raw_pipe_out.poll():
|
||||||
|
data = raw_pipe_out.recv()
|
||||||
|
local_buffer.append(data)
|
||||||
|
|
||||||
|
for data in local_buffer:
|
||||||
|
self.process_raw(
|
||||||
|
data["adsb_ts"],
|
||||||
|
data["adsb_msg"],
|
||||||
|
data["commb_ts"],
|
||||||
|
data["commb_msg"],
|
||||||
|
)
|
||||||
|
local_buffer = []
|
||||||
|
|
||||||
|
acs = self.get_aircraft()
|
||||||
|
ac_pipe_in.send(acs)
|
||||||
|
time.sleep(0.001)
|
||||||
@@ -3,118 +3,132 @@
|
|||||||
from __future__ import print_function, division
|
from __future__ import print_function, division
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import argparse
|
import argparse
|
||||||
import curses
|
import curses
|
||||||
from threading import Lock
|
import signal
|
||||||
import pyModeS as pms
|
import multiprocessing
|
||||||
from pyModeS.extra.tcpclient import BaseClient
|
from pyModeS.streamer.decode import Decode
|
||||||
from pyModeS.streamer.stream import Stream
|
|
||||||
from pyModeS.streamer.screen import Screen
|
from pyModeS.streamer.screen import Screen
|
||||||
|
from pyModeS.streamer.source import NetSource, RtlSdrSource
|
||||||
LOCK = Lock()
|
|
||||||
ADSB_MSG = []
|
|
||||||
ADSB_TS = []
|
|
||||||
COMMB_MSG = []
|
|
||||||
COMMB_TS = []
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('--server', help='server address or IP', required=True)
|
|
||||||
parser.add_argument('--port', help='raw data port', required=True)
|
|
||||||
parser.add_argument('--rawtype', help='beast, avr or skysense', required=True)
|
|
||||||
parser.add_argument('--latlon', help='receiver position', nargs=2, metavar=('LAT', 'LON'), required=True)
|
|
||||||
parser.add_argument('--show-uncertainty', dest='uncertainty', help='display uncertaint values, default off', action='store_true', required=False, default=False)
|
|
||||||
parser.add_argument('--dumpto', help='folder to dump decoded output', required=False, default=None)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
SERVER = args.server
|
|
||||||
PORT = int(args.port)
|
|
||||||
RAWTYPE = args.rawtype
|
|
||||||
LAT0 = float(args.latlon[0])
|
|
||||||
LON0 = float(args.latlon[1])
|
|
||||||
UNCERTAINTY = args.uncertainty
|
|
||||||
DUMPTO = args.dumpto
|
|
||||||
|
|
||||||
if DUMPTO is not None:
|
|
||||||
# append to current folder except root is given
|
|
||||||
if DUMPTO[0] != '/':
|
|
||||||
DUMPTO = os.getcwd() + '/' + DUMPTO
|
|
||||||
|
|
||||||
if not os.path.isdir(DUMPTO):
|
|
||||||
print('Error: dump folder (%s) does not exist' % DUMPTO)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
class ModesClient(BaseClient):
|
|
||||||
def __init__(self, host, port, rawtype):
|
|
||||||
super(ModesClient, self).__init__(host, port, rawtype)
|
|
||||||
|
|
||||||
def handle_messages(self, messages):
|
|
||||||
local_buffer_adsb_msg = []
|
|
||||||
local_buffer_adsb_ts = []
|
|
||||||
local_buffer_ehs_msg = []
|
|
||||||
local_buffer_ehs_ts = []
|
|
||||||
|
|
||||||
for msg, t in messages:
|
|
||||||
if len(msg) < 28: # only process long messages
|
|
||||||
continue
|
|
||||||
|
|
||||||
df = pms.df(msg)
|
|
||||||
|
|
||||||
if df == 17 or df == 18:
|
|
||||||
local_buffer_adsb_msg.append(msg)
|
|
||||||
local_buffer_adsb_ts.append(t)
|
|
||||||
elif df == 20 or df == 21:
|
|
||||||
local_buffer_ehs_msg.append(msg)
|
|
||||||
local_buffer_ehs_ts.append(t)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
|
||||||
LOCK.acquire()
|
|
||||||
ADSB_MSG.extend(local_buffer_adsb_msg)
|
|
||||||
ADSB_TS.extend(local_buffer_adsb_ts)
|
|
||||||
COMMB_MSG.extend(local_buffer_ehs_msg)
|
|
||||||
COMMB_TS.extend(local_buffer_ehs_ts)
|
|
||||||
LOCK.release()
|
|
||||||
|
|
||||||
|
|
||||||
# redirect all stdout to null, avoiding messing up with the screen
|
# redirect all stdout to null, avoiding messing up with the screen
|
||||||
sys.stdout = open(os.devnull, 'w')
|
sys.stdout = open(os.devnull, "w")
|
||||||
|
|
||||||
client = ModesClient(host=SERVER, port=PORT, rawtype=RAWTYPE)
|
|
||||||
client.daemon = True
|
|
||||||
client.start()
|
|
||||||
|
|
||||||
stream = Stream(lat0=LAT0, lon0=LON0, dumpto=DUMPTO)
|
support_rawtypes = ["raw", "beast", "skysense"]
|
||||||
|
|
||||||
try:
|
parser = argparse.ArgumentParser()
|
||||||
screen = Screen(uncertainty=UNCERTAINTY)
|
parser.add_argument(
|
||||||
screen.daemon = True
|
"--source",
|
||||||
screen.start()
|
help='Choose data source, "rtlsdr" or "net"',
|
||||||
|
required=True,
|
||||||
|
default="net",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--connect",
|
||||||
|
help="Define server, port and data type. Supported data types are: %s"
|
||||||
|
% support_rawtypes,
|
||||||
|
nargs=3,
|
||||||
|
metavar=("SERVER", "PORT", "DATATYPE"),
|
||||||
|
default=None,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--latlon",
|
||||||
|
help="Receiver latitude and longitude, needed for the surface position, default none",
|
||||||
|
nargs=2,
|
||||||
|
metavar=("LAT", "LON"),
|
||||||
|
default=None,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--show-uncertainty",
|
||||||
|
dest="uncertainty",
|
||||||
|
help="Display uncertainty values, default off",
|
||||||
|
action="store_true",
|
||||||
|
required=False,
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dumpto",
|
||||||
|
help="Folder to dump decoded output, default none",
|
||||||
|
required=False,
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
while True:
|
SOURCE = args.source
|
||||||
if len(ADSB_MSG) > 200:
|
LATLON = args.latlon
|
||||||
LOCK.acquire()
|
UNCERTAINTY = args.uncertainty
|
||||||
stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
|
DUMPTO = args.dumpto
|
||||||
ADSB_MSG = []
|
|
||||||
ADSB_TS = []
|
|
||||||
COMMB_MSG = []
|
|
||||||
COMMB_TS = []
|
|
||||||
LOCK.release()
|
|
||||||
|
|
||||||
acs = stream.get_aircraft()
|
if SOURCE == "rtlsdr":
|
||||||
try:
|
pass
|
||||||
screen.update_data(acs)
|
elif SOURCE == "net":
|
||||||
screen.update()
|
if args.connect is None:
|
||||||
time.sleep(0.02)
|
print("Error: --connect argument must not be empty.")
|
||||||
except KeyboardInterrupt:
|
else:
|
||||||
raise
|
SERVER, PORT, DATATYPE = args.connect
|
||||||
except:
|
if DATATYPE not in support_rawtypes:
|
||||||
continue
|
print("Data type not supported, avaiable ones are %s" % support_rawtypes)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
else:
|
||||||
sys.exit(0)
|
print('Source must be "rtlsdr" or "net".')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
finally:
|
if DUMPTO is not None:
|
||||||
|
# append to current folder except root is given
|
||||||
|
if DUMPTO[0] != "/":
|
||||||
|
DUMPTO = os.getcwd() + "/" + DUMPTO
|
||||||
|
|
||||||
|
if not os.path.isdir(DUMPTO):
|
||||||
|
print("Error: dump folder (%s) does not exist" % DUMPTO)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# raw_event = multiprocessing.Event()
|
||||||
|
# ac_event = multiprocessing.Event()
|
||||||
|
# raw_queue = multiprocessing.Queue()
|
||||||
|
# ac_queue = multiprocessing.Queue()
|
||||||
|
|
||||||
|
raw_pipe_in, raw_pipe_out = multiprocessing.Pipe()
|
||||||
|
ac_pipe_in, ac_pipe_out = multiprocessing.Pipe()
|
||||||
|
stop_flag = multiprocessing.Value("b", False)
|
||||||
|
|
||||||
|
if SOURCE == "net":
|
||||||
|
source = NetSource(host=SERVER, port=PORT, rawtype=DATATYPE)
|
||||||
|
elif SOURCE == "rtlsdr":
|
||||||
|
source = RtlSdrSource()
|
||||||
|
|
||||||
|
|
||||||
|
recv_process = multiprocessing.Process(target=source.run, args=(raw_pipe_in, stop_flag))
|
||||||
|
|
||||||
|
|
||||||
|
decode = Decode(latlon=LATLON, dumpto=DUMPTO)
|
||||||
|
decode_process = multiprocessing.Process(
|
||||||
|
target=decode.run, args=(raw_pipe_out, ac_pipe_in)
|
||||||
|
)
|
||||||
|
|
||||||
|
screen = Screen(uncertainty=UNCERTAINTY)
|
||||||
|
screen_process = multiprocessing.Process(target=screen.run, args=(ac_pipe_out,))
|
||||||
|
|
||||||
|
|
||||||
|
def closeall(signal, frame):
|
||||||
|
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
|
||||||
|
stop_flag.value = True
|
||||||
curses.endwin()
|
curses.endwin()
|
||||||
|
recv_process.terminate()
|
||||||
|
decode_process.terminate()
|
||||||
|
screen_process.terminate()
|
||||||
|
recv_process.join()
|
||||||
|
decode_process.join()
|
||||||
|
screen_process.join()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, closeall)
|
||||||
|
|
||||||
|
recv_process.start()
|
||||||
|
decode_process.start()
|
||||||
|
screen_process.start()
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
from __future__ import print_function, division
|
from __future__ import print_function, division
|
||||||
import os
|
|
||||||
import curses
|
import curses
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import time
|
import time
|
||||||
from threading import Thread
|
import threading
|
||||||
|
|
||||||
COLUMNS = [
|
COLUMNS = [
|
||||||
('call', 10),
|
("call", 10),
|
||||||
('lat', 10),
|
("lat", 10),
|
||||||
('lon', 10),
|
("lon", 10),
|
||||||
('alt', 7),
|
("alt", 7),
|
||||||
('gs', 5),
|
("gs", 5),
|
||||||
('tas', 5),
|
("tas", 5),
|
||||||
('ias', 5),
|
("ias", 5),
|
||||||
('mach', 7),
|
("mach", 7),
|
||||||
('roc', 7),
|
("roc", 7),
|
||||||
('trk', 10),
|
("trk", 10),
|
||||||
('hdg', 10),
|
("hdg", 10),
|
||||||
('live', 6),
|
("live", 6),
|
||||||
]
|
]
|
||||||
|
|
||||||
UNCERTAINTY_COLUMNS = [
|
UNCERTAINTY_COLUMNS = [
|
||||||
('|', 5),
|
("|", 5),
|
||||||
('ver', 4),
|
("ver", 4),
|
||||||
('HPL', 5),
|
("HPL", 5),
|
||||||
('RCu', 5),
|
("RCu", 5),
|
||||||
('RCv', 5),
|
("RCv", 5),
|
||||||
('HVE', 5),
|
("HVE", 5),
|
||||||
('VVE', 5),
|
("VVE", 5),
|
||||||
('Rc', 4),
|
("Rc", 4),
|
||||||
('VPL', 5),
|
("VPL", 5),
|
||||||
('EPU', 5),
|
("EPU", 5),
|
||||||
('VEPU', 6),
|
("VEPU", 6),
|
||||||
('HFOMr', 7),
|
("HFOMr", 7),
|
||||||
('VFOMr', 7),
|
("VFOMr", 7),
|
||||||
('PE_RCu', 8),
|
("PE_RCu", 8),
|
||||||
('PE_VPL', 8),
|
("PE_VPL", 8),
|
||||||
]
|
]
|
||||||
|
|
||||||
class Screen(Thread):
|
|
||||||
|
class Screen(object):
|
||||||
def __init__(self, uncertainty=False):
|
def __init__(self, uncertainty=False):
|
||||||
Thread.__init__(self)
|
super(Screen, self).__init__()
|
||||||
self.screen = curses.initscr()
|
self.screen = curses.initscr()
|
||||||
curses.noecho()
|
curses.noecho()
|
||||||
curses.mousemask(1)
|
curses.mousemask(1)
|
||||||
@@ -55,16 +55,20 @@ class Screen(Thread):
|
|||||||
if uncertainty:
|
if uncertainty:
|
||||||
self.columns.extend(UNCERTAINTY_COLUMNS)
|
self.columns.extend(UNCERTAINTY_COLUMNS)
|
||||||
|
|
||||||
|
|
||||||
def reset_cursor_pos(self):
|
def reset_cursor_pos(self):
|
||||||
self.screen.move(self.y, self.x)
|
self.screen.move(self.y, self.x)
|
||||||
|
|
||||||
def update_data(self, acs):
|
def update_ac(self, acs):
|
||||||
self.acs = acs
|
self.acs = acs
|
||||||
|
|
||||||
def draw_frame(self):
|
def draw_frame(self):
|
||||||
self.screen.border(0)
|
self.screen.border(0)
|
||||||
self.screen.addstr(0, 2, "Online aircraft [%d] ('Ctrl+C' to exit, 'Enter' to lock one)" % len(self.acs))
|
self.screen.addstr(
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
"Online aircraft [%d] ('Ctrl+C' to exit, 'Enter' to lock one)"
|
||||||
|
% len(self.acs),
|
||||||
|
)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
if len(self.acs) == 0:
|
if len(self.acs) == 0:
|
||||||
@@ -81,21 +85,20 @@ class Screen(Thread):
|
|||||||
|
|
||||||
row = 1
|
row = 1
|
||||||
|
|
||||||
header = ' icao'
|
header = " icao"
|
||||||
for c, cw in self.columns:
|
for c, cw in self.columns:
|
||||||
header += (cw-len(c))*' ' + c
|
header += (cw - len(c)) * " " + c
|
||||||
|
|
||||||
# fill end with spaces
|
# fill end with spaces
|
||||||
header += (self.scr_w - 2 - len(header)) * ' '
|
header += (self.scr_w - 2 - len(header)) * " "
|
||||||
|
|
||||||
if len(header) > self.scr_w - 2:
|
if len(header) > self.scr_w - 2:
|
||||||
header = header[:self.scr_w-3] + '>'
|
header = header[: self.scr_w - 3] + ">"
|
||||||
|
|
||||||
|
|
||||||
self.screen.addstr(row, 1, header)
|
self.screen.addstr(row, 1, header)
|
||||||
|
|
||||||
row +=1
|
row += 1
|
||||||
self.screen.addstr(row, 1, '-'*(self.scr_w-2))
|
self.screen.addstr(row, 1, "-" * (self.scr_w - 2))
|
||||||
|
|
||||||
icaos = np.array(list(self.acs.keys()))
|
icaos = np.array(list(self.acs.keys()))
|
||||||
icaos = np.sort(icaos)
|
icaos = np.sort(icaos)
|
||||||
@@ -105,10 +108,10 @@ class Screen(Thread):
|
|||||||
idx = row + self.offset - 3
|
idx = row + self.offset - 3
|
||||||
|
|
||||||
if idx > len(icaos) - 1:
|
if idx > len(icaos) - 1:
|
||||||
line = ' '*(self.scr_w-2)
|
line = " " * (self.scr_w - 2)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
line = ''
|
line = ""
|
||||||
|
|
||||||
icao = icaos[idx]
|
icao = icaos[idx]
|
||||||
ac = self.acs[icao]
|
ac = self.acs[icao]
|
||||||
@@ -116,22 +119,22 @@ class Screen(Thread):
|
|||||||
line += icao
|
line += icao
|
||||||
|
|
||||||
for c, cw in self.columns:
|
for c, cw in self.columns:
|
||||||
if c=='|':
|
if c == "|":
|
||||||
val = '|'
|
val = "|"
|
||||||
elif c=='live':
|
elif c == "live":
|
||||||
val = str(int(time.time() - ac[c]))+'s'
|
val = str(ac[c] - int(time.time())) + "s"
|
||||||
elif ac[c] is None:
|
elif ac[c] is None:
|
||||||
val = ''
|
val = ""
|
||||||
else:
|
else:
|
||||||
val = ac[c]
|
val = ac[c]
|
||||||
val_str = str(val)
|
val_str = str(val)
|
||||||
line += (cw-len(val_str))*' ' + val_str
|
line += (cw - len(val_str)) * " " + val_str
|
||||||
|
|
||||||
# fill end with spaces
|
# fill end with spaces
|
||||||
line += (self.scr_w - 2 - len(line)) * ' '
|
line += (self.scr_w - 2 - len(line)) * " "
|
||||||
|
|
||||||
if len(line) > self.scr_w - 2:
|
if len(line) > self.scr_w - 2:
|
||||||
line = line[:self.scr_w-3] + '>'
|
line = line[: self.scr_w - 3] + ">"
|
||||||
|
|
||||||
if (icao is not None) and (self.lock_icao == icao):
|
if (icao is not None) and (self.lock_icao == icao):
|
||||||
self.screen.addstr(row, 1, line, curses.A_STANDOUT)
|
self.screen.addstr(row, 1, line, curses.A_STANDOUT)
|
||||||
@@ -140,15 +143,15 @@ class Screen(Thread):
|
|||||||
else:
|
else:
|
||||||
self.screen.addstr(row, 1, line)
|
self.screen.addstr(row, 1, line)
|
||||||
|
|
||||||
self.screen.addstr(self.scr_h-3, 1, '-'*(self.scr_w-2))
|
self.screen.addstr(self.scr_h - 3, 1, "-" * (self.scr_w - 2))
|
||||||
|
|
||||||
total_page = len(icaos) // (self.scr_h - 4) + 1
|
total_page = len(icaos) // (self.scr_h - 4) + 1
|
||||||
current_page = self.offset // (self.scr_h - 4) + 1
|
current_page = self.offset // (self.scr_h - 4) + 1
|
||||||
self.screen.addstr(self.scr_h-2, 1, '(%d / %d)' % (current_page, total_page))
|
self.screen.addstr(self.scr_h - 2, 1, "(%d / %d)" % (current_page, total_page))
|
||||||
|
|
||||||
self.reset_cursor_pos()
|
self.reset_cursor_pos()
|
||||||
|
|
||||||
def run(self):
|
def kye_handling(self):
|
||||||
self.draw_frame()
|
self.draw_frame()
|
||||||
self.scr_h, self.scr_w = self.screen.getmaxyx()
|
self.scr_h, self.scr_w = self.screen.getmaxyx()
|
||||||
|
|
||||||
@@ -168,7 +171,7 @@ class Screen(Thread):
|
|||||||
self.offset = offset_intent
|
self.offset = offset_intent
|
||||||
else:
|
else:
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
elif c == curses.KEY_DOWN :
|
elif c == curses.KEY_DOWN:
|
||||||
y_intent = self.y + 1
|
y_intent = self.y + 1
|
||||||
if y_intent < self.scr_h - 3:
|
if y_intent < self.scr_h - 3:
|
||||||
self.y = y_intent
|
self.y = y_intent
|
||||||
@@ -178,6 +181,30 @@ class Screen(Thread):
|
|||||||
self.y = y_intent
|
self.y = y_intent
|
||||||
elif c == curses.KEY_ENTER or c == 10 or c == 13:
|
elif c == curses.KEY_ENTER or c == 10 or c == 13:
|
||||||
self.lock_icao = (self.screen.instr(self.y, 1, 6)).decode()
|
self.lock_icao = (self.screen.instr(self.y, 1, 6)).decode()
|
||||||
|
elif c == 27: # escape key
|
||||||
|
self.lock_icao = None
|
||||||
elif c == curses.KEY_F5:
|
elif c == curses.KEY_F5:
|
||||||
self.screen.refresh()
|
self.screen.refresh()
|
||||||
self.draw_frame()
|
self.draw_frame()
|
||||||
|
|
||||||
|
def run(self, ac_pipe_out):
|
||||||
|
local_buffer = []
|
||||||
|
key_thread = threading.Thread(target=self.kye_handling)
|
||||||
|
key_thread.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
while ac_pipe_out.poll():
|
||||||
|
acs = ac_pipe_out.recv()
|
||||||
|
local_buffer.append(acs)
|
||||||
|
|
||||||
|
for acs in local_buffer:
|
||||||
|
self.update_ac(acs)
|
||||||
|
|
||||||
|
local_buffer = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.update()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
time.sleep(0.001)
|
||||||
|
|||||||
91
pyModeS/streamer/source.py
Normal file
91
pyModeS/streamer/source.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import pyModeS as pms
|
||||||
|
from pyModeS.extra.tcpclient import TcpClient
|
||||||
|
from pyModeS.extra.rtlreader import RtlReader
|
||||||
|
|
||||||
|
|
||||||
|
class NetSource(TcpClient):
|
||||||
|
def __init__(self, host, port, rawtype):
|
||||||
|
super(NetSource, self).__init__(host, port, rawtype)
|
||||||
|
self.reset_local_buffer()
|
||||||
|
|
||||||
|
def reset_local_buffer(self):
|
||||||
|
self.local_buffer_adsb_msg = []
|
||||||
|
self.local_buffer_adsb_ts = []
|
||||||
|
self.local_buffer_commb_msg = []
|
||||||
|
self.local_buffer_commb_ts = []
|
||||||
|
|
||||||
|
def handle_messages(self, messages):
|
||||||
|
|
||||||
|
if self.stop_flag.value is True:
|
||||||
|
self.stop()
|
||||||
|
return
|
||||||
|
|
||||||
|
for msg, t in messages:
|
||||||
|
if len(msg) < 28: # only process long messages
|
||||||
|
continue
|
||||||
|
|
||||||
|
df = pms.df(msg)
|
||||||
|
|
||||||
|
if df == 17 or df == 18:
|
||||||
|
self.local_buffer_adsb_msg.append(msg)
|
||||||
|
self.local_buffer_adsb_ts.append(t)
|
||||||
|
elif df == 20 or df == 21:
|
||||||
|
self.local_buffer_commb_msg.append(msg)
|
||||||
|
self.local_buffer_commb_ts.append(t)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(self.local_buffer_adsb_msg) > 1:
|
||||||
|
self.raw_pipe_in.send(
|
||||||
|
{
|
||||||
|
"adsb_ts": self.local_buffer_adsb_ts,
|
||||||
|
"adsb_msg": self.local_buffer_adsb_msg,
|
||||||
|
"commb_ts": self.local_buffer_commb_ts,
|
||||||
|
"commb_msg": self.local_buffer_commb_msg,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.reset_local_buffer()
|
||||||
|
|
||||||
|
|
||||||
|
class RtlSdrSource(RtlReader):
|
||||||
|
def __init__(self):
|
||||||
|
super(RtlSdrSource, self).__init__()
|
||||||
|
self.reset_local_buffer()
|
||||||
|
|
||||||
|
def reset_local_buffer(self):
|
||||||
|
self.local_buffer_adsb_msg = []
|
||||||
|
self.local_buffer_adsb_ts = []
|
||||||
|
self.local_buffer_commb_msg = []
|
||||||
|
self.local_buffer_commb_ts = []
|
||||||
|
|
||||||
|
def handle_messages(self, messages):
|
||||||
|
|
||||||
|
if self.stop_flag.value is True:
|
||||||
|
self.stop()
|
||||||
|
return
|
||||||
|
|
||||||
|
for msg, t in messages:
|
||||||
|
if len(msg) < 28: # only process long messages
|
||||||
|
continue
|
||||||
|
|
||||||
|
df = pms.df(msg)
|
||||||
|
|
||||||
|
if df == 17 or df == 18:
|
||||||
|
self.local_buffer_adsb_msg.append(msg)
|
||||||
|
self.local_buffer_adsb_ts.append(t)
|
||||||
|
elif df == 20 or df == 21:
|
||||||
|
self.local_buffer_commb_msg.append(msg)
|
||||||
|
self.local_buffer_commb_ts.append(t)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(self.local_buffer_adsb_msg) > 1:
|
||||||
|
self.raw_pipe_in.send(
|
||||||
|
{
|
||||||
|
"adsb_ts": self.local_buffer_adsb_ts,
|
||||||
|
"adsb_msg": self.local_buffer_adsb_msg,
|
||||||
|
"commb_ts": self.local_buffer_commb_ts,
|
||||||
|
"commb_msg": self.local_buffer_commb_msg,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.reset_local_buffer()
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import datetime
|
|
||||||
import csv
|
|
||||||
import pyModeS as pms
|
|
||||||
|
|
||||||
class Stream():
|
|
||||||
def __init__(self, lat0, lon0, dumpto=None):
|
|
||||||
|
|
||||||
self.acs = dict()
|
|
||||||
|
|
||||||
self.lat0 = lat0
|
|
||||||
self.lon0 = lon0
|
|
||||||
|
|
||||||
self.t = 0
|
|
||||||
self.cache_timeout = 60 # seconds
|
|
||||||
|
|
||||||
|
|
||||||
if dumpto is not None and os.path.isdir(dumpto):
|
|
||||||
self.dumpto = dumpto
|
|
||||||
else:
|
|
||||||
self.dumpto = None
|
|
||||||
|
|
||||||
|
|
||||||
def process_raw(self, adsb_ts, adsb_msgs, commb_ts, commb_msgs, tnow=None):
|
|
||||||
"""process a chunk of adsb and commb messages recieved in the same
|
|
||||||
time period.
|
|
||||||
"""
|
|
||||||
if tnow is None:
|
|
||||||
tnow = time.time()
|
|
||||||
|
|
||||||
self.t = tnow
|
|
||||||
|
|
||||||
local_updated_acs_buffer = []
|
|
||||||
output_buffer = []
|
|
||||||
|
|
||||||
# process adsb message
|
|
||||||
for t, msg in zip(adsb_ts, adsb_msgs):
|
|
||||||
icao = pms.icao(msg)
|
|
||||||
tc = pms.adsb.typecode(msg)
|
|
||||||
|
|
||||||
if icao not in self.acs:
|
|
||||||
self.acs[icao] = {
|
|
||||||
'live': None,
|
|
||||||
'call': None,
|
|
||||||
'lat': None,
|
|
||||||
'lon': None,
|
|
||||||
'alt': None,
|
|
||||||
'gs': None,
|
|
||||||
'trk': None,
|
|
||||||
'roc': None,
|
|
||||||
'tas': None,
|
|
||||||
'roll': None,
|
|
||||||
'rtrk': None,
|
|
||||||
'ias': None,
|
|
||||||
'mach': None,
|
|
||||||
'hdg': None,
|
|
||||||
'ver' : None,
|
|
||||||
'HPL' : None,
|
|
||||||
'RCu' : None,
|
|
||||||
'RCv' : None,
|
|
||||||
'HVE' : None,
|
|
||||||
'VVE' : None,
|
|
||||||
'Rc' : None,
|
|
||||||
'VPL' : None,
|
|
||||||
'EPU' : None,
|
|
||||||
'VEPU' : None,
|
|
||||||
'HFOMr' : None,
|
|
||||||
'VFOMr' : None,
|
|
||||||
'PE_RCu' : None,
|
|
||||||
'PE_VPL' : None,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.acs[icao]['t'] = t
|
|
||||||
self.acs[icao]['live'] = int(t)
|
|
||||||
|
|
||||||
if 1 <= tc <= 4:
|
|
||||||
cs = pms.adsb.callsign(msg)
|
|
||||||
self.acs[icao]['call'] = cs
|
|
||||||
output_buffer.append([t, icao, 'cs', cs])
|
|
||||||
|
|
||||||
if (5 <= tc <= 8) or (tc == 19):
|
|
||||||
vdata = pms.adsb.velocity(msg)
|
|
||||||
if vdata is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
spd, trk, roc, tag = vdata
|
|
||||||
if tag != 'GS':
|
|
||||||
continue
|
|
||||||
if (spd is None) or (trk is None):
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.acs[icao]['gs'] = spd
|
|
||||||
self.acs[icao]['trk'] = trk
|
|
||||||
self.acs[icao]['roc'] = roc
|
|
||||||
self.acs[icao]['tv'] = t
|
|
||||||
|
|
||||||
output_buffer.append([t, icao, 'gs', spd])
|
|
||||||
output_buffer.append([t, icao, 'trk', trk])
|
|
||||||
output_buffer.append([t, icao, 'roc', roc])
|
|
||||||
|
|
||||||
|
|
||||||
if (5 <= tc <= 18):
|
|
||||||
oe = pms.adsb.oe_flag(msg)
|
|
||||||
self.acs[icao][oe] = msg
|
|
||||||
self.acs[icao]['t'+str(oe)] = t
|
|
||||||
|
|
||||||
if ('tpos' in self.acs[icao]) and (t - self.acs[icao]['tpos'] < 180):
|
|
||||||
# use single message decoding
|
|
||||||
rlat = self.acs[icao]['lat']
|
|
||||||
rlon = self.acs[icao]['lon']
|
|
||||||
latlon = pms.adsb.position_with_ref(msg, rlat, rlon)
|
|
||||||
elif ('t0' in self.acs[icao]) and ('t1' in self.acs[icao]) and \
|
|
||||||
(abs(self.acs[icao]['t0'] - self.acs[icao]['t1']) < 10):
|
|
||||||
# use multi message decoding
|
|
||||||
try:
|
|
||||||
latlon = pms.adsb.position(
|
|
||||||
self.acs[icao][0],
|
|
||||||
self.acs[icao][1],
|
|
||||||
self.acs[icao]['t0'],
|
|
||||||
self.acs[icao]['t1'],
|
|
||||||
self.lat0, self.lon0
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
# mix of surface and airborne position message
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
latlon = None
|
|
||||||
|
|
||||||
if latlon is not None:
|
|
||||||
self.acs[icao]['tpos'] = t
|
|
||||||
self.acs[icao]['lat'] = latlon[0]
|
|
||||||
self.acs[icao]['lon'] = latlon[1]
|
|
||||||
|
|
||||||
alt = pms.adsb.altitude(msg)
|
|
||||||
self.acs[icao]['alt'] = alt
|
|
||||||
|
|
||||||
output_buffer.append([t, icao, 'lat', latlon[0]])
|
|
||||||
output_buffer.append([t, icao, 'lon', latlon[1]])
|
|
||||||
output_buffer.append([t, icao, 'alt', alt])
|
|
||||||
|
|
||||||
local_updated_acs_buffer.append(icao)
|
|
||||||
|
|
||||||
# Uncertainty & accuracy
|
|
||||||
ac = self.acs[icao]
|
|
||||||
|
|
||||||
if 9 <= tc <= 18:
|
|
||||||
ac['nic_bc'] = pms.adsb.nic_b(msg)
|
|
||||||
|
|
||||||
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
|
|
||||||
ac['HPL'], ac['RCu'], ac['RCv'] = pms.adsb.nuc_p(msg)
|
|
||||||
|
|
||||||
if (ac['ver'] == 1) and ('nic_s' in ac.keys()):
|
|
||||||
ac['Rc'], ac['VPL'] = pms.adsb.nic_v1(msg, ac['nic_s'])
|
|
||||||
elif (ac['ver'] == 2) and ('nic_a' in ac.keys()) and ('nic_bc' in ac.keys()):
|
|
||||||
ac['Rc'] = pms.adsb.nic_v2(msg, ac['nic_a'], ac['nic_bc'])
|
|
||||||
|
|
||||||
if tc == 19:
|
|
||||||
ac['HVE'], ac['VVE'] = pms.adsb.nuc_v(msg)
|
|
||||||
if ac['ver'] in [1, 2]:
|
|
||||||
ac['HFOMr'], ac['VFOMr'] = pms.adsb.nac_v(msg)
|
|
||||||
|
|
||||||
if tc == 29:
|
|
||||||
ac['PE_RCu'], ac['PE_VPL'], ac['base'] = pms.adsb.sil(msg, ac['ver'])
|
|
||||||
ac['EPU'], ac['VEPU'] = pms.adsb.nac_p(msg)
|
|
||||||
|
|
||||||
if tc == 31:
|
|
||||||
ac['ver'] = pms.adsb.version(msg)
|
|
||||||
ac['EPU'], ac['VEPU'] = pms.adsb.nac_p(msg)
|
|
||||||
ac['PE_RCu'], ac['PE_VPL'], ac['sil_base'] = pms.adsb.sil(msg, ac['ver'])
|
|
||||||
|
|
||||||
if ac['ver'] == 1:
|
|
||||||
ac['nic_s'] = pms.adsb.nic_s(msg)
|
|
||||||
elif ac['ver'] == 2:
|
|
||||||
ac['nic_a'], ac['nic_bc'] = pms.adsb.nic_a_c(msg)
|
|
||||||
|
|
||||||
|
|
||||||
# process commb message
|
|
||||||
for t, msg in zip(commb_ts, commb_msgs):
|
|
||||||
icao = pms.icao(msg)
|
|
||||||
|
|
||||||
if icao not in self.acs:
|
|
||||||
continue
|
|
||||||
|
|
||||||
bds = pms.bds.infer(msg)
|
|
||||||
|
|
||||||
if bds == 'BDS50':
|
|
||||||
roll50 = pms.commb.roll50(msg)
|
|
||||||
trk50 = pms.commb.trk50(msg)
|
|
||||||
rtrk50 = pms.commb.rtrk50(msg)
|
|
||||||
gs50 = pms.commb.gs50(msg)
|
|
||||||
tas50 = pms.commb.tas50(msg)
|
|
||||||
|
|
||||||
self.acs[icao]['t50'] = t
|
|
||||||
if tas50:
|
|
||||||
self.acs[icao]['tas'] = tas50
|
|
||||||
output_buffer.append([t, icao, 'tas50', tas50])
|
|
||||||
if roll50:
|
|
||||||
self.acs[icao]['roll'] = roll50
|
|
||||||
output_buffer.append([t, icao, 'roll50', roll50])
|
|
||||||
if rtrk50:
|
|
||||||
self.acs[icao]['rtrk'] = rtrk50
|
|
||||||
output_buffer.append([t, icao, 'rtrk50', rtrk50])
|
|
||||||
|
|
||||||
if trk50:
|
|
||||||
output_buffer.append([t, icao, 'trk50', trk50])
|
|
||||||
if gs50:
|
|
||||||
output_buffer.append([t, icao, 'gs50', gs50])
|
|
||||||
|
|
||||||
elif bds == 'BDS60':
|
|
||||||
ias60 = pms.commb.ias60(msg)
|
|
||||||
hdg60 = pms.commb.hdg60(msg)
|
|
||||||
mach60 = pms.commb.mach60(msg)
|
|
||||||
roc60baro = pms.commb.vr60baro(msg)
|
|
||||||
roc60ins = pms.commb.vr60ins(msg)
|
|
||||||
|
|
||||||
if ias60 or hdg60 or mach60:
|
|
||||||
self.acs[icao]['t60'] = t
|
|
||||||
if ias60:
|
|
||||||
self.acs[icao]['ias'] = ias60
|
|
||||||
if hdg60:
|
|
||||||
self.acs[icao]['hdg'] = hdg60
|
|
||||||
if mach60:
|
|
||||||
self.acs[icao]['mach'] = mach60
|
|
||||||
|
|
||||||
if roc60baro:
|
|
||||||
output_buffer.append([t, icao, 'roc60baro', roc60baro])
|
|
||||||
if roc60ins:
|
|
||||||
output_buffer.append([t, icao, 'roc60ins', roc60ins])
|
|
||||||
|
|
||||||
# clear up old data
|
|
||||||
for icao in list(self.acs.keys()):
|
|
||||||
if self.t - self.acs[icao]['live'] > self.cache_timeout:
|
|
||||||
del self.acs[icao]
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self.dumpto is not None:
|
|
||||||
dh = str(datetime.datetime.now().strftime("%Y%m%d_%H"))
|
|
||||||
fn = self.dumpto + '/pymodes_dump_%s.csv' % dh
|
|
||||||
output_buffer.sort(key=lambda x: x[0])
|
|
||||||
with open(fn, "a") as f:
|
|
||||||
writer = csv.writer(f)
|
|
||||||
writer.writerows(output_buffer)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_aircraft(self):
|
|
||||||
"""all aircraft that are stored in memeory"""
|
|
||||||
acs = self.acs
|
|
||||||
icaos = list(acs.keys())
|
|
||||||
for icao in icaos:
|
|
||||||
if acs[icao]['lat'] is None:
|
|
||||||
acs.pop(icao)
|
|
||||||
return acs
|
|
||||||
23
setup.py
23
setup.py
@@ -6,7 +6,7 @@ https://github.com/pypa/sampleproject
|
|||||||
|
|
||||||
Steps for deploying a new verison:
|
Steps for deploying a new verison:
|
||||||
1. Increase the version number
|
1. Increase the version number
|
||||||
2. remove the old deployment under [dist] folder
|
2. remove the old deployment under [dist] and [build] folder
|
||||||
3. run: python setup.py sdist
|
3. run: python setup.py sdist
|
||||||
run: python setup.py bdist_wheel --universal
|
run: python setup.py bdist_wheel --universal
|
||||||
4. twine upload dist/*
|
4. twine upload dist/*
|
||||||
@@ -15,6 +15,13 @@ Steps for deploying a new verison:
|
|||||||
# Always prefer setuptools over distutils
|
# Always prefer setuptools over distutils
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
# Compile some parts
|
||||||
|
from setuptools.extension import Extension
|
||||||
|
from Cython.Build import cythonize
|
||||||
|
|
||||||
|
extensions = [Extension("pyModeS.decoder.c_common", ["pyModeS/decoder/c_common.pyx"])]
|
||||||
|
|
||||||
|
|
||||||
# To use a consistent encoding
|
# To use a consistent encoding
|
||||||
from codecs import open
|
from codecs import open
|
||||||
from os import path
|
from os import path
|
||||||
@@ -30,7 +37,7 @@ setup(
|
|||||||
# Versions should comply with PEP440. For a discussion on single-sourcing
|
# Versions should comply with PEP440. For a discussion on single-sourcing
|
||||||
# the version across setup.py and the project code, see
|
# the version across setup.py and the project code, see
|
||||||
# https://packaging.python.org/en/latest/single_source_version.html
|
# https://packaging.python.org/en/latest/single_source_version.html
|
||||||
version="2.2",
|
version="2.4",
|
||||||
description="Python Mode-S and ADS-B Decoder",
|
description="Python Mode-S and ADS-B Decoder",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
# The project's main homepage.
|
# The project's main homepage.
|
||||||
@@ -49,21 +56,17 @@ setup(
|
|||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
# Indicate who your project is intended for
|
# Indicate who your project is intended for
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Topic :: Software Development :: Build Tools",
|
"Topic :: Software Development :: Libraries",
|
||||||
# Pick your license as you wish (should match "license" above)
|
# Pick your license as you wish (should match "license" above)
|
||||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
# Specify the Python versions you support here. In particular, ensure
|
# Specify the Python versions you support here. In particular, ensure
|
||||||
# that you indicate whether you support Python 2, Python 3 or both.
|
# that you indicate whether you support Python 2, Python 3 or both.
|
||||||
"Programming Language :: Python :: 2",
|
"Programming Language :: Python :: 2",
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.3",
|
|
||||||
"Programming Language :: Python :: 3.4",
|
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
|
||||||
],
|
],
|
||||||
|
ext_modules=cythonize(extensions),
|
||||||
# What does your project relate to?
|
# What does your project relate to?
|
||||||
keywords="Mode-S ADS-B EHS decoding",
|
keywords="Mode-S ADS-B EHS ELS Comm-B",
|
||||||
# You can just specify the packages manually here if your project is
|
# You can just specify the packages manually here if your project is
|
||||||
# simple. Or you can use find_packages().
|
# simple. Or you can use find_packages().
|
||||||
packages=find_packages(exclude=["contrib", "docs", "tests"]),
|
packages=find_packages(exclude=["contrib", "docs", "tests"]),
|
||||||
@@ -74,7 +77,7 @@ setup(
|
|||||||
# your project is installed. For an analysis of "install_requires" vs pip's
|
# your project is installed. For an analysis of "install_requires" vs pip's
|
||||||
# requirements files see:
|
# requirements files see:
|
||||||
# https://packaging.python.org/en/latest/requirements.html
|
# https://packaging.python.org/en/latest/requirements.html
|
||||||
install_requires=["numpy", "argparse"],
|
install_requires=["numpy", "argparse", "pyzmq", "pyrtlsdr"],
|
||||||
# List additional groups of dependencies here (e.g. development
|
# List additional groups of dependencies here (e.g. development
|
||||||
# dependencies). You can install these using the following syntax,
|
# dependencies). You can install these using the following syntax,
|
||||||
# for example:
|
# for example:
|
||||||
|
|||||||
130
tests/benchmark.py
Normal file
130
tests/benchmark.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import pandas as pd
|
||||||
|
from tqdm import tqdm
|
||||||
|
from pyModeS.decoder import adsb
|
||||||
|
|
||||||
|
fin = sys.argv[1]
|
||||||
|
|
||||||
|
df = pd.read_csv(fin, names=["ts", "df", "icao", "msg"])
|
||||||
|
df_adsb = df[df["df"] == 17].copy()
|
||||||
|
|
||||||
|
total = df_adsb.shape[0]
|
||||||
|
|
||||||
|
|
||||||
|
def native():
|
||||||
|
|
||||||
|
from pyModeS.decoder import common
|
||||||
|
|
||||||
|
# airborne position
|
||||||
|
m_air_0 = None
|
||||||
|
m_air_1 = None
|
||||||
|
|
||||||
|
# surface position
|
||||||
|
m_surf_0 = None
|
||||||
|
m_surf_1 = None
|
||||||
|
|
||||||
|
for i, r in tqdm(df_adsb.iterrows(), total=total):
|
||||||
|
ts = r.ts
|
||||||
|
m = r.msg
|
||||||
|
|
||||||
|
downlink_format = common.df(m)
|
||||||
|
crc = common.crc(m)
|
||||||
|
icao = adsb.icao(m)
|
||||||
|
tc = adsb.typecode(m)
|
||||||
|
|
||||||
|
if 1 <= tc <= 4:
|
||||||
|
category = adsb.category(m)
|
||||||
|
callsign = adsb.callsign(m)
|
||||||
|
if tc == 19:
|
||||||
|
velocity = adsb.velocity(m)
|
||||||
|
|
||||||
|
if 5 <= tc <= 8:
|
||||||
|
if adsb.oe_flag(m):
|
||||||
|
m_surf_1 = m
|
||||||
|
t1 = ts
|
||||||
|
else:
|
||||||
|
m_surf_0 = m
|
||||||
|
t0 = ts
|
||||||
|
|
||||||
|
if m_surf_0 and m_surf_1:
|
||||||
|
position = adsb.surface_position(
|
||||||
|
m_surf_0, m_surf_1, t0, t1, 50.01, 4.35
|
||||||
|
)
|
||||||
|
altitude = adsb.altitude(m)
|
||||||
|
|
||||||
|
if 9 <= tc <= 18:
|
||||||
|
if adsb.oe_flag(m):
|
||||||
|
m_air_1 = m
|
||||||
|
t1 = ts
|
||||||
|
else:
|
||||||
|
m_air_0 = m
|
||||||
|
t0 = ts
|
||||||
|
|
||||||
|
if m_air_0 and m_air_1:
|
||||||
|
position = adsb.position(m_air_0, m_air_1, t0, t1)
|
||||||
|
altitude = adsb.altitude(m)
|
||||||
|
|
||||||
|
|
||||||
|
def cython():
|
||||||
|
|
||||||
|
from pyModeS.decoder import c_common as common
|
||||||
|
|
||||||
|
# airborne position
|
||||||
|
m_air_0 = None
|
||||||
|
m_air_1 = None
|
||||||
|
|
||||||
|
# surface position
|
||||||
|
m_surf_0 = None
|
||||||
|
m_surf_1 = None
|
||||||
|
|
||||||
|
for i, r in tqdm(df_adsb.iterrows(), total=total):
|
||||||
|
ts = r.ts
|
||||||
|
m = r.msg
|
||||||
|
|
||||||
|
downlink_format = common.df(m)
|
||||||
|
crc = common.crc(m)
|
||||||
|
icao = adsb.icao(m)
|
||||||
|
tc = adsb.typecode(m)
|
||||||
|
|
||||||
|
if 1 <= tc <= 4:
|
||||||
|
category = adsb.category(m)
|
||||||
|
callsign = adsb.callsign(m)
|
||||||
|
if tc == 19:
|
||||||
|
velocity = adsb.velocity(m)
|
||||||
|
|
||||||
|
if 5 <= tc <= 8:
|
||||||
|
if adsb.oe_flag(m):
|
||||||
|
m_surf_1 = m
|
||||||
|
t1 = ts
|
||||||
|
else:
|
||||||
|
m_surf_0 = m
|
||||||
|
t0 = ts
|
||||||
|
|
||||||
|
if m_surf_0 and m_surf_1:
|
||||||
|
position = adsb.surface_position(
|
||||||
|
m_surf_0, m_surf_1, t0, t1, 50.01, 4.35
|
||||||
|
)
|
||||||
|
altitude = adsb.altitude(m)
|
||||||
|
|
||||||
|
if 9 <= tc <= 18:
|
||||||
|
if adsb.oe_flag(m):
|
||||||
|
m_air_1 = m
|
||||||
|
t1 = ts
|
||||||
|
else:
|
||||||
|
m_air_0 = m
|
||||||
|
t0 = ts
|
||||||
|
|
||||||
|
if m_air_0 and m_air_1:
|
||||||
|
position = adsb.position(m_air_0, m_air_1, t0, t1)
|
||||||
|
altitude = adsb.altitude(m)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
t1 = time.time()
|
||||||
|
native()
|
||||||
|
dt1 = time.time() - t1
|
||||||
|
|
||||||
|
t2 = time.time()
|
||||||
|
cython()
|
||||||
|
dt2 = time.time() - t2
|
||||||
@@ -1,41 +1,46 @@
|
|||||||
from __future__ import print_function
|
import sys
|
||||||
from pyModeS import adsb, ehs
|
import time
|
||||||
|
import csv
|
||||||
|
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == "cython":
|
||||||
|
from pyModeS.c_decoder import adsb
|
||||||
|
else:
|
||||||
|
from pyModeS.decoder import adsb
|
||||||
|
|
||||||
|
print("===== Decode ADS-B sample data=====")
|
||||||
|
|
||||||
|
f = open("tests/data/sample_data_adsb.csv", "rt")
|
||||||
|
|
||||||
|
msg0 = None
|
||||||
|
msg1 = None
|
||||||
|
|
||||||
|
tstart = time.time()
|
||||||
|
for i, r in enumerate(csv.reader(f)):
|
||||||
|
|
||||||
|
ts = int(r[0])
|
||||||
|
m = r[1].encode()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
# === Decode sample data file ===
|
dt = time.time() - tstart
|
||||||
|
|
||||||
def adsb_decode_all(n=None):
|
print("Execution time: {} seconds".format(dt))
|
||||||
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)
|
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ from pyModeS import commb, common, bds
|
|||||||
|
|
||||||
# === Decode sample data file ===
|
# === Decode sample data file ===
|
||||||
|
|
||||||
|
|
||||||
def bds_info(BDS, m):
|
def bds_info(BDS, m):
|
||||||
if BDS == "BDS10":
|
if BDS == "BDS10":
|
||||||
info = [commb.ovc10(m)]
|
info = [commb.ovc10(m)]
|
||||||
|
|
||||||
elif BDS == "BDS17":
|
elif BDS == "BDS17":
|
||||||
info = ([i[-2:] for i in commb.cap17(m)])
|
info = [i[-2:] for i in commb.cap17(m)]
|
||||||
|
|
||||||
elif BDS == "BDS20":
|
elif BDS == "BDS20":
|
||||||
info = [commb.cs20(m)]
|
info = [commb.cs20(m)]
|
||||||
@@ -20,13 +21,30 @@ def bds_info(BDS, m):
|
|||||||
info = (commb.wind44(m), commb.temp44(m), commb.p44(m), commb.hum44(m))
|
info = (commb.wind44(m), commb.temp44(m), commb.p44(m), commb.hum44(m))
|
||||||
|
|
||||||
elif BDS == "BDS44REV":
|
elif BDS == "BDS44REV":
|
||||||
info = (commb.wind44(m, rev=True), commb.temp44(m, rev=True), commb.p44(m, rev=True), commb.hum44(m, rev=True))
|
info = (
|
||||||
|
commb.wind44(m, rev=True),
|
||||||
|
commb.temp44(m, rev=True),
|
||||||
|
commb.p44(m, rev=True),
|
||||||
|
commb.hum44(m, rev=True),
|
||||||
|
)
|
||||||
|
|
||||||
elif BDS == "BDS50":
|
elif BDS == "BDS50":
|
||||||
info = (commb.roll50(m), commb.trk50(m), commb.gs50(m), commb.rtrk50(m), commb.tas50(m))
|
info = (
|
||||||
|
commb.roll50(m),
|
||||||
|
commb.trk50(m),
|
||||||
|
commb.gs50(m),
|
||||||
|
commb.rtrk50(m),
|
||||||
|
commb.tas50(m),
|
||||||
|
)
|
||||||
|
|
||||||
elif BDS == "BDS60":
|
elif BDS == "BDS60":
|
||||||
info = (commb.hdg60(m), commb.ias60(m), commb.mach60(m), commb.vr60baro(m), commb.vr60ins(m))
|
info = (
|
||||||
|
commb.hdg60(m),
|
||||||
|
commb.ias60(m),
|
||||||
|
commb.mach60(m),
|
||||||
|
commb.vr60baro(m),
|
||||||
|
commb.vr60ins(m),
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
info = None
|
info = None
|
||||||
@@ -39,8 +57,7 @@ def commb_decode_all(df, n=None):
|
|||||||
|
|
||||||
print("===== Decode Comm-B sample data (DF=%s)=====" % df)
|
print("===== Decode Comm-B sample data (DF=%s)=====" % df)
|
||||||
|
|
||||||
f = open('tests/data/sample_data_commb_df%s.csv' % df, 'rt')
|
f = open("tests/data/sample_data_commb_df%s.csv" % df, "rt")
|
||||||
|
|
||||||
|
|
||||||
for i, r in enumerate(csv.reader(f)):
|
for i, r in enumerate(csv.reader(f)):
|
||||||
if n and i > n:
|
if n and i > n:
|
||||||
@@ -55,21 +72,21 @@ def commb_decode_all(df, n=None):
|
|||||||
code = common.altcode(m) if df == 20 else common.idcode(m)
|
code = common.altcode(m) if df == 20 else common.idcode(m)
|
||||||
|
|
||||||
if not BDS:
|
if not BDS:
|
||||||
print(ts, m, icao, df, '%5s'%code, 'UNKNOWN')
|
print(ts, m, icao, df, "%5s" % code, "UNKNOWN")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(BDS.split(",")) > 1:
|
if len(BDS.split(",")) > 1:
|
||||||
print(ts, m, icao, df, '%5s' % code, end=' ')
|
print(ts, m, icao, df, "%5s" % code, end=" ")
|
||||||
for i, _bds in enumerate(BDS.split(",")):
|
for i, _bds in enumerate(BDS.split(",")):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
print(_bds, *bds_info(_bds, m))
|
print(_bds, *bds_info(_bds, m))
|
||||||
else:
|
else:
|
||||||
print(' ' * 55, _bds, *bds_info(_bds, m))
|
print(" " * 55, _bds, *bds_info(_bds, m))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(ts, m, icao, df, '%5s'%code, BDS, *bds_info(BDS, m))
|
print(ts, m, icao, df, "%5s" % code, BDS, *bds_info(BDS, m))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
commb_decode_all(df=20, n=100)
|
commb_decode_all(df=20, n=100)
|
||||||
commb_decode_all(df=21, n=100)
|
commb_decode_all(df=21, n=100)
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from pyModeS import adsb
|
|||||||
|
|
||||||
# === TEST ADS-B package ===
|
# === TEST ADS-B package ===
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_icao():
|
def test_adsb_icao():
|
||||||
assert adsb.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
assert adsb.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_category():
|
def test_adsb_category():
|
||||||
assert adsb.category("8D406B902015A678D4D220AA4BDA") == 5
|
assert adsb.category("8D406B902015A678D4D220AA4BDA") == 0
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_callsign():
|
def test_adsb_callsign():
|
||||||
@@ -15,9 +16,22 @@ def test_adsb_callsign():
|
|||||||
|
|
||||||
|
|
||||||
def test_adsb_position():
|
def test_adsb_position():
|
||||||
pos = adsb.position("8D40058B58C901375147EFD09357",
|
pos = adsb.position(
|
||||||
"8D40058B58C904A87F402D3B8C59",
|
"8D40058B58C901375147EFD09357",
|
||||||
1446332400, 1446332405)
|
"8D40058B58C904A87F402D3B8C59",
|
||||||
|
1446332400,
|
||||||
|
1446332405,
|
||||||
|
)
|
||||||
|
assert pos == (49.81755, 6.08442)
|
||||||
|
|
||||||
|
|
||||||
|
def test_adsb_position_swap_odd_even():
|
||||||
|
pos = adsb.position(
|
||||||
|
"8D40058B58C904A87F402D3B8C59",
|
||||||
|
"8D40058B58C901375147EFD09357",
|
||||||
|
1446332405,
|
||||||
|
1446332400,
|
||||||
|
)
|
||||||
assert pos == (49.81755, 6.08442)
|
assert pos == (49.81755, 6.08442)
|
||||||
|
|
||||||
|
|
||||||
@@ -29,27 +43,29 @@ def test_adsb_position_with_ref():
|
|||||||
|
|
||||||
|
|
||||||
def test_adsb_airborne_position_with_ref():
|
def test_adsb_airborne_position_with_ref():
|
||||||
pos = adsb.airborne_position_with_ref("8D40058B58C901375147EFD09357",
|
pos = adsb.airborne_position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0)
|
||||||
49.0, 6.0)
|
|
||||||
assert pos == (49.82410, 6.06785)
|
assert pos == (49.82410, 6.06785)
|
||||||
pos = adsb.airborne_position_with_ref("8D40058B58C904A87F402D3B8C59",
|
pos = adsb.airborne_position_with_ref("8D40058B58C904A87F402D3B8C59", 49.0, 6.0)
|
||||||
49.0, 6.0)
|
|
||||||
assert pos == (49.81755, 6.08442)
|
assert pos == (49.81755, 6.08442)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_surface_position_with_ref():
|
def test_adsb_surface_position_with_ref():
|
||||||
pos = adsb.surface_position_with_ref("8FC8200A3AB8F5F893096B000000",
|
pos = adsb.surface_position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
|
||||||
-43.5, 172.5)
|
|
||||||
assert pos == (-43.48564, 172.53942)
|
assert pos == (-43.48564, 172.53942)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_surface_position():
|
def test_adsb_surface_position():
|
||||||
pos = adsb.surface_position("8CC8200A3AC8F009BCDEF2000000",
|
pos = adsb.surface_position(
|
||||||
"8FC8200A3AB8F5F893096B000000",
|
"8CC8200A3AC8F009BCDEF2000000",
|
||||||
0, 2,
|
"8FC8200A3AB8F5F893096B000000",
|
||||||
-43.496, 172.558)
|
0,
|
||||||
|
2,
|
||||||
|
-43.496,
|
||||||
|
172.558,
|
||||||
|
)
|
||||||
assert pos == (-43.48564, 172.53942)
|
assert pos == (-43.48564, 172.53942)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_alt():
|
def test_adsb_alt():
|
||||||
assert adsb.altitude("8D40058B58C901375147EFD09357") == 39000
|
assert adsb.altitude("8D40058B58C901375147EFD09357") == 39000
|
||||||
|
|
||||||
@@ -58,10 +74,10 @@ 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.88, -832, 'GS')
|
assert vgs == (159, 182.88, -832, "GS")
|
||||||
assert vas == (375, 243.98, -2304, 'TAS')
|
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
|
||||||
|
|
||||||
|
|
||||||
# def test_nic():
|
# def test_nic():
|
||||||
|
|||||||
@@ -1,20 +1,36 @@
|
|||||||
from pyModeS import bds
|
from pyModeS import bds
|
||||||
|
|
||||||
def test_bds_infer():
|
|
||||||
assert bds.infer("8D406B902015A678D4D220AA4BDA") == 'BDS08'
|
|
||||||
assert bds.infer("8FC8200A3AB8F5F893096B000000") == 'BDS06'
|
|
||||||
assert bds.infer("8D40058B58C901375147EFD09357") == 'BDS05'
|
|
||||||
assert bds.infer("8D485020994409940838175B284F") == 'BDS09'
|
|
||||||
|
|
||||||
assert bds.infer("A800178D10010080F50000D5893C") == 'BDS10'
|
def test_bds_infer():
|
||||||
assert bds.infer("A0000638FA81C10000000081A92F") == 'BDS17'
|
assert bds.infer("8D406B902015A678D4D220AA4BDA") == "BDS08"
|
||||||
assert bds.infer("A0001838201584F23468207CDFA5") == 'BDS20'
|
assert bds.infer("8FC8200A3AB8F5F893096B000000") == "BDS06"
|
||||||
assert bds.infer("A0001839CA3800315800007448D9") == 'BDS40'
|
assert bds.infer("8D40058B58C901375147EFD09357") == "BDS05"
|
||||||
assert bds.infer("A000139381951536E024D4CCF6B5") == 'BDS50'
|
assert bds.infer("8D485020994409940838175B284F") == "BDS09"
|
||||||
assert bds.infer("A00004128F39F91A7E27C46ADC21") == 'BDS60'
|
|
||||||
|
assert bds.infer("A800178D10010080F50000D5893C") == "BDS10"
|
||||||
|
assert bds.infer("A0000638FA81C10000000081A92F") == "BDS17"
|
||||||
|
assert bds.infer("A0001838201584F23468207CDFA5") == "BDS20"
|
||||||
|
assert bds.infer("A0001839CA3800315800007448D9") == "BDS40"
|
||||||
|
assert bds.infer("A000139381951536E024D4CCF6B5") == "BDS50"
|
||||||
|
assert bds.infer("A00004128F39F91A7E27C46ADC21") == "BDS60"
|
||||||
|
|
||||||
|
|
||||||
def test_bds_is50or60():
|
def test_bds_is50or60():
|
||||||
assert bds.is50or60("A0001838201584F23468207CDFA5", 0, 0, 0) == None
|
assert bds.is50or60("A0001838201584F23468207CDFA5", 0, 0, 0) == None
|
||||||
assert bds.is50or60("A0000000FFDA9517000464000000", 182, 237, 1250) == 'BDS50'
|
assert bds.is50or60("A0000000FFDA9517000464000000", 182, 237, 1250) == "BDS50"
|
||||||
assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == 'BDS60'
|
assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == "BDS60"
|
||||||
|
|
||||||
|
|
||||||
|
def test_surface_position():
|
||||||
|
msg0 = "8FE48C033A9FA184B934E744C6FD"
|
||||||
|
msg1 = "8FE48C033A9FA68F7C3D39B1C2F0"
|
||||||
|
|
||||||
|
t0 = 1565608663102
|
||||||
|
t1 = 1565608666214
|
||||||
|
|
||||||
|
lat_ref = -23.4265448
|
||||||
|
lon_ref = -46.4816258
|
||||||
|
|
||||||
|
lat, lon = bds.bds06.surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
|
||||||
|
|
||||||
|
assert abs(lon_ref - lon) < 0.05
|
||||||
|
|||||||
60
tests/test_c_common.py
Normal file
60
tests/test_c_common.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from pyModeS.decoder import c_common as common
|
||||||
|
|
||||||
|
|
||||||
|
def test_conversions():
|
||||||
|
assert common.hex2bin("6E406B") == "011011100100000001101011"
|
||||||
|
|
||||||
|
|
||||||
|
def test_crc_decode():
|
||||||
|
|
||||||
|
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
||||||
|
assert common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
||||||
|
assert common.crc("8d45cab390c39509496ca9a32912") == 0
|
||||||
|
assert common.crc("8d74802958c904e6ef4ba0184d5c") == 0
|
||||||
|
assert common.crc("8d4400cd9b0000b4f87000e71a10") == 0
|
||||||
|
assert common.crc("8d4065de58a1054a7ef0218e226a") == 0
|
||||||
|
|
||||||
|
assert common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
|
||||||
|
assert common.crc("a800089d8094e33a6004e4b8a522") == 4805588
|
||||||
|
assert common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
|
||||||
|
assert common.crc("a0000410bc900010a40000f5f477") == 11727682
|
||||||
|
assert common.crc("8d4ca251204994b1c36e60a5343d") == 16
|
||||||
|
assert common.crc("b0001718c65632b0a82040715b65") == 353333
|
||||||
|
|
||||||
|
|
||||||
|
def test_crc_encode():
|
||||||
|
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||||
|
assert parity == 11160538
|
||||||
|
|
||||||
|
|
||||||
|
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,12 +1,14 @@
|
|||||||
from pyModeS import bds, commb
|
from pyModeS import bds, commb
|
||||||
|
|
||||||
# from pyModeS import ehs, els # deprecated
|
# from pyModeS import ehs, els # deprecated
|
||||||
|
|
||||||
def test_bds20_callsign():
|
|
||||||
assert bds.bds20.cs20("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
|
|
||||||
assert bds.bds20.cs20("A0001993202422F2E37CE038738E") == 'IBK2873_'
|
|
||||||
|
|
||||||
assert commb.cs20("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
|
def test_bds20_callsign():
|
||||||
assert commb.cs20("A0001993202422F2E37CE038738E") == 'IBK2873_'
|
assert bds.bds20.cs20("A000083E202CC371C31DE0AA1CCF") == "KLM1017_"
|
||||||
|
assert bds.bds20.cs20("A0001993202422F2E37CE038738E") == "IBK2873_"
|
||||||
|
|
||||||
|
assert commb.cs20("A000083E202CC371C31DE0AA1CCF") == "KLM1017_"
|
||||||
|
assert commb.cs20("A0001993202422F2E37CE038738E") == "IBK2873_"
|
||||||
|
|
||||||
|
|
||||||
def test_bds40_functions():
|
def test_bds40_functions():
|
||||||
@@ -21,14 +23,14 @@ def test_bds40_functions():
|
|||||||
|
|
||||||
def test_bds50_functions():
|
def test_bds50_functions():
|
||||||
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||||
assert bds.bds50.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
assert bds.bds50.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
||||||
assert bds.bds50.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
assert bds.bds50.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||||
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 commb.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
assert commb.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||||
assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
||||||
assert commb.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
assert commb.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||||
assert commb.gs50("A000139381951536E024D4CCF6B5") == 438
|
assert commb.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||||
assert commb.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
assert commb.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||||
|
|||||||
@@ -1,58 +1,62 @@
|
|||||||
from pyModeS import common
|
from pyModeS.decoder import common
|
||||||
|
|
||||||
|
|
||||||
def test_conversions():
|
def test_conversions():
|
||||||
assert common.hex2bin('6E406B') == "011011100100000001101011"
|
assert common.hex2bin("6E406B") == "011011100100000001101011"
|
||||||
assert common.bin2hex('011011100100000001101011') == "6E406B"
|
|
||||||
assert common.int2hex(11160538) == "AA4BDA"
|
|
||||||
|
|
||||||
def test_crc_decode():
|
def test_crc_decode():
|
||||||
assert common.crc_legacy("8D406B902015A678D4D220AA4BDA") == 0
|
assert common.crc_legacy("8D406B902015A678D4D220AA4BDA") == 0
|
||||||
|
|
||||||
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
||||||
assert common.crc('8d8960ed58bf053cf11bc5932b7d') == 0
|
assert common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
||||||
assert common.crc('8d45cab390c39509496ca9a32912') == 0
|
assert common.crc("8d45cab390c39509496ca9a32912") == 0
|
||||||
assert common.crc('8d49d3d4e1089d00000000744c3b') == 0
|
assert common.crc("8d49d3d4e1089d00000000744c3b") == 0
|
||||||
assert common.crc('8d74802958c904e6ef4ba0184d5c') == 0
|
assert common.crc("8d74802958c904e6ef4ba0184d5c") == 0
|
||||||
assert common.crc('8d4400cd9b0000b4f87000e71a10') == 0
|
assert common.crc("8d4400cd9b0000b4f87000e71a10") == 0
|
||||||
assert common.crc('8d4065de58a1054a7ef0218e226a') == 0
|
assert common.crc("8d4065de58a1054a7ef0218e226a") == 0
|
||||||
|
|
||||||
|
assert common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
|
||||||
|
assert common.crc("a800089d8094e33a6004e4b8a522") == 4805588
|
||||||
|
assert common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
|
||||||
|
assert common.crc("a0000410bc900010a40000f5f477") == 11727682
|
||||||
|
assert common.crc("8d4ca251204994b1c36e60a5343d") == 16
|
||||||
|
assert common.crc("b0001718c65632b0a82040715b65") == 353333
|
||||||
|
|
||||||
assert common.crc('c80b2dca34aa21dd821a04cb64d4') == 10719924
|
|
||||||
assert common.crc('a800089d8094e33a6004e4b8a522') == 4805588
|
|
||||||
assert common.crc('a8000614a50b6d32bed000bbe0ed') == 5659991
|
|
||||||
assert common.crc('a0000410bc900010a40000f5f477') == 11727682
|
|
||||||
assert common.crc('8d4ca251204994b1c36e60a5343d') == 16
|
|
||||||
assert common.crc('b0001718c65632b0a82040715b65') == 353333
|
|
||||||
|
|
||||||
def test_crc_encode():
|
def test_crc_encode():
|
||||||
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||||
assert common.int2hex(parity) == "AA4BDA"
|
assert parity == 11160538
|
||||||
|
|
||||||
|
|
||||||
def test_icao():
|
def test_icao():
|
||||||
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||||
assert common.icao("A0001839CA3800315800007448D9") == '400940'
|
assert common.icao("A0001839CA3800315800007448D9") == "400940"
|
||||||
assert common.icao("A000139381951536E024D4CCF6B5") == '3C4DD2'
|
assert common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
|
||||||
assert common.icao("A000029CFFBAA11E2004727281F1") == '4243D0'
|
assert common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
|
||||||
|
|
||||||
|
|
||||||
def test_modes_altcode():
|
def test_modes_altcode():
|
||||||
assert common.altcode("A02014B400000000000000F9D514") == 32300
|
assert common.altcode("A02014B400000000000000F9D514") == 32300
|
||||||
|
|
||||||
|
|
||||||
def test_modes_idcode():
|
def test_modes_idcode():
|
||||||
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == '1346'
|
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
|
||||||
|
|
||||||
|
|
||||||
def test_graycode_to_altitude():
|
def test_graycode_to_altitude():
|
||||||
assert common.gray2alt('00000000010') == -1000
|
assert common.gray2alt("00000000010") == -1000
|
||||||
assert common.gray2alt('00000001010') == -500
|
assert common.gray2alt("00000001010") == -500
|
||||||
assert common.gray2alt('00000011011') == -100
|
assert common.gray2alt("00000011011") == -100
|
||||||
assert common.gray2alt('00000011010') == 0
|
assert common.gray2alt("00000011010") == 0
|
||||||
assert common.gray2alt('00000011110') == 100
|
assert common.gray2alt("00000011110") == 100
|
||||||
assert common.gray2alt('00000010011') == 600
|
assert common.gray2alt("00000010011") == 600
|
||||||
assert common.gray2alt('00000110010') == 1000
|
assert common.gray2alt("00000110010") == 1000
|
||||||
assert common.gray2alt('00001001001') == 5800
|
assert common.gray2alt("00001001001") == 5800
|
||||||
assert common.gray2alt('00011100100') == 10300
|
assert common.gray2alt("00011100100") == 10300
|
||||||
assert common.gray2alt('01100011010') == 32000
|
assert common.gray2alt("01100011010") == 32000
|
||||||
assert common.gray2alt('01110000100') == 46300
|
assert common.gray2alt("01110000100") == 46300
|
||||||
assert common.gray2alt('01010101100') == 50200
|
assert common.gray2alt("01010101100") == 50200
|
||||||
assert common.gray2alt('11011110100') == 73200
|
assert common.gray2alt("11011110100") == 73200
|
||||||
assert common.gray2alt('10000000011') == 126600
|
assert common.gray2alt("10000000011") == 126600
|
||||||
assert common.gray2alt('10000000001') == 126700
|
assert common.gray2alt("10000000001") == 126700
|
||||||
|
|||||||
20
tests/test_tell.py
Normal file
20
tests/test_tell.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from pyModeS.decoder import tell
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
"8D406B902015A678D4D220AA4BDA",
|
||||||
|
"8FC8200A3AB8F5F893096B000000",
|
||||||
|
"8D40058B58C901375147EFD09357",
|
||||||
|
"8D485020994409940838175B284F",
|
||||||
|
"A000083E202CC371C31DE0AA1CCF",
|
||||||
|
"A8001E2520053332C1A820363386",
|
||||||
|
"A000029C85E42F313000007047D3",
|
||||||
|
"A5DC282C2A0108372CA6DA9693B0",
|
||||||
|
"A00015B8C26A00328400004242DA",
|
||||||
|
"A000139381951536E024D4CCF6B5",
|
||||||
|
"A00004128F39F91A7E27C46ADC21",
|
||||||
|
]
|
||||||
|
|
||||||
|
print("-" * 70)
|
||||||
|
for m in messages:
|
||||||
|
tell(m)
|
||||||
|
print("-" * 70)
|
||||||
Reference in New Issue
Block a user